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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @sdev: scsi device to quiesce.
|
||||
|
|
|
@ -236,6 +236,7 @@ static struct scsi_device *scsi_alloc_sdev(struct scsi_target *starget,
|
|||
struct scsi_device *sdev;
|
||||
int display_failure_msg = 1, ret;
|
||||
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,
|
||||
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->cmd_list);
|
||||
INIT_LIST_HEAD(&sdev->starved_entry);
|
||||
INIT_LIST_HEAD(&sdev->event_list);
|
||||
spin_lock_init(&sdev->list_lock);
|
||||
INIT_WORK(&sdev->event_work, scsi_evt_thread);
|
||||
|
||||
sdev->sdev_gendev.parent = get_device(&starget->dev);
|
||||
sdev->sdev_target = starget;
|
||||
|
|
|
@ -268,6 +268,7 @@ static void scsi_device_dev_release_usercontext(struct work_struct *work)
|
|||
struct scsi_device *sdev;
|
||||
struct device *parent;
|
||||
struct scsi_target *starget;
|
||||
struct list_head *this, *tmp;
|
||||
unsigned long flags;
|
||||
|
||||
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);
|
||||
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) {
|
||||
sdev->request_queue->queuedata = NULL;
|
||||
/* 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);
|
||||
|
||||
#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 */
|
||||
static struct attribute *scsi_sdev_attrs[] = {
|
||||
&dev_attr_device_blocked.attr,
|
||||
|
@ -631,6 +677,7 @@ static struct attribute *scsi_sdev_attrs[] = {
|
|||
&dev_attr_iodone_cnt.attr,
|
||||
&dev_attr_ioerr_cnt.attr,
|
||||
&dev_attr_modalias.attr,
|
||||
REF_EVT(media_change),
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
|
@ -46,6 +46,22 @@ enum scsi_device_state {
|
|||
* 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_Host *host;
|
||||
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 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 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);
|
||||
extern int scsi_device_set_state(struct scsi_device *sdev,
|
||||
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 void scsi_device_resume(struct scsi_device *sdev);
|
||||
extern void scsi_target_quiesce(struct scsi_target *);
|
||||
|
|
Loading…
Reference in a new issue