diff options
author | Jeff Garzik <jeff@garzik.org> | 2007-10-29 17:15:22 -0400 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2007-11-03 22:23:02 -0400 |
commit | a341cd0f6a0fde1f85fec9aa8f81f824ea4a3f92 (patch) | |
tree | e96b2ab04c94cb1a29d972b135dd6b2bdfac0f78 | |
parent | b4f555081fdd27d13e6ff39d455d5aefae9d2c0c (diff) |
SCSI: add asynchronous event notification API
Originally based on a patch by Kristen Carlson Accardi @ Intel.
Copious input from James Bottomley.
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
-rw-r--r-- | drivers/scsi/scsi_lib.c | 136 | ||||
-rw-r--r-- | drivers/scsi/scsi_scan.c | 3 | ||||
-rw-r--r-- | drivers/scsi/scsi_sysfs.c | 47 | ||||
-rw-r--r-- | include/scsi/scsi_device.h | 25 |
4 files changed, 211 insertions, 0 deletions
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 88de771d3569..0e81e4cf8876 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c | |||
@@ -2115,6 +2115,142 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state) | |||
2115 | EXPORT_SYMBOL(scsi_device_set_state); | 2115 | EXPORT_SYMBOL(scsi_device_set_state); |
2116 | 2116 | ||
2117 | /** | 2117 | /** |
2118 | * sdev_evt_emit - emit a single SCSI device uevent | ||
2119 | * @sdev: associated SCSI device | ||
2120 | * @evt: event to emit | ||
2121 | * | ||
2122 | * Send a single uevent (scsi_event) to the associated scsi_device. | ||
2123 | */ | ||
2124 | static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt) | ||
2125 | { | ||
2126 | int idx = 0; | ||
2127 | char *envp[3]; | ||
2128 | |||
2129 | switch (evt->evt_type) { | ||
2130 | case SDEV_EVT_MEDIA_CHANGE: | ||
2131 | envp[idx++] = "SDEV_MEDIA_CHANGE=1"; | ||
2132 | break; | ||
2133 | |||
2134 | default: | ||
2135 | /* do nothing */ | ||
2136 | break; | ||
2137 | } | ||
2138 | |||
2139 | envp[idx++] = NULL; | ||
2140 | |||
2141 | kobject_uevent_env(&sdev->sdev_gendev.kobj, KOBJ_CHANGE, envp); | ||
2142 | } | ||
2143 | |||
2144 | /** | ||
2145 | * sdev_evt_thread - send a uevent for each scsi event | ||
2146 | * @work: work struct for scsi_device | ||
2147 | * | ||
2148 | * Dispatch queued events to their associated scsi_device kobjects | ||
2149 | * as uevents. | ||
2150 | */ | ||
2151 | void scsi_evt_thread(struct work_struct *work) | ||
2152 | { | ||
2153 | struct scsi_device *sdev; | ||
2154 | LIST_HEAD(event_list); | ||
2155 | |||
2156 | sdev = container_of(work, struct scsi_device, event_work); | ||
2157 | |||
2158 | while (1) { | ||
2159 | struct scsi_event *evt; | ||
2160 | struct list_head *this, *tmp; | ||
2161 | unsigned long flags; | ||
2162 | |||
2163 | spin_lock_irqsave(&sdev->list_lock, flags); | ||
2164 | list_splice_init(&sdev->event_list, &event_list); | ||
2165 | spin_unlock_irqrestore(&sdev->list_lock, flags); | ||
2166 | |||
2167 | if (list_empty(&event_list)) | ||
2168 | break; | ||
2169 | |||
2170 | list_for_each_safe(this, tmp, &event_list) { | ||
2171 | evt = list_entry(this, struct scsi_event, node); | ||
2172 | list_del(&evt->node); | ||
2173 | scsi_evt_emit(sdev, evt); | ||
2174 | kfree(evt); | ||
2175 | } | ||
2176 | } | ||
2177 | } | ||
2178 | |||
2179 | /** | ||
2180 | * sdev_evt_send - send asserted event to uevent thread | ||
2181 | * @sdev: scsi_device event occurred on | ||
2182 | * @evt: event to send | ||
2183 | * | ||
2184 | * Assert scsi device event asynchronously. | ||
2185 | */ | ||
2186 | void sdev_evt_send(struct scsi_device *sdev, struct scsi_event *evt) | ||
2187 | { | ||
2188 | unsigned long flags; | ||
2189 | |||
2190 | if (!test_bit(evt->evt_type, sdev->supported_events)) { | ||
2191 | kfree(evt); | ||
2192 | return; | ||
2193 | } | ||
2194 | |||
2195 | spin_lock_irqsave(&sdev->list_lock, flags); | ||
2196 | list_add_tail(&evt->node, &sdev->event_list); | ||
2197 | schedule_work(&sdev->event_work); | ||
2198 | spin_unlock_irqrestore(&sdev->list_lock, flags); | ||
2199 | } | ||
2200 | EXPORT_SYMBOL_GPL(sdev_evt_send); | ||
2201 | |||
2202 | /** | ||
2203 | * sdev_evt_alloc - allocate a new scsi event | ||
2204 | * @evt_type: type of event to allocate | ||
2205 | * @gfpflags: GFP flags for allocation | ||
2206 | * | ||
2207 | * Allocates and returns a new scsi_event. | ||
2208 | */ | ||
2209 | struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type, | ||
2210 | gfp_t gfpflags) | ||
2211 | { | ||
2212 | struct scsi_event *evt = kzalloc(sizeof(struct scsi_event), gfpflags); | ||
2213 | if (!evt) | ||
2214 | return NULL; | ||
2215 | |||
2216 | evt->evt_type = evt_type; | ||
2217 | INIT_LIST_HEAD(&evt->node); | ||
2218 | |||
2219 | /* evt_type-specific initialization, if any */ | ||
2220 | switch (evt_type) { | ||
2221 | case SDEV_EVT_MEDIA_CHANGE: | ||
2222 | default: | ||
2223 | /* do nothing */ | ||
2224 | break; | ||
2225 | } | ||
2226 | |||
2227 | return evt; | ||
2228 | } | ||
2229 | EXPORT_SYMBOL_GPL(sdev_evt_alloc); | ||
2230 | |||
2231 | /** | ||
2232 | * sdev_evt_send_simple - send asserted event to uevent thread | ||
2233 | * @sdev: scsi_device event occurred on | ||
2234 | * @evt_type: type of event to send | ||
2235 | * @gfpflags: GFP flags for allocation | ||
2236 | * | ||
2237 | * Assert scsi device event asynchronously, given an event type. | ||
2238 | */ | ||
2239 | void sdev_evt_send_simple(struct scsi_device *sdev, | ||
2240 | enum scsi_device_event evt_type, gfp_t gfpflags) | ||
2241 | { | ||
2242 | struct scsi_event *evt = sdev_evt_alloc(evt_type, gfpflags); | ||
2243 | if (!evt) { | ||
2244 | sdev_printk(KERN_ERR, sdev, "event %d eaten due to OOM\n", | ||
2245 | evt_type); | ||
2246 | return; | ||
2247 | } | ||
2248 | |||
2249 | sdev_evt_send(sdev, evt); | ||
2250 | } | ||
2251 | EXPORT_SYMBOL_GPL(sdev_evt_send_simple); | ||
2252 | |||
2253 | /** | ||
2118 | * scsi_device_quiesce - Block user issued commands. | 2254 | * scsi_device_quiesce - Block user issued commands. |
2119 | * @sdev: scsi device to quiesce. | 2255 | * @sdev: scsi device to quiesce. |
2120 | * | 2256 | * |
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index b53c5f67e372..40ea71cd2ca6 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c | |||
@@ -236,6 +236,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, | |||
236 | struct scsi_device *sdev; | 236 | struct scsi_device *sdev; |
237 | int display_failure_msg = 1, ret; | 237 | int display_failure_msg = 1, ret; |
238 | struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); | 238 | struct Scsi_Host *shost = dev_to_shost(starget->dev.parent); |
239 | extern void scsi_evt_thread(struct work_struct *work); | ||
239 | 240 | ||
240 | sdev = kzalloc(sizeof(*sdev) + shost->transportt->device_size, | 241 | sdev = kzalloc(sizeof(*sdev) + shost->transportt->device_size, |
241 | GFP_ATOMIC); | 242 | GFP_ATOMIC); |
@@ -254,7 +255,9 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget, | |||
254 | INIT_LIST_HEAD(&sdev->same_target_siblings); | 255 | INIT_LIST_HEAD(&sdev->same_target_siblings); |
255 | INIT_LIST_HEAD(&sdev->cmd_list); | 256 | INIT_LIST_HEAD(&sdev->cmd_list); |
256 | INIT_LIST_HEAD(&sdev->starved_entry); | 257 | INIT_LIST_HEAD(&sdev->starved_entry); |
258 | INIT_LIST_HEAD(&sdev->event_list); | ||
257 | spin_lock_init(&sdev->list_lock); | 259 | spin_lock_init(&sdev->list_lock); |
260 | INIT_WORK(&sdev->event_work, scsi_evt_thread); | ||
258 | 261 | ||
259 | sdev->sdev_gendev.parent = get_device(&starget->dev); | 262 | sdev->sdev_gendev.parent = get_device(&starget->dev); |
260 | sdev->sdev_target = starget; | 263 | sdev->sdev_target = starget; |
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index d531ceeb0d8c..f374fdcb6815 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c | |||
@@ -268,6 +268,7 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work) | |||
268 | struct scsi_device *sdev; | 268 | struct scsi_device *sdev; |
269 | struct device *parent; | 269 | struct device *parent; |
270 | struct scsi_target *starget; | 270 | struct scsi_target *starget; |
271 | struct list_head *this, *tmp; | ||
271 | unsigned long flags; | 272 | unsigned long flags; |
272 | 273 | ||
273 | sdev = container_of(work, struct scsi_device, ew.work); | 274 | sdev = container_of(work, struct scsi_device, ew.work); |
@@ -282,6 +283,16 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work) | |||
282 | list_del(&sdev->starved_entry); | 283 | list_del(&sdev->starved_entry); |
283 | spin_unlock_irqrestore(sdev->host->host_lock, flags); | 284 | spin_unlock_irqrestore(sdev->host->host_lock, flags); |
284 | 285 | ||
286 | cancel_work_sync(&sdev->event_work); | ||
287 | |||
288 | list_for_each_safe(this, tmp, &sdev->event_list) { | ||
289 | struct scsi_event *evt; | ||
290 | |||
291 | evt = list_entry(this, struct scsi_event, node); | ||
292 | list_del(&evt->node); | ||
293 | kfree(evt); | ||
294 | } | ||
295 | |||
285 | if (sdev->request_queue) { | 296 | if (sdev->request_queue) { |
286 | sdev->request_queue->queuedata = NULL; | 297 | sdev->request_queue->queuedata = NULL; |
287 | /* user context needed to free queue */ | 298 | /* user context needed to free queue */ |
@@ -614,6 +625,41 @@ sdev_show_modalias(struct device *dev, struct device_attribute *attr, char *buf) | |||
614 | } | 625 | } |
615 | static DEVICE_ATTR(modalias, S_IRUGO, sdev_show_modalias, NULL); | 626 | static DEVICE_ATTR(modalias, S_IRUGO, sdev_show_modalias, NULL); |
616 | 627 | ||
628 | #define DECLARE_EVT_SHOW(name, Cap_name) \ | ||
629 | static ssize_t \ | ||
630 | sdev_show_evt_##name(struct device *dev, struct device_attribute *attr, \ | ||
631 | char *buf) \ | ||
632 | { \ | ||
633 | struct scsi_device *sdev = to_scsi_device(dev); \ | ||
634 | int val = test_bit(SDEV_EVT_##Cap_name, sdev->supported_events);\ | ||
635 | return snprintf(buf, 20, "%d\n", val); \ | ||
636 | } | ||
637 | |||
638 | #define DECLARE_EVT_STORE(name, Cap_name) \ | ||
639 | static ssize_t \ | ||
640 | sdev_store_evt_##name(struct device *dev, struct device_attribute *attr, \ | ||
641 | const char *buf, size_t count) \ | ||
642 | { \ | ||
643 | struct scsi_device *sdev = to_scsi_device(dev); \ | ||
644 | int val = simple_strtoul(buf, NULL, 0); \ | ||
645 | if (val == 0) \ | ||
646 | clear_bit(SDEV_EVT_##Cap_name, sdev->supported_events); \ | ||
647 | else if (val == 1) \ | ||
648 | set_bit(SDEV_EVT_##Cap_name, sdev->supported_events); \ | ||
649 | else \ | ||
650 | return -EINVAL; \ | ||
651 | return count; \ | ||
652 | } | ||
653 | |||
654 | #define DECLARE_EVT(name, Cap_name) \ | ||
655 | DECLARE_EVT_SHOW(name, Cap_name) \ | ||
656 | DECLARE_EVT_STORE(name, Cap_name) \ | ||
657 | static DEVICE_ATTR(evt_##name, S_IRUGO, sdev_show_evt_##name, \ | ||
658 | sdev_store_evt_##name); | ||
659 | #define REF_EVT(name) &dev_attr_evt_##name.attr | ||
660 | |||
661 | DECLARE_EVT(media_change, MEDIA_CHANGE) | ||
662 | |||
617 | /* Default template for device attributes. May NOT be modified */ | 663 | /* Default template for device attributes. May NOT be modified */ |
618 | static struct attribute *scsi_sdev_attrs[] = { | 664 | static struct attribute *scsi_sdev_attrs[] = { |
619 | &dev_attr_device_blocked.attr, | 665 | &dev_attr_device_blocked.attr, |
@@ -631,6 +677,7 @@ static struct attribute *scsi_sdev_attrs[] = { | |||
631 | &dev_attr_iodone_cnt.attr, | 677 | &dev_attr_iodone_cnt.attr, |
632 | &dev_attr_ioerr_cnt.attr, | 678 | &dev_attr_ioerr_cnt.attr, |
633 | &dev_attr_modalias.attr, | 679 | &dev_attr_modalias.attr, |
680 | REF_EVT(media_change), | ||
634 | NULL | 681 | NULL |
635 | }; | 682 | }; |
636 | 683 | ||
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index d5057bc338ff..66e9058357e0 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h | |||
@@ -46,6 +46,22 @@ enum scsi_device_state { | |||
46 | * to the scsi lld. */ | 46 | * to the scsi lld. */ |
47 | }; | 47 | }; |
48 | 48 | ||
49 | enum scsi_device_event { | ||
50 | SDEV_EVT_MEDIA_CHANGE = 1, /* media has changed */ | ||
51 | |||
52 | SDEV_EVT_LAST = SDEV_EVT_MEDIA_CHANGE, | ||
53 | SDEV_EVT_MAXBITS = SDEV_EVT_LAST + 1 | ||
54 | }; | ||
55 | |||
56 | struct scsi_event { | ||
57 | enum scsi_device_event evt_type; | ||
58 | struct list_head node; | ||
59 | |||
60 | /* put union of data structures, for non-simple event types, | ||
61 | * here | ||
62 | */ | ||
63 | }; | ||
64 | |||
49 | struct scsi_device { | 65 | struct scsi_device { |
50 | struct Scsi_Host *host; | 66 | struct Scsi_Host *host; |
51 | struct request_queue *request_queue; | 67 | struct request_queue *request_queue; |
@@ -127,6 +143,10 @@ struct scsi_device { | |||
127 | unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */ | 143 | unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */ |
128 | unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ | 144 | unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */ |
129 | 145 | ||
146 | DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */ | ||
147 | struct list_head event_list; /* asserted events */ | ||
148 | struct work_struct event_work; | ||
149 | |||
130 | unsigned int device_blocked; /* Device returned QUEUE_FULL. */ | 150 | unsigned int device_blocked; /* Device returned QUEUE_FULL. */ |
131 | 151 | ||
132 | unsigned int max_device_blocked; /* what device_blocked counts down from */ | 152 | unsigned int max_device_blocked; /* what device_blocked counts down from */ |
@@ -275,6 +295,11 @@ extern int scsi_test_unit_ready(struct scsi_device *sdev, int timeout, | |||
275 | int retries); | 295 | int retries); |
276 | extern int scsi_device_set_state(struct scsi_device *sdev, | 296 | extern int scsi_device_set_state(struct scsi_device *sdev, |
277 | enum scsi_device_state state); | 297 | enum scsi_device_state state); |
298 | extern struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type, | ||
299 | gfp_t gfpflags); | ||
300 | extern void sdev_evt_send(struct scsi_device *sdev, struct scsi_event *evt); | ||
301 | extern void sdev_evt_send_simple(struct scsi_device *sdev, | ||
302 | enum scsi_device_event evt_type, gfp_t gfpflags); | ||
278 | extern int scsi_device_quiesce(struct scsi_device *sdev); | 303 | extern int scsi_device_quiesce(struct scsi_device *sdev); |
279 | extern void scsi_device_resume(struct scsi_device *sdev); | 304 | extern void scsi_device_resume(struct scsi_device *sdev); |
280 | extern void scsi_target_quiesce(struct scsi_target *); | 305 | extern void scsi_target_quiesce(struct scsi_target *); |