Implement unload unplug - driver unloading / PCI removal via hot unplug path. This is done by ata_port_detach() which requests detach of all devices, schedules EH and wait for it to complete. EH path is slightly modified to handle this (e.g. force zero eh_info during unloading). With this patch, EH and unloading are properly synchronized and unloading should be safe under any circumstances. Signed-off-by: Tejun Heo <htejun@xxxxxxxxx> --- drivers/scsi/ahci.c | 10 ++----- drivers/scsi/libata-core.c | 59 ++++++++++++++++++++++++++++++++++++++++---- drivers/scsi/libata-eh.c | 7 +++-- include/linux/libata.h | 1 + 4 files changed, 61 insertions(+), 16 deletions(-) dc54139a72085d78f1fc35a721cff16cdeb38ef9 diff --git a/drivers/scsi/ahci.c b/drivers/scsi/ahci.c index afb3805..60f455b 100644 --- a/drivers/scsi/ahci.c +++ b/drivers/scsi/ahci.c @@ -1389,21 +1389,17 @@ static void ahci_remove_one (struct pci_ struct device *dev = pci_dev_to_dev(pdev); struct ata_host_set *host_set = dev_get_drvdata(dev); struct ahci_host_priv *hpriv = host_set->private_data; - struct ata_port *ap; unsigned int i; int have_msi; - for (i = 0; i < host_set->n_ports; i++) { - ap = host_set->ports[i]; - - scsi_remove_host(ap->host); - } + for (i = 0; i < host_set->n_ports; i++) + ata_port_detach(host_set->ports[i]); have_msi = hpriv->flags & AHCI_FLAG_MSI; free_irq(host_set->irq, host_set); for (i = 0; i < host_set->n_ports; i++) { - ap = host_set->ports[i]; + struct ata_port *ap = host_set->ports[i]; ata_scsi_release(ap->host); scsi_host_put(ap->host); diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index f45e04f..b622c61 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c @@ -5613,6 +5613,55 @@ err_free_ret: } /** + * ata_port_detach - Detach ATA port in prepration of device removal + * @ap: ATA port to be detached + * + * Detach all ATA devices and the associated SCSI devices of @ap; + * then, remove the associated SCSI host. @ap is guaranteed to + * be quiescent on return from this function. + * + * LOCKING: + * Kernel thread context (may sleep). + */ +void ata_port_detach(struct ata_port *ap) +{ + unsigned long flags; + int i; + + if (!ap->ops->error_handler) + return; + + /* tell EH to self-destruct */ + spin_lock_irqsave(&ap->host_set->lock, flags); + + ap->flags |= ATA_FLAG_UNLOADING; + for (i = 0; i < ATA_MAX_DEVICES; i++) + ap->device[i].flags |= ATA_DFLAG_DETACH; + + ata_port_schedule_eh(ap); + + spin_unlock_irqrestore(&ap->host_set->lock, flags); + + /* wait for EH */ + while (scsi_host_in_recovery(ap->host)) + msleep(100); + + /* Flush hotplug task. The sequence is similar to + * ata_port_flush_task(). ATA_FLAG_UNLOADING prevents further + * queueing of hotplug_task. + */ + flush_workqueue(ata_scsi_wq); + cancel_delayed_work(&ap->hotplug_task); + flush_workqueue(ata_scsi_wq); + + /* freeze the port */ + ata_eh_freeze_port(ap); + + /* remove the associated SCSI host */ + scsi_remove_host(ap->host); +} + +/** * ata_host_set_remove - PCI layer callback for device removal * @host_set: ATA host set that was removed * @@ -5625,18 +5674,15 @@ err_free_ret: void ata_host_set_remove(struct ata_host_set *host_set) { - struct ata_port *ap; unsigned int i; - for (i = 0; i < host_set->n_ports; i++) { - ap = host_set->ports[i]; - scsi_remove_host(ap->host); - } + for (i = 0; i < host_set->n_ports; i++) + ata_port_detach(host_set->ports[i]); free_irq(host_set->irq, host_set); for (i = 0; i < host_set->n_ports; i++) { - ap = host_set->ports[i]; + struct ata_port *ap = host_set->ports[i]; ata_scsi_release(ap->host); @@ -5904,6 +5950,7 @@ EXPORT_SYMBOL_GPL(sata_deb_timing_before EXPORT_SYMBOL_GPL(ata_std_bios_param); EXPORT_SYMBOL_GPL(ata_std_ports); EXPORT_SYMBOL_GPL(ata_device_add); +EXPORT_SYMBOL_GPL(ata_port_detach); EXPORT_SYMBOL_GPL(ata_host_set_remove); EXPORT_SYMBOL_GPL(ata_sg_init); EXPORT_SYMBOL_GPL(ata_sg_init_one); diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c index 7b5cb02..9eb73bb 100644 --- a/drivers/scsi/libata-eh.c +++ b/drivers/scsi/libata-eh.c @@ -234,7 +234,8 @@ void ata_scsi_error(struct Scsi_Host *ho spin_lock_irqsave(hs_lock, flags); memset(&ap->eh_context, 0, sizeof(ap->eh_context)); - ap->eh_context.i = ap->eh_info; + if (!(ap->flags & ATA_FLAG_UNLOADING)) + ap->eh_context.i = ap->eh_info; memset(&ap->eh_info, 0, sizeof(ap->eh_info)); ap->flags &= ~ATA_FLAG_EH_PENDING; @@ -290,7 +291,7 @@ void ata_scsi_error(struct Scsi_Host *ho int bit = fls(ATA_FLAG_LOADING) - 1; clear_bit(bit, &ap->flags); wake_up_bit(&ap->flags, bit); - } else { + } else if (!(ap->flags & ATA_FLAG_UNLOADING)) { if (ap->flags & ATA_FLAG_SCSI_HOTPLUG) queue_work(ata_scsi_wq, &ap->hotplug_task); if (ap->flags & ATA_FLAG_RECOVERED) @@ -1769,7 +1770,7 @@ void ata_do_eh(struct ata_port *ap, ata_ ata_reset_fn_t softreset, ata_reset_fn_t hardreset, ata_postreset_fn_t postreset) { - if (!(ap->flags & ATA_FLAG_LOADING)) { + if (!(ap->flags & (ATA_FLAG_LOADING | ATA_FLAG_UNLOADING))) { ata_eh_autopsy(ap); ata_eh_report(ap); } diff --git a/include/linux/libata.h b/include/linux/libata.h index b216400..99a2b68 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -647,6 +647,7 @@ extern int ata_pci_device_resume(struct extern int ata_pci_clear_simplex(struct pci_dev *pdev); #endif /* CONFIG_PCI */ extern int ata_device_add(const struct ata_probe_ent *ent); +extern void ata_port_detach(struct ata_port *ap); extern void ata_host_set_remove(struct ata_host_set *host_set); extern int ata_scsi_detect(struct scsi_host_template *sht); extern int ata_scsi_ioctl(struct scsi_device *dev, int cmd, void __user *arg); -- 1.3.2 - : send the line "unsubscribe linux-ide" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html