diff options
author | Tejun Heo <htejun@gmail.com> | 2006-07-03 03:07:27 -0400 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2006-07-05 22:16:28 -0400 |
commit | 500530f652f9e5dabe7571b018dec47742ce0f16 (patch) | |
tree | cb9653c45a7e37d9bfe8dcc3923ed6b33ca134ea /drivers/scsi/libata-eh.c | |
parent | d6f26d1f1f1128a896f38a7f8426daed0a1205a2 (diff) |
[PATCH] libata: reimplement controller-wide PM
Reimplement controller-wide PM. ata_host_set_suspend/resume() are
defined to suspend and resume a host_set. While suspended, EHs for
all ports in the host_set are pegged using ATA_FLAG_SUSPENDED and
frozen.
Because SCSI device hotplug is done asynchronously against the rest of
libata EH and the same mutex is used when adding new device, suspend
cannot wait for hotplug to complete. So, if SCSI device hotplug is in
progress, suspend fails with -EBUSY.
In most cases, host_set resume is followed by device resume. As each
resume operation requires a reset, a single host_set-wide resume
operation may result in multiple resets. To avoid this, resume waits
upto 1 second giving PM to request resume for devices.
Signed-off-by: Tejun Heo <htejun@gmail.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers/scsi/libata-eh.c')
-rw-r--r-- | drivers/scsi/libata-eh.c | 128 |
1 files changed, 126 insertions, 2 deletions
diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c index b9df49a36214..4b6aa30f4d68 100644 --- a/drivers/scsi/libata-eh.c +++ b/drivers/scsi/libata-eh.c | |||
@@ -47,6 +47,8 @@ | |||
47 | 47 | ||
48 | static void __ata_port_freeze(struct ata_port *ap); | 48 | static void __ata_port_freeze(struct ata_port *ap); |
49 | static void ata_eh_finish(struct ata_port *ap); | 49 | static void ata_eh_finish(struct ata_port *ap); |
50 | static void ata_eh_handle_port_suspend(struct ata_port *ap); | ||
51 | static void ata_eh_handle_port_resume(struct ata_port *ap); | ||
50 | 52 | ||
51 | static void ata_ering_record(struct ata_ering *ering, int is_io, | 53 | static void ata_ering_record(struct ata_ering *ering, int is_io, |
52 | unsigned int err_mask) | 54 | unsigned int err_mask) |
@@ -262,6 +264,9 @@ void ata_scsi_error(struct Scsi_Host *host) | |||
262 | repeat: | 264 | repeat: |
263 | /* invoke error handler */ | 265 | /* invoke error handler */ |
264 | if (ap->ops->error_handler) { | 266 | if (ap->ops->error_handler) { |
267 | /* process port resume request */ | ||
268 | ata_eh_handle_port_resume(ap); | ||
269 | |||
265 | /* fetch & clear EH info */ | 270 | /* fetch & clear EH info */ |
266 | spin_lock_irqsave(ap->lock, flags); | 271 | spin_lock_irqsave(ap->lock, flags); |
267 | 272 | ||
@@ -274,12 +279,15 @@ void ata_scsi_error(struct Scsi_Host *host) | |||
274 | 279 | ||
275 | spin_unlock_irqrestore(ap->lock, flags); | 280 | spin_unlock_irqrestore(ap->lock, flags); |
276 | 281 | ||
277 | /* invoke EH. if unloading, just finish failed qcs */ | 282 | /* invoke EH, skip if unloading or suspended */ |
278 | if (!(ap->pflags & ATA_PFLAG_UNLOADING)) | 283 | if (!(ap->pflags & (ATA_PFLAG_UNLOADING | ATA_PFLAG_SUSPENDED))) |
279 | ap->ops->error_handler(ap); | 284 | ap->ops->error_handler(ap); |
280 | else | 285 | else |
281 | ata_eh_finish(ap); | 286 | ata_eh_finish(ap); |
282 | 287 | ||
288 | /* process port suspend request */ | ||
289 | ata_eh_handle_port_suspend(ap); | ||
290 | |||
283 | /* Exception might have happend after ->error_handler | 291 | /* Exception might have happend after ->error_handler |
284 | * recovered the port but before this point. Repeat | 292 | * recovered the port but before this point. Repeat |
285 | * EH in such case. | 293 | * EH in such case. |
@@ -2101,3 +2109,119 @@ void ata_do_eh(struct ata_port *ap, ata_prereset_fn_t prereset, | |||
2101 | ata_eh_recover(ap, prereset, softreset, hardreset, postreset); | 2109 | ata_eh_recover(ap, prereset, softreset, hardreset, postreset); |
2102 | ata_eh_finish(ap); | 2110 | ata_eh_finish(ap); |
2103 | } | 2111 | } |
2112 | |||
2113 | /** | ||
2114 | * ata_eh_handle_port_suspend - perform port suspend operation | ||
2115 | * @ap: port to suspend | ||
2116 | * | ||
2117 | * Suspend @ap. | ||
2118 | * | ||
2119 | * LOCKING: | ||
2120 | * Kernel thread context (may sleep). | ||
2121 | */ | ||
2122 | static void ata_eh_handle_port_suspend(struct ata_port *ap) | ||
2123 | { | ||
2124 | unsigned long flags; | ||
2125 | int rc = 0; | ||
2126 | |||
2127 | /* are we suspending? */ | ||
2128 | spin_lock_irqsave(ap->lock, flags); | ||
2129 | if (!(ap->pflags & ATA_PFLAG_PM_PENDING) || | ||
2130 | ap->pm_mesg.event == PM_EVENT_ON) { | ||
2131 | spin_unlock_irqrestore(ap->lock, flags); | ||
2132 | return; | ||
2133 | } | ||
2134 | spin_unlock_irqrestore(ap->lock, flags); | ||
2135 | |||
2136 | WARN_ON(ap->pflags & ATA_PFLAG_SUSPENDED); | ||
2137 | |||
2138 | /* suspend */ | ||
2139 | ata_eh_freeze_port(ap); | ||
2140 | |||
2141 | if (ap->ops->port_suspend) | ||
2142 | rc = ap->ops->port_suspend(ap, ap->pm_mesg); | ||
2143 | |||
2144 | /* report result */ | ||
2145 | spin_lock_irqsave(ap->lock, flags); | ||
2146 | |||
2147 | ap->pflags &= ~ATA_PFLAG_PM_PENDING; | ||
2148 | if (rc == 0) | ||
2149 | ap->pflags |= ATA_PFLAG_SUSPENDED; | ||
2150 | else | ||
2151 | ata_port_schedule_eh(ap); | ||
2152 | |||
2153 | if (ap->pm_result) { | ||
2154 | *ap->pm_result = rc; | ||
2155 | ap->pm_result = NULL; | ||
2156 | } | ||
2157 | |||
2158 | spin_unlock_irqrestore(ap->lock, flags); | ||
2159 | |||
2160 | return; | ||
2161 | } | ||
2162 | |||
2163 | /** | ||
2164 | * ata_eh_handle_port_resume - perform port resume operation | ||
2165 | * @ap: port to resume | ||
2166 | * | ||
2167 | * Resume @ap. | ||
2168 | * | ||
2169 | * This function also waits upto one second until all devices | ||
2170 | * hanging off this port requests resume EH action. This is to | ||
2171 | * prevent invoking EH and thus reset multiple times on resume. | ||
2172 | * | ||
2173 | * On DPM resume, where some of devices might not be resumed | ||
2174 | * together, this may delay port resume upto one second, but such | ||
2175 | * DPM resumes are rare and 1 sec delay isn't too bad. | ||
2176 | * | ||
2177 | * LOCKING: | ||
2178 | * Kernel thread context (may sleep). | ||
2179 | */ | ||
2180 | static void ata_eh_handle_port_resume(struct ata_port *ap) | ||
2181 | { | ||
2182 | unsigned long timeout; | ||
2183 | unsigned long flags; | ||
2184 | int i, rc = 0; | ||
2185 | |||
2186 | /* are we resuming? */ | ||
2187 | spin_lock_irqsave(ap->lock, flags); | ||
2188 | if (!(ap->pflags & ATA_PFLAG_PM_PENDING) || | ||
2189 | ap->pm_mesg.event != PM_EVENT_ON) { | ||
2190 | spin_unlock_irqrestore(ap->lock, flags); | ||
2191 | return; | ||
2192 | } | ||
2193 | spin_unlock_irqrestore(ap->lock, flags); | ||
2194 | |||
2195 | /* spurious? */ | ||
2196 | if (!(ap->pflags & ATA_PFLAG_SUSPENDED)) | ||
2197 | goto done; | ||
2198 | |||
2199 | if (ap->ops->port_resume) | ||
2200 | rc = ap->ops->port_resume(ap); | ||
2201 | |||
2202 | /* give devices time to request EH */ | ||
2203 | timeout = jiffies + HZ; /* 1s max */ | ||
2204 | while (1) { | ||
2205 | for (i = 0; i < ATA_MAX_DEVICES; i++) { | ||
2206 | struct ata_device *dev = &ap->device[i]; | ||
2207 | unsigned int action = ata_eh_dev_action(dev); | ||
2208 | |||
2209 | if ((dev->flags & ATA_DFLAG_SUSPENDED) && | ||
2210 | !(action & ATA_EH_RESUME)) | ||
2211 | break; | ||
2212 | } | ||
2213 | |||
2214 | if (i == ATA_MAX_DEVICES || time_after(jiffies, timeout)) | ||
2215 | break; | ||
2216 | msleep(10); | ||
2217 | } | ||
2218 | |||
2219 | done: | ||
2220 | spin_lock_irqsave(ap->lock, flags); | ||
2221 | ap->pflags &= ~(ATA_PFLAG_PM_PENDING | ATA_PFLAG_SUSPENDED); | ||
2222 | if (ap->pm_result) { | ||
2223 | *ap->pm_result = rc; | ||
2224 | ap->pm_result = NULL; | ||
2225 | } | ||
2226 | spin_unlock_irqrestore(ap->lock, flags); | ||
2227 | } | ||