From 6533cd198db5819bd78e3e148d1ea40aa229a2f6 Mon Sep 17 00:00:00 2001 From: Alexander Motin Date: Fri, 21 May 2010 13:29:28 +0000 Subject: [PATCH] Improve suspend/resume support. Make sure controller is idle on suspend and reset it on resume. --- sys/dev/ahci/ahci.c | 77 +++++++++++++++++++++++++++++-------------- sys/dev/ata/ata-all.c | 19 ++++++++--- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/sys/dev/ahci/ahci.c b/sys/dev/ahci/ahci.c index 1aa719e044be..2cd40a27f6c1 100644 --- a/sys/dev/ahci/ahci.c +++ b/sys/dev/ahci/ahci.c @@ -60,6 +60,8 @@ static void ahci_intr(void *data); static void ahci_intr_one(void *data); static int ahci_suspend(device_t dev); static int ahci_resume(device_t dev); +static int ahci_ch_init(device_t dev); +static int ahci_ch_deinit(device_t dev); static int ahci_ch_suspend(device_t dev); static int ahci_ch_resume(device_t dev); static void ahci_ch_pm(void *arg); @@ -877,7 +879,7 @@ ahci_ch_attach(device_t dev) return (ENXIO); ahci_dmainit(dev); ahci_slotsalloc(dev); - ahci_ch_resume(dev); + ahci_ch_init(dev); mtx_lock(&ch->mtx); rid = ATA_IRQ_RID; if (!(ch->r_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, @@ -969,7 +971,7 @@ ahci_ch_detach(device_t dev) bus_teardown_intr(dev, ch->r_irq, ch->ih); bus_release_resource(dev, SYS_RES_IRQ, ATA_IRQ_RID, ch->r_irq); - ahci_ch_suspend(dev); + ahci_ch_deinit(dev); ahci_slotsfree(dev); ahci_dmafini(dev); @@ -979,28 +981,7 @@ ahci_ch_detach(device_t dev) } static int -ahci_ch_suspend(device_t dev) -{ - struct ahci_channel *ch = device_get_softc(dev); - - /* Disable port interrupts. */ - ATA_OUTL(ch->r_mem, AHCI_P_IE, 0); - /* Reset command register. */ - ahci_stop(dev); - ahci_stop_fr(dev); - ATA_OUTL(ch->r_mem, AHCI_P_CMD, 0); - /* Allow everything, including partial and slumber modes. */ - ATA_OUTL(ch->r_mem, AHCI_P_SCTL, 0); - /* Request slumber mode transition and give some time to get there. */ - ATA_OUTL(ch->r_mem, AHCI_P_CMD, AHCI_P_CMD_SLUMBER); - DELAY(100); - /* Disable PHY. */ - ATA_OUTL(ch->r_mem, AHCI_P_SCTL, ATA_SC_DET_DISABLE); - return (0); -} - -static int -ahci_ch_resume(device_t dev) +ahci_ch_init(device_t dev) { struct ahci_channel *ch = device_get_softc(dev); uint64_t work; @@ -1024,6 +1005,54 @@ ahci_ch_resume(device_t dev) return (0); } +static int +ahci_ch_deinit(device_t dev) +{ + struct ahci_channel *ch = device_get_softc(dev); + + /* Disable port interrupts. */ + ATA_OUTL(ch->r_mem, AHCI_P_IE, 0); + /* Reset command register. */ + ahci_stop(dev); + ahci_stop_fr(dev); + ATA_OUTL(ch->r_mem, AHCI_P_CMD, 0); + /* Allow everything, including partial and slumber modes. */ + ATA_OUTL(ch->r_mem, AHCI_P_SCTL, 0); + /* Request slumber mode transition and give some time to get there. */ + ATA_OUTL(ch->r_mem, AHCI_P_CMD, AHCI_P_CMD_SLUMBER); + DELAY(100); + /* Disable PHY. */ + ATA_OUTL(ch->r_mem, AHCI_P_SCTL, ATA_SC_DET_DISABLE); + return (0); +} + +static int +ahci_ch_suspend(device_t dev) +{ + struct ahci_channel *ch = device_get_softc(dev); + + mtx_lock(&ch->mtx); + xpt_freeze_simq(ch->sim, 1); + while (ch->oslots) + msleep(ch, &ch->mtx, PRIBIO, "ahcisusp", hz/100); + ahci_ch_deinit(dev); + mtx_unlock(&ch->mtx); + return (0); +} + +static int +ahci_ch_resume(device_t dev) +{ + struct ahci_channel *ch = device_get_softc(dev); + + mtx_lock(&ch->mtx); + ahci_ch_init(dev); + ahci_reset(dev); + xpt_release_simq(ch->sim, TRUE); + mtx_unlock(&ch->mtx); + return (0); +} + devclass_t ahcich_devclass; static device_method_t ahcich_methods[] = { DEVMETHOD(device_probe, ahci_ch_probe), diff --git a/sys/dev/ata/ata-all.c b/sys/dev/ata/ata-all.c index 5c6f0db714f1..707cc8c249a9 100644 --- a/sys/dev/ata/ata-all.c +++ b/sys/dev/ata/ata-all.c @@ -432,7 +432,13 @@ ata_suspend(device_t dev) if (!dev || !(ch = device_get_softc(dev))) return ENXIO; -#ifndef ATA_CAM +#ifdef ATA_CAM + mtx_lock(&ch->state_mtx); + xpt_freeze_simq(ch->sim, 1); + while (ch->state != ATA_IDLE) + msleep(ch, &ch->state_mtx, PRIBIO, "atasusp", hz/100); + mtx_unlock(&ch->state_mtx); +#else /* wait for the channel to be IDLE or detached before suspending */ while (ch->r_irq) { mtx_lock(&ch->state_mtx); @@ -452,16 +458,21 @@ ata_suspend(device_t dev) int ata_resume(device_t dev) { + struct ata_channel *ch; int error; /* check for valid device */ - if (!dev || !device_get_softc(dev)) + if (!dev || !(ch = device_get_softc(dev))) return ENXIO; +#ifdef ATA_CAM + mtx_lock(&ch->state_mtx); + error = ata_reinit(dev); + xpt_release_simq(ch->sim, TRUE); + mtx_unlock(&ch->state_mtx); +#else /* reinit the devices, we dont know what mode/state they are in */ error = ata_reinit(dev); - -#ifndef ATA_CAM /* kick off requests on the queue */ ata_start(dev); #endif