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>
This commit is contained in:
parent
b4f555081f
commit
a341cd0f6a
4 changed files with 211 additions and 0 deletions
|
@ -2114,6 +2114,142 @@ scsi_device_set_state(struct scsi_device *sdev, enum scsi_device_state state)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(scsi_device_set_state);
|
EXPORT_SYMBOL(scsi_device_set_state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdev_evt_emit - emit a single SCSI device uevent
|
||||||
|
* @sdev: associated SCSI device
|
||||||
|
* @evt: event to emit
|
||||||
|
*
|
||||||
|
* Send a single uevent (scsi_event) to the associated scsi_device.
|
||||||
|
*/
|
||||||
|
static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt)
|
||||||
|
{
|
||||||
|
int idx = 0;
|
||||||
|
char *envp[3];
|
||||||
|
|
||||||
|
switch (evt->evt_type) {
|
||||||
|
case SDEV_EVT_MEDIA_CHANGE:
|
||||||
|
envp[idx++] = "SDEV_MEDIA_CHANGE=1";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* do nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
envp[idx++] = NULL;
|
||||||
|
|
||||||
|
kobject_uevent_env(&sdev->sdev_gendev.kobj, KOBJ_CHANGE, envp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdev_evt_thread - send a uevent for each scsi event
|
||||||
|
* @work: work struct for scsi_device
|
||||||
|
*
|
||||||
|
* Dispatch queued events to their associated scsi_device kobjects
|
||||||
|
* as uevents.
|
||||||
|
*/
|
||||||
|
void scsi_evt_thread(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct scsi_device *sdev;
|
||||||
|
LIST_HEAD(event_list);
|
||||||
|
|
||||||
|
sdev = container_of(work, struct scsi_device, event_work);
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
struct scsi_event *evt;
|
||||||
|
struct list_head *this, *tmp;
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
spin_lock_irqsave(&sdev->list_lock, flags);
|
||||||
|
list_splice_init(&sdev->event_list, &event_list);
|
||||||
|
spin_unlock_irqrestore(&sdev->list_lock, flags);
|
||||||
|
|
||||||
|
if (list_empty(&event_list))
|
||||||
|
break;
|
||||||
|
|
||||||
|
list_for_each_safe(this, tmp, &event_list) {
|
||||||
|
evt = list_entry(this, struct scsi_event, node);
|
||||||
|
list_del(&evt->node);
|
||||||
|
scsi_evt_emit(sdev, evt);
|
||||||
|
kfree(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdev_evt_send - send asserted event to uevent thread
|
||||||
|
* @sdev: scsi_device event occurred on
|
||||||
|
* @evt: event to send
|
||||||
|
*
|
||||||
|
* Assert scsi device event asynchronously.
|
||||||
|
*/
|
||||||
|
void sdev_evt_send(struct scsi_device *sdev, struct scsi_event *evt)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
if (!test_bit(evt->evt_type, sdev->supported_events)) {
|
||||||
|
kfree(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_irqsave(&sdev->list_lock, flags);
|
||||||
|
list_add_tail(&evt->node, &sdev->event_list);
|
||||||
|
schedule_work(&sdev->event_work);
|
||||||
|
spin_unlock_irqrestore(&sdev->list_lock, flags);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(sdev_evt_send);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdev_evt_alloc - allocate a new scsi event
|
||||||
|
* @evt_type: type of event to allocate
|
||||||
|
* @gfpflags: GFP flags for allocation
|
||||||
|
*
|
||||||
|
* Allocates and returns a new scsi_event.
|
||||||
|
*/
|
||||||
|
struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type,
|
||||||
|
gfp_t gfpflags)
|
||||||
|
{
|
||||||
|
struct scsi_event *evt = kzalloc(sizeof(struct scsi_event), gfpflags);
|
||||||
|
if (!evt)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
evt->evt_type = evt_type;
|
||||||
|
INIT_LIST_HEAD(&evt->node);
|
||||||
|
|
||||||
|
/* evt_type-specific initialization, if any */
|
||||||
|
switch (evt_type) {
|
||||||
|
case SDEV_EVT_MEDIA_CHANGE:
|
||||||
|
default:
|
||||||
|
/* do nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(sdev_evt_alloc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sdev_evt_send_simple - send asserted event to uevent thread
|
||||||
|
* @sdev: scsi_device event occurred on
|
||||||
|
* @evt_type: type of event to send
|
||||||
|
* @gfpflags: GFP flags for allocation
|
||||||
|
*
|
||||||
|
* Assert scsi device event asynchronously, given an event type.
|
||||||
|
*/
|
||||||
|
void sdev_evt_send_simple(struct scsi_device *sdev,
|
||||||
|
enum scsi_device_event evt_type, gfp_t gfpflags)
|
||||||
|
{
|
||||||
|
struct scsi_event *evt = sdev_evt_alloc(evt_type, gfpflags);
|
||||||
|
if (!evt) {
|
||||||
|
sdev_printk(KERN_ERR, sdev, "event %d eaten due to OOM\n",
|
||||||
|
evt_type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sdev_evt_send(sdev, evt);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(sdev_evt_send_simple);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* scsi_device_quiesce - Block user issued commands.
|
* scsi_device_quiesce - Block user issued commands.
|
||||||
* @sdev: scsi device to quiesce.
|
* @sdev: scsi device to quiesce.
|
||||||
|
|
|
@ -236,6 +236,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
|
||||||
struct scsi_device *sdev;
|
struct scsi_device *sdev;
|
||||||
int display_failure_msg = 1, ret;
|
int display_failure_msg = 1, ret;
|
||||||
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
|
struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
|
||||||
|
extern void scsi_evt_thread(struct work_struct *work);
|
||||||
|
|
||||||
sdev = kzalloc(sizeof(*sdev) + shost->transportt->device_size,
|
sdev = kzalloc(sizeof(*sdev) + shost->transportt->device_size,
|
||||||
GFP_ATOMIC);
|
GFP_ATOMIC);
|
||||||
|
@ -254,7 +255,9 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
|
||||||
INIT_LIST_HEAD(&sdev->same_target_siblings);
|
INIT_LIST_HEAD(&sdev->same_target_siblings);
|
||||||
INIT_LIST_HEAD(&sdev->cmd_list);
|
INIT_LIST_HEAD(&sdev->cmd_list);
|
||||||
INIT_LIST_HEAD(&sdev->starved_entry);
|
INIT_LIST_HEAD(&sdev->starved_entry);
|
||||||
|
INIT_LIST_HEAD(&sdev->event_list);
|
||||||
spin_lock_init(&sdev->list_lock);
|
spin_lock_init(&sdev->list_lock);
|
||||||
|
INIT_WORK(&sdev->event_work, scsi_evt_thread);
|
||||||
|
|
||||||
sdev->sdev_gendev.parent = get_device(&starget->dev);
|
sdev->sdev_gendev.parent = get_device(&starget->dev);
|
||||||
sdev->sdev_target = starget;
|
sdev->sdev_target = starget;
|
||||||
|
|
|
@ -268,6 +268,7 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work)
|
||||||
struct scsi_device *sdev;
|
struct scsi_device *sdev;
|
||||||
struct device *parent;
|
struct device *parent;
|
||||||
struct scsi_target *starget;
|
struct scsi_target *starget;
|
||||||
|
struct list_head *this, *tmp;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
sdev = container_of(work, struct scsi_device, ew.work);
|
sdev = container_of(work, struct scsi_device, ew.work);
|
||||||
|
@ -282,6 +283,16 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work)
|
||||||
list_del(&sdev->starved_entry);
|
list_del(&sdev->starved_entry);
|
||||||
spin_unlock_irqrestore(sdev->host->host_lock, flags);
|
spin_unlock_irqrestore(sdev->host->host_lock, flags);
|
||||||
|
|
||||||
|
cancel_work_sync(&sdev->event_work);
|
||||||
|
|
||||||
|
list_for_each_safe(this, tmp, &sdev->event_list) {
|
||||||
|
struct scsi_event *evt;
|
||||||
|
|
||||||
|
evt = list_entry(this, struct scsi_event, node);
|
||||||
|
list_del(&evt->node);
|
||||||
|
kfree(evt);
|
||||||
|
}
|
||||||
|
|
||||||
if (sdev->request_queue) {
|
if (sdev->request_queue) {
|
||||||
sdev->request_queue->queuedata = NULL;
|
sdev->request_queue->queuedata = NULL;
|
||||||
/* user context needed to free queue */
|
/* user context needed to free queue */
|
||||||
|
@ -614,6 +625,41 @@ sdev_show_modalias(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
}
|
}
|
||||||
static DEVICE_ATTR(modalias, S_IRUGO, sdev_show_modalias, NULL);
|
static DEVICE_ATTR(modalias, S_IRUGO, sdev_show_modalias, NULL);
|
||||||
|
|
||||||
|
#define DECLARE_EVT_SHOW(name, Cap_name) \
|
||||||
|
static ssize_t \
|
||||||
|
sdev_show_evt_##name(struct device *dev, struct device_attribute *attr, \
|
||||||
|
char *buf) \
|
||||||
|
{ \
|
||||||
|
struct scsi_device *sdev = to_scsi_device(dev); \
|
||||||
|
int val = test_bit(SDEV_EVT_##Cap_name, sdev->supported_events);\
|
||||||
|
return snprintf(buf, 20, "%d\n", val); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DECLARE_EVT_STORE(name, Cap_name) \
|
||||||
|
static ssize_t \
|
||||||
|
sdev_store_evt_##name(struct device *dev, struct device_attribute *attr, \
|
||||||
|
const char *buf, size_t count) \
|
||||||
|
{ \
|
||||||
|
struct scsi_device *sdev = to_scsi_device(dev); \
|
||||||
|
int val = simple_strtoul(buf, NULL, 0); \
|
||||||
|
if (val == 0) \
|
||||||
|
clear_bit(SDEV_EVT_##Cap_name, sdev->supported_events); \
|
||||||
|
else if (val == 1) \
|
||||||
|
set_bit(SDEV_EVT_##Cap_name, sdev->supported_events); \
|
||||||
|
else \
|
||||||
|
return -EINVAL; \
|
||||||
|
return count; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DECLARE_EVT(name, Cap_name) \
|
||||||
|
DECLARE_EVT_SHOW(name, Cap_name) \
|
||||||
|
DECLARE_EVT_STORE(name, Cap_name) \
|
||||||
|
static DEVICE_ATTR(evt_##name, S_IRUGO, sdev_show_evt_##name, \
|
||||||
|
sdev_store_evt_##name);
|
||||||
|
#define REF_EVT(name) &dev_attr_evt_##name.attr
|
||||||
|
|
||||||
|
DECLARE_EVT(media_change, MEDIA_CHANGE)
|
||||||
|
|
||||||
/* Default template for device attributes. May NOT be modified */
|
/* Default template for device attributes. May NOT be modified */
|
||||||
static struct attribute *scsi_sdev_attrs[] = {
|
static struct attribute *scsi_sdev_attrs[] = {
|
||||||
&dev_attr_device_blocked.attr,
|
&dev_attr_device_blocked.attr,
|
||||||
|
@ -631,6 +677,7 @@ static struct attribute *scsi_sdev_attrs[] = {
|
||||||
&dev_attr_iodone_cnt.attr,
|
&dev_attr_iodone_cnt.attr,
|
||||||
&dev_attr_ioerr_cnt.attr,
|
&dev_attr_ioerr_cnt.attr,
|
||||||
&dev_attr_modalias.attr,
|
&dev_attr_modalias.attr,
|
||||||
|
REF_EVT(media_change),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,22 @@ enum scsi_device_state {
|
||||||
* to the scsi lld. */
|
* to the scsi lld. */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum scsi_device_event {
|
||||||
|
SDEV_EVT_MEDIA_CHANGE = 1, /* media has changed */
|
||||||
|
|
||||||
|
SDEV_EVT_LAST = SDEV_EVT_MEDIA_CHANGE,
|
||||||
|
SDEV_EVT_MAXBITS = SDEV_EVT_LAST + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scsi_event {
|
||||||
|
enum scsi_device_event evt_type;
|
||||||
|
struct list_head node;
|
||||||
|
|
||||||
|
/* put union of data structures, for non-simple event types,
|
||||||
|
* here
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
struct scsi_device {
|
struct scsi_device {
|
||||||
struct Scsi_Host *host;
|
struct Scsi_Host *host;
|
||||||
struct request_queue *request_queue;
|
struct request_queue *request_queue;
|
||||||
|
@ -127,6 +143,10 @@ struct scsi_device {
|
||||||
unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */
|
unsigned guess_capacity:1; /* READ_CAPACITY might be too high by 1 */
|
||||||
unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */
|
unsigned retry_hwerror:1; /* Retry HARDWARE_ERROR */
|
||||||
|
|
||||||
|
DECLARE_BITMAP(supported_events, SDEV_EVT_MAXBITS); /* supported events */
|
||||||
|
struct list_head event_list; /* asserted events */
|
||||||
|
struct work_struct event_work;
|
||||||
|
|
||||||
unsigned int device_blocked; /* Device returned QUEUE_FULL. */
|
unsigned int device_blocked; /* Device returned QUEUE_FULL. */
|
||||||
|
|
||||||
unsigned int max_device_blocked; /* what device_blocked counts down from */
|
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,
|
||||||
int retries);
|
int retries);
|
||||||
extern int scsi_device_set_state(struct scsi_device *sdev,
|
extern int scsi_device_set_state(struct scsi_device *sdev,
|
||||||
enum scsi_device_state state);
|
enum scsi_device_state state);
|
||||||
|
extern struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type,
|
||||||
|
gfp_t gfpflags);
|
||||||
|
extern void sdev_evt_send(struct scsi_device *sdev, struct scsi_event *evt);
|
||||||
|
extern void sdev_evt_send_simple(struct scsi_device *sdev,
|
||||||
|
enum scsi_device_event evt_type, gfp_t gfpflags);
|
||||||
extern int scsi_device_quiesce(struct scsi_device *sdev);
|
extern int scsi_device_quiesce(struct scsi_device *sdev);
|
||||||
extern void scsi_device_resume(struct scsi_device *sdev);
|
extern void scsi_device_resume(struct scsi_device *sdev);
|
||||||
extern void scsi_target_quiesce(struct scsi_target *);
|
extern void scsi_target_quiesce(struct scsi_target *);
|
||||||
|
|
Loading…
Reference in a new issue