diff options
author | Tejun Heo <htejun@gmail.com> | 2006-05-31 05:28:05 -0400 |
---|---|---|
committer | Tejun Heo <htejun@gmail.com> | 2006-05-31 05:28:05 -0400 |
commit | 580b2102327ab8444af5bde4e70b50d268a1d558 (patch) | |
tree | 01389e898e09dbaddb06405b182116b9be0e6d59 /drivers | |
parent | 084fe639b81c4d418a2cf714acb0475e3713cb73 (diff) |
[PATCH] libata-hp: implement SCSI part of hotplug
Implement SCSI part of hotplug.
This must be done in a separate context as SCSI makes use of EH during
probing. SCSI scan fails silently if EH is in progress. In such
cases, libata pauses briefly and retries until every device is
attached.
Signed-off-by: Tejun Heo <htejun@gmail.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/scsi/libata-core.c | 1 | ||||
-rw-r--r-- | drivers/scsi/libata-eh.c | 6 | ||||
-rw-r--r-- | drivers/scsi/libata-scsi.c | 116 | ||||
-rw-r--r-- | drivers/scsi/libata.h | 1 |
4 files changed, 123 insertions, 1 deletions
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index c965eea3b3d4..8df8ecc51a78 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c | |||
@@ -5370,6 +5370,7 @@ static void ata_host_init(struct ata_port *ap, struct Scsi_Host *host, | |||
5370 | ap->msg_enable = ATA_MSG_DRV; | 5370 | ap->msg_enable = ATA_MSG_DRV; |
5371 | 5371 | ||
5372 | INIT_WORK(&ap->port_task, NULL, NULL); | 5372 | INIT_WORK(&ap->port_task, NULL, NULL); |
5373 | INIT_WORK(&ap->hotplug_task, ata_scsi_hotplug, ap); | ||
5373 | INIT_LIST_HEAD(&ap->eh_done_q); | 5374 | INIT_LIST_HEAD(&ap->eh_done_q); |
5374 | init_waitqueue_head(&ap->eh_wait_q); | 5375 | init_waitqueue_head(&ap->eh_wait_q); |
5375 | 5376 | ||
diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c index b53e2e7db498..733dfa532977 100644 --- a/drivers/scsi/libata-eh.c +++ b/drivers/scsi/libata-eh.c | |||
@@ -287,9 +287,13 @@ void ata_scsi_error(struct Scsi_Host *host) | |||
287 | /* clean up */ | 287 | /* clean up */ |
288 | spin_lock_irqsave(hs_lock, flags); | 288 | spin_lock_irqsave(hs_lock, flags); |
289 | 289 | ||
290 | if (ap->flags & ATA_FLAG_SCSI_HOTPLUG) | ||
291 | queue_work(ata_aux_wq, &ap->hotplug_task); | ||
292 | |||
290 | if (ap->flags & ATA_FLAG_RECOVERED) | 293 | if (ap->flags & ATA_FLAG_RECOVERED) |
291 | ata_port_printk(ap, KERN_INFO, "EH complete\n"); | 294 | ata_port_printk(ap, KERN_INFO, "EH complete\n"); |
292 | ap->flags &= ~ATA_FLAG_RECOVERED; | 295 | |
296 | ap->flags &= ~(ATA_FLAG_SCSI_HOTPLUG | ATA_FLAG_RECOVERED); | ||
293 | 297 | ||
294 | /* tell wait_eh that we're done */ | 298 | /* tell wait_eh that we're done */ |
295 | ap->flags &= ~ATA_FLAG_EH_IN_PROGRESS; | 299 | ap->flags &= ~ATA_FLAG_EH_IN_PROGRESS; |
diff --git a/drivers/scsi/libata-scsi.c b/drivers/scsi/libata-scsi.c index 3dc6188af0e8..12563998d97c 100644 --- a/drivers/scsi/libata-scsi.c +++ b/drivers/scsi/libata-scsi.c | |||
@@ -2786,3 +2786,119 @@ int ata_scsi_offline_dev(struct ata_device *dev) | |||
2786 | } | 2786 | } |
2787 | return 0; | 2787 | return 0; |
2788 | } | 2788 | } |
2789 | |||
2790 | /** | ||
2791 | * ata_scsi_remove_dev - remove attached SCSI device | ||
2792 | * @dev: ATA device to remove attached SCSI device for | ||
2793 | * | ||
2794 | * This function is called from ata_eh_scsi_hotplug() and | ||
2795 | * responsible for removing the SCSI device attached to @dev. | ||
2796 | * | ||
2797 | * LOCKING: | ||
2798 | * Kernel thread context (may sleep). | ||
2799 | */ | ||
2800 | static void ata_scsi_remove_dev(struct ata_device *dev) | ||
2801 | { | ||
2802 | struct ata_port *ap = dev->ap; | ||
2803 | struct scsi_device *sdev; | ||
2804 | unsigned long flags; | ||
2805 | |||
2806 | /* Alas, we need to grab scan_mutex to ensure SCSI device | ||
2807 | * state doesn't change underneath us and thus | ||
2808 | * scsi_device_get() always succeeds. The mutex locking can | ||
2809 | * be removed if there is __scsi_device_get() interface which | ||
2810 | * increments reference counts regardless of device state. | ||
2811 | */ | ||
2812 | mutex_lock(&ap->host->scan_mutex); | ||
2813 | spin_lock_irqsave(&ap->host_set->lock, flags); | ||
2814 | |||
2815 | /* clearing dev->sdev is protected by host_set lock */ | ||
2816 | sdev = dev->sdev; | ||
2817 | dev->sdev = NULL; | ||
2818 | |||
2819 | if (sdev) { | ||
2820 | /* If user initiated unplug races with us, sdev can go | ||
2821 | * away underneath us after the host_set lock and | ||
2822 | * scan_mutex are released. Hold onto it. | ||
2823 | */ | ||
2824 | if (scsi_device_get(sdev) == 0) { | ||
2825 | /* The following ensures the attached sdev is | ||
2826 | * offline on return from ata_scsi_offline_dev() | ||
2827 | * regardless it wins or loses the race | ||
2828 | * against this function. | ||
2829 | */ | ||
2830 | scsi_device_set_state(sdev, SDEV_OFFLINE); | ||
2831 | } else { | ||
2832 | WARN_ON(1); | ||
2833 | sdev = NULL; | ||
2834 | } | ||
2835 | } | ||
2836 | |||
2837 | spin_unlock_irqrestore(&ap->host_set->lock, flags); | ||
2838 | mutex_unlock(&ap->host->scan_mutex); | ||
2839 | |||
2840 | if (sdev) { | ||
2841 | ata_dev_printk(dev, KERN_INFO, "detaching (SCSI %s)\n", | ||
2842 | sdev->sdev_gendev.bus_id); | ||
2843 | |||
2844 | scsi_remove_device(sdev); | ||
2845 | scsi_device_put(sdev); | ||
2846 | } | ||
2847 | } | ||
2848 | |||
2849 | /** | ||
2850 | * ata_scsi_hotplug - SCSI part of hotplug | ||
2851 | * @data: Pointer to ATA port to perform SCSI hotplug on | ||
2852 | * | ||
2853 | * Perform SCSI part of hotplug. It's executed from a separate | ||
2854 | * workqueue after EH completes. This is necessary because SCSI | ||
2855 | * hot plugging requires working EH and hot unplugging is | ||
2856 | * synchronized with hot plugging with a mutex. | ||
2857 | * | ||
2858 | * LOCKING: | ||
2859 | * Kernel thread context (may sleep). | ||
2860 | */ | ||
2861 | void ata_scsi_hotplug(void *data) | ||
2862 | { | ||
2863 | struct ata_port *ap = data; | ||
2864 | int i; | ||
2865 | |||
2866 | if (ap->flags & ATA_FLAG_UNLOADING) { | ||
2867 | DPRINTK("ENTER/EXIT - unloading\n"); | ||
2868 | return; | ||
2869 | } | ||
2870 | |||
2871 | DPRINTK("ENTER\n"); | ||
2872 | |||
2873 | /* unplug detached devices */ | ||
2874 | for (i = 0; i < ATA_MAX_DEVICES; i++) { | ||
2875 | struct ata_device *dev = &ap->device[i]; | ||
2876 | unsigned long flags; | ||
2877 | |||
2878 | if (!(dev->flags & ATA_DFLAG_DETACHED)) | ||
2879 | continue; | ||
2880 | |||
2881 | spin_lock_irqsave(&ap->host_set->lock, flags); | ||
2882 | dev->flags &= ~ATA_DFLAG_DETACHED; | ||
2883 | spin_unlock_irqrestore(&ap->host_set->lock, flags); | ||
2884 | |||
2885 | ata_scsi_remove_dev(dev); | ||
2886 | } | ||
2887 | |||
2888 | /* scan for new ones */ | ||
2889 | ata_scsi_scan_host(ap); | ||
2890 | |||
2891 | /* If we scanned while EH was in progress, scan would have | ||
2892 | * failed silently. Requeue if there are enabled but | ||
2893 | * unattached devices. | ||
2894 | */ | ||
2895 | for (i = 0; i < ATA_MAX_DEVICES; i++) { | ||
2896 | struct ata_device *dev = &ap->device[i]; | ||
2897 | if (ata_dev_enabled(dev) && !dev->sdev) { | ||
2898 | queue_delayed_work(ata_aux_wq, &ap->hotplug_task, HZ); | ||
2899 | break; | ||
2900 | } | ||
2901 | } | ||
2902 | |||
2903 | DPRINTK("EXIT\n"); | ||
2904 | } | ||
diff --git a/drivers/scsi/libata.h b/drivers/scsi/libata.h index e38759fdc183..0586b0cd73fd 100644 --- a/drivers/scsi/libata.h +++ b/drivers/scsi/libata.h | |||
@@ -76,6 +76,7 @@ extern struct scsi_transport_template ata_scsi_transport_template; | |||
76 | 76 | ||
77 | extern void ata_scsi_scan_host(struct ata_port *ap); | 77 | extern void ata_scsi_scan_host(struct ata_port *ap); |
78 | extern int ata_scsi_offline_dev(struct ata_device *dev); | 78 | extern int ata_scsi_offline_dev(struct ata_device *dev); |
79 | extern void ata_scsi_hotplug(void *data); | ||
79 | extern unsigned int ata_scsiop_inq_std(struct ata_scsi_args *args, u8 *rbuf, | 80 | extern unsigned int ata_scsiop_inq_std(struct ata_scsi_args *args, u8 *rbuf, |
80 | unsigned int buflen); | 81 | unsigned int buflen); |
81 | 82 | ||