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 *); |
