@@ -1198,6 +1198,103 @@ sdev_show_preferred_path(struct device *dev,
static DEVICE_ATTR(preferred_path, S_IRUGO, sdev_show_preferred_path, NULL);
#endif
+#ifdef CONFIG_SCSI_MULTIPATH
+static const struct {
+ unsigned char value;
+ char *name;
+} scsi_multipath_iopolicy[] = {
+ { SCSI_MPATH_IOPOLICY_NUMA, "NUMA" },
+ { SCSI_MPATH_IOPOLICY_RR, "Round-Robin" },
+};
+static const char *scsi_mpath_policy_name(unsigned char policy)
+{
+ int i;
+ char *name = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(scsi_multipath_iopolicy); i++) {
+ if (scsi_multipath_iopolicy[i].value == policy) {
+ name = scsi_multipath_iopolicy[i].name;
+ break;
+ }
+ }
+ return name;
+}
+
+static ssize_t
+sdev_show_multipath_iopolicy(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ const char *name = scsi_mpath_policy_name(sdev->mpath_iopolicy);
+
+ if (!sdev->mpath_disk)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", name);
+}
+
+static ssize_t sdev_store_multipath_iopolicy(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(scsi_multipath_iopolicy); i++) {
+ if (sysfs_streq(buf, scsi_mpath_policy_name(i))) {
+ scsi_multipath_iopolicy_update(sdev, i);
+ return count;
+ }
+ }
+
+ return -EINVAL;
+}
+static DEVICE_ATTR(multipath_iopolicy, S_IRUGO, sdev_show_multipath_iopolicy,
+ sdev_store_multipath_iopolicy);
+
+static const struct {
+ unsigned char value;
+ char *name;
+} scsi_mpath_states[] = {
+ { SCSI_MPATH_OPTIMAL, "active/optimized" },
+ { SCSI_MPATH_ACTIVE, "active/non-optimized" },
+ { SCSI_MPATH_STANDBY, "standby" },
+ { SCSI_MPATH_UNAVAILABLE,"unavailable" },
+ { SCSI_MPATH_LBA, "lba-dependent" },
+ { SCSI_MPATH_OFFLINE, "offline" },
+ { SCSI_MPATH_TRANSITIONING,"transitioning" },
+};
+
+static const char *scsi_mpath_state_names(unsigned char state)
+{
+ int i;
+ char *name = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(scsi_mpath_states); i++) {
+ if (scsi_mpath_states[i].value == state) {
+ name = scsi_mpath_states[i].name;
+ break;
+ }
+ }
+ return name;
+}
+
+static ssize_t
+sdev_show_multipath_state(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scsi_device *sdev = to_scsi_device(dev);
+ const char *name = scsi_mpath_state_names(sdev->mpath_state);
+
+ if (!sdev->mpath_disk)
+ return -EINVAL;
+
+ return sysfs_emit(buf, "%s\n", name);
+}
+static DEVICE_ATTR(multipath_state, S_IRUGO, sdev_show_multipath_state, NULL);
+#endif
+
static ssize_t
sdev_show_queue_ramp_up_period(struct device *dev,
struct device_attribute *attr,
@@ -1335,6 +1432,10 @@ static struct attribute *scsi_sdev_attrs[] = {
&dev_attr_dh_state.attr,
&dev_attr_access_state.attr,
&dev_attr_preferred_path.attr,
+#endif
+#ifdef CONFIG_SCSI_MULTIPATH
+ &dev_attr_multipath_iopolicy.attr,
+ &dev_attr_multipath_state.attr,
#endif
&dev_attr_queue_ramp_up_period.attr,
&dev_attr_cdl_supported.attr,
@@ -1500,6 +1601,9 @@ void __scsi_remove_device(struct scsi_device *sdev)
} else
put_device(&sdev->sdev_dev);
+ if (scsi_is_sdev_multipath(sdev))
+ scsi_mpath_dev_release(sdev);
+
/*
* Stop accepting new requests and wait until all queuecommand() and
* scsi_run_queue() invocations have finished before tearing down the