diff options
Diffstat (limited to 'drivers/scsi/scsi_sysfs.c')
-rw-r--r-- | drivers/scsi/scsi_sysfs.c | 96 |
1 files changed, 89 insertions, 7 deletions
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 21930c9ac9cd..00bc7218a7f8 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -17,6 +17,7 @@ #include <scsi/scsi_device.h> #include <scsi/scsi_host.h> #include <scsi/scsi_tcq.h> +#include <scsi/scsi_dh.h> #include <scsi/scsi_transport.h> #include <scsi/scsi_driver.h> @@ -760,11 +761,15 @@ show_vpd_##_page(struct file *filp, struct kobject *kobj, \ { \ struct device *dev = container_of(kobj, struct device, kobj); \ struct scsi_device *sdev = to_scsi_device(dev); \ + int ret; \ if (!sdev->vpd_##_page) \ return -EINVAL; \ - return memory_read_from_buffer(buf, count, &off, \ - sdev->vpd_##_page, \ + rcu_read_lock(); \ + ret = memory_read_from_buffer(buf, count, &off, \ + rcu_dereference(sdev->vpd_##_page), \ sdev->vpd_##_page##_len); \ + rcu_read_unlock(); \ + return ret; \ } \ static struct bin_attribute dev_attr_vpd_##_page = { \ .attr = {.name = __stringify(vpd_##_page), .mode = S_IRUGO }, \ @@ -901,6 +906,76 @@ static DEVICE_ATTR(queue_depth, S_IRUGO | S_IWUSR, sdev_show_queue_depth, sdev_store_queue_depth); static ssize_t +sdev_show_wwid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + ssize_t count; + + count = scsi_vpd_lun_id(sdev, buf, PAGE_SIZE); + if (count > 0) { + buf[count] = '\n'; + count++; + } + return count; +} +static DEVICE_ATTR(wwid, S_IRUGO, sdev_show_wwid, NULL); + +#ifdef CONFIG_SCSI_DH +static ssize_t +sdev_show_dh_state(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct scsi_device *sdev = to_scsi_device(dev); + + if (!sdev->handler) + return snprintf(buf, 20, "detached\n"); + + return snprintf(buf, 20, "%s\n", sdev->handler->name); +} + +static ssize_t +sdev_store_dh_state(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct scsi_device *sdev = to_scsi_device(dev); + int err = -EINVAL; + + if (sdev->sdev_state == SDEV_CANCEL || + sdev->sdev_state == SDEV_DEL) + return -ENODEV; + + if (!sdev->handler) { + /* + * Attach to a device handler + */ + err = scsi_dh_attach(sdev->request_queue, buf); + } else if (!strncmp(buf, "activate", 8)) { + /* + * Activate a device handler + */ + if (sdev->handler->activate) + err = sdev->handler->activate(sdev, NULL, NULL); + else + err = 0; + } else if (!strncmp(buf, "detach", 6)) { + /* + * Detach from a device handler + */ + sdev_printk(KERN_WARNING, sdev, + "can't detach handler %s.\n", + sdev->handler->name); + err = -EINVAL; + } + + return err < 0 ? err : count; +} + +static DEVICE_ATTR(dh_state, S_IRUGO | S_IWUSR, sdev_show_dh_state, + sdev_store_dh_state); +#endif + +static ssize_t sdev_show_queue_ramp_up_period(struct device *dev, struct device_attribute *attr, char *buf) @@ -969,6 +1044,10 @@ static struct attribute *scsi_sdev_attrs[] = { &dev_attr_modalias.attr, &dev_attr_queue_depth.attr, &dev_attr_queue_type.attr, + &dev_attr_wwid.attr, +#ifdef CONFIG_SCSI_DH + &dev_attr_dh_state.attr, +#endif &dev_attr_queue_ramp_up_period.attr, REF_EVT(media_change), REF_EVT(inquiry_change_reported), @@ -1058,11 +1137,12 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev) } error = scsi_dh_add_device(sdev); - if (error) { + if (error) + /* + * device_handler is optional, so any error can be ignored + */ sdev_printk(KERN_INFO, sdev, "failed to add device handler: %d\n", error); - return error; - } device_enable_async_suspend(&sdev->sdev_dev); error = device_add(&sdev->sdev_dev); @@ -1192,16 +1272,18 @@ static void __scsi_remove_target(struct scsi_target *starget) void scsi_remove_target(struct device *dev) { struct Scsi_Host *shost = dev_to_shost(dev->parent); - struct scsi_target *starget; + struct scsi_target *starget, *last_target = NULL; unsigned long flags; restart: spin_lock_irqsave(shost->host_lock, flags); list_for_each_entry(starget, &shost->__targets, siblings) { - if (starget->state == STARGET_DEL) + if (starget->state == STARGET_DEL || + starget == last_target) continue; if (starget->dev.parent == dev || &starget->dev == dev) { kref_get(&starget->reap_ref); + last_target = starget; spin_unlock_irqrestore(shost->host_lock, flags); __scsi_remove_target(starget); scsi_target_reap(starget); |