Skip to content

Commit

Permalink
SCSI: add asynchronous event notification API
Browse files Browse the repository at this point in the history
Originally based on a patch by Kristen Carlson Accardi @ Intel.
Copious input from James Bottomley.

Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
  • Loading branch information
Jeff Garzik committed Nov 4, 2007
1 parent b4f5550 commit a341cd0
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
136 changes: 136 additions & 0 deletions drivers/scsi/scsi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions drivers/scsi/scsi_scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
47 changes: 47 additions & 0 deletions drivers/scsi/scsi_sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 */
Expand Down Expand Up @@ -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,
Expand All @@ -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
};

Expand Down
25 changes: 25 additions & 0 deletions include/scsi/scsi_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 *);
Expand Down

0 comments on commit a341cd0

Please sign in to comment.