This patch adds capability for SCSI layer to generate uevent for SCSI sense code. The feature is gated by CONFIG_SCSI_SENSE_UEVENT. We can configure which sense keys generate uevent for each device through sysfs entry sense_event_filter. For example, the following enables uevent for MEDIUM_ERROR (0x03) and HARDWARE_ERROR (0x04) on scsi drive sdc: echo 0x000c > /sys/block/sdc/device/sense_event_filter Here is an example output captured by udevadm: KERNEL[1214.945358] change /devices/pci0000:00/XXXXXXX ACTION=change DEVPATH=/devices/pci0000:00/0000:00:01.0/0000:01:00.0/host6/XXXXXXX DEVTYPE=scsi_device DRIVER=sd LBA=0 MODALIAS=scsi:t-0x00 SDEV_UA=SCSI_SENSE SENSE_CODE=3/11/14 SEQNUM=4536 SIZE=4096 SUBSYSTEM=scsi Signed-off-by: Song Liu <songliubraving@xxxxxx> --- drivers/scsi/Kconfig | 14 +++++++++++ drivers/scsi/scsi_error.c | 26 ++++++++++++++++++++ drivers/scsi/scsi_lib.c | 27 +++++++++++++++++++-- drivers/scsi/scsi_sysfs.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++ include/scsi/scsi_device.h | 26 +++++++++++++++++++- 5 files changed, 150 insertions(+), 3 deletions(-) diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index 3c52867..4f7f211 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -237,6 +237,20 @@ config SCSI_LOGGING there should be no noticeable performance impact as long as you have logging turned off. +config SCSI_SENSE_UEVENT + bool "SCSI sense code logging" + depends on SCSI + default n + ---help--- + This turns on uevent for SCSI sense code. + + You can configure which sense keys generate uevent for each device + through sysfs entry sense_event_filter. For example, the following + enables uevent for MEDIUM_ERROR (0x03) and HARDWARE_ERROR (0x04) + on scsi drive sdc: + + echo 0x000c > /sys/block/sdc/device/sense_event_filter + config SCSI_SCAN_ASYNC bool "Asynchronous SCSI scanning" depends on SCSI diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index d70c67c..eda150e 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -426,6 +426,31 @@ static void scsi_report_sense(struct scsi_device *sdev, } } +/* + * generate uevent when receiving sense code from device + */ +static void scsi_send_sense_uevent(struct scsi_device *sdev, + struct scsi_cmnd *scmd, + struct scsi_sense_hdr *sshdr) +{ +#ifdef CONFIG_SCSI_SENSE_UEVENT + struct scsi_event *evt; + + if (!test_bit(sshdr->sense_key & 0xf, + &sdev->sense_event_filter)) + return; + evt = sdev_evt_alloc(SDEV_EVT_SCSI_SENSE, GFP_ATOMIC); + if (!evt) + return; + + evt->sense_evt_data.lba = scsi_get_lba(scmd); + evt->sense_evt_data.size = blk_rq_bytes(scmd->request); + memcpy(&evt->sense_evt_data.sshdr, sshdr, + sizeof(struct scsi_sense_hdr)); + sdev_evt_send(sdev, evt); +#endif +} + /** * scsi_check_sense - Examine scsi cmd sense * @scmd: Cmd to have sense checked. @@ -446,6 +471,7 @@ int scsi_check_sense(struct scsi_cmnd *scmd) return FAILED; /* no valid sense data */ scsi_report_sense(sdev, &sshdr); + scsi_send_sense_uevent(sdev, scmd, &sshdr); if (scsi_sense_is_deferred(&sshdr)) return NEEDS_RETRY; diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 95f963b..1095f27 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -2656,8 +2656,9 @@ EXPORT_SYMBOL(scsi_device_set_state); */ static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt) { - int idx = 0; - char *envp[3]; + int idx = 0, i; + char *envp[5]; /* SDEV_EVT_SCSI_SENSE needs most entries (4) */ + int free_envp = -1; switch (evt->evt_type) { case SDEV_EVT_MEDIA_CHANGE: @@ -2682,6 +2683,23 @@ static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt) case SDEV_EVT_ALUA_STATE_CHANGE_REPORTED: envp[idx++] = "SDEV_UA=ASYMMETRIC_ACCESS_STATE_CHANGED"; break; +#ifdef CONFIG_SCSI_SENSE_UEVENT + case SDEV_EVT_SCSI_SENSE: + envp[idx++] = "SDEV_UA=SCSI_SENSE"; + for (i = idx; i < idx + 3; ++i) { + envp[i] = kzalloc(32, GFP_ATOMIC); + if (!envp[i]) + break; + free_envp = i; + } + snprintf(envp[idx++], 32, "LBA=%lu", evt->sense_evt_data.lba); + snprintf(envp[idx++], 32, "SIZE=%d", evt->sense_evt_data.size); + snprintf(envp[idx++], 32, "SENSE_CODE=%1x/%02x/%02x", + evt->sense_evt_data.sshdr.sense_key, + evt->sense_evt_data.sshdr.asc, + evt->sense_evt_data.sshdr.ascq); + break; +#endif default: /* do nothing */ break; @@ -2690,6 +2708,10 @@ static void scsi_evt_emit(struct scsi_device *sdev, struct scsi_event *evt) envp[idx++] = NULL; kobject_uevent_env(&sdev->sdev_gendev.kobj, KOBJ_CHANGE, envp); + + /* no need to free envp[0], so start with i = 1 */ + for (i = 1 ; i < free_envp; ++i) + kfree(envp[i]); } /** @@ -2786,6 +2808,7 @@ struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type, case SDEV_EVT_MODE_PARAMETER_CHANGE_REPORTED: case SDEV_EVT_LUN_CHANGE_REPORTED: case SDEV_EVT_ALUA_STATE_CHANGE_REPORTED: + case SDEV_EVT_SCSI_SENSE: default: /* do nothing */ break; diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 82dfe07..cfc7380 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -1072,6 +1072,63 @@ static DEVICE_ATTR(queue_ramp_up_period, S_IRUGO | S_IWUSR, sdev_show_queue_ramp_up_period, sdev_store_queue_ramp_up_period); +#ifdef CONFIG_SCSI_SENSE_UEVENT + +/* + * SCSI sense key could be 0x00 - 0x08, 0x0a, 0x0b, 0x0d, 0x0e, so the + * mask is 0x6dff. + */ +#define SCSI_SENSE_EVENT_FILTER_MASK 0x6dff + +static ssize_t +sdev_show_sense_event_filter(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev; + + sdev = to_scsi_device(dev); + return snprintf(buf, 20, "0x%04lx\n", + (sdev->sense_event_filter & + SCSI_SENSE_EVENT_FILTER_MASK)); +} + +static ssize_t +sdev_store_sense_event_filter(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + unsigned long filter; + int i; + + if (buf[0] == '0' && buf[1] == 'x') { + if (kstrtoul(buf + 2, 16, &filter)) + return -EINVAL; + } else + if (kstrtoul(buf, 10, &filter)) + return -EINVAL; + + /* + * Accurate mask for all sense keys is 0x6dff. However, we allow + * user to enable event for all sense keys by echoing 0xffff + */ + if ((filter & 0xffff) != filter) + return -EINVAL; + + for (i = 0; i < 15; i++) + if (filter & SCSI_SENSE_EVENT_FILTER_MASK & (1 << i)) + set_bit(i, &sdev->sense_event_filter); + else + clear_bit(i, &sdev->sense_event_filter); + return count; +} + +static DEVICE_ATTR(sense_event_filter, 0644, + sdev_show_sense_event_filter, + sdev_store_sense_event_filter); +#endif + static umode_t scsi_sdev_attr_is_visible(struct kobject *kobj, struct attribute *attr, int i) { @@ -1142,6 +1199,9 @@ static struct attribute *scsi_sdev_attrs[] = { &dev_attr_preferred_path.attr, #endif &dev_attr_queue_ramp_up_period.attr, +#ifdef CONFIG_SCSI_SENSE_UEVENT + &dev_attr_sense_event_filter.attr, +#endif REF_EVT(media_change), REF_EVT(inquiry_change_reported), REF_EVT(capacity_change_reported), diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h index 05641ae..d160bd7 100644 --- a/include/scsi/scsi_device.h +++ b/include/scsi/scsi_device.h @@ -64,13 +64,23 @@ enum scsi_device_event { SDEV_EVT_MODE_PARAMETER_CHANGE_REPORTED, /* 2A 01 UA reported */ SDEV_EVT_LUN_CHANGE_REPORTED, /* 3F 0E UA reported */ SDEV_EVT_ALUA_STATE_CHANGE_REPORTED, /* 2A 06 UA reported */ + SDEV_EVT_SCSI_SENSE, SDEV_EVT_FIRST = SDEV_EVT_MEDIA_CHANGE, - SDEV_EVT_LAST = SDEV_EVT_ALUA_STATE_CHANGE_REPORTED, + SDEV_EVT_LAST = SDEV_EVT_SCSI_SENSE, SDEV_EVT_MAXBITS = SDEV_EVT_LAST + 1 }; +#ifdef CONFIG_SCSI_SENSE_UEVENT +/* data for for SDEV_EVT_SCSI_SENSE */ +struct scsi_sense_uevent_data { + sector_t lba; /* LBA from the scsi command */ + int size; /* size of the request */ + struct scsi_sense_hdr sshdr; +}; +#endif + struct scsi_event { enum scsi_device_event evt_type; struct list_head node; @@ -78,6 +88,11 @@ struct scsi_event { /* put union of data structures, for non-simple event types, * here */ + union { +#ifdef CONFIG_SCSI_SENSE_UEVENT + struct scsi_sense_uevent_data sense_evt_data; +#endif + }; }; struct scsi_device { @@ -197,6 +212,15 @@ struct scsi_device { atomic_t iodone_cnt; atomic_t ioerr_cnt; +#ifdef CONFIG_SCSI_SENSE_UEVENT + /* + * filter of sense code uevent + * setting bit X (0x00 - 0x0e) of sense_event_filter enables + * uevent for sense key X + */ + unsigned long sense_event_filter; +#endif + struct device sdev_gendev, sdev_dev; -- 2.9.3