|
@@ -81,6 +81,33 @@ const char *scsi_host_state_name(enum scsi_host_state state)
|
|
return name;
|
|
return name;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const struct {
|
|
|
|
+ unsigned char value;
|
|
|
|
+ char *name;
|
|
|
|
+} sdev_access_states[] = {
|
|
|
|
+ { SCSI_ACCESS_STATE_OPTIMAL, "active/optimized" },
|
|
|
|
+ { SCSI_ACCESS_STATE_ACTIVE, "active/non-optimized" },
|
|
|
|
+ { SCSI_ACCESS_STATE_STANDBY, "standby" },
|
|
|
|
+ { SCSI_ACCESS_STATE_UNAVAILABLE, "unavailable" },
|
|
|
|
+ { SCSI_ACCESS_STATE_LBA, "lba-dependent" },
|
|
|
|
+ { SCSI_ACCESS_STATE_OFFLINE, "offline" },
|
|
|
|
+ { SCSI_ACCESS_STATE_TRANSITIONING, "transitioning" },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const char *scsi_access_state_name(unsigned char state)
|
|
|
|
+{
|
|
|
|
+ int i;
|
|
|
|
+ char *name = NULL;
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(sdev_access_states); i++) {
|
|
|
|
+ if (sdev_access_states[i].value == state) {
|
|
|
|
+ name = sdev_access_states[i].name;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return name;
|
|
|
|
+}
|
|
|
|
+
|
|
static int check_set(unsigned long long *val, char *src)
|
|
static int check_set(unsigned long long *val, char *src)
|
|
{
|
|
{
|
|
char *last;
|
|
char *last;
|
|
@@ -973,6 +1000,43 @@ sdev_store_dh_state(struct device *dev, struct device_attribute *attr,
|
|
|
|
|
|
static DEVICE_ATTR(dh_state, S_IRUGO | S_IWUSR, sdev_show_dh_state,
|
|
static DEVICE_ATTR(dh_state, S_IRUGO | S_IWUSR, sdev_show_dh_state,
|
|
sdev_store_dh_state);
|
|
sdev_store_dh_state);
|
|
|
|
+
|
|
|
|
+static ssize_t
|
|
|
|
+sdev_show_access_state(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct scsi_device *sdev = to_scsi_device(dev);
|
|
|
|
+ unsigned char access_state;
|
|
|
|
+ const char *access_state_name;
|
|
|
|
+
|
|
|
|
+ if (!sdev->handler)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ access_state = (sdev->access_state & SCSI_ACCESS_STATE_MASK);
|
|
|
|
+ access_state_name = scsi_access_state_name(access_state);
|
|
|
|
+
|
|
|
|
+ return sprintf(buf, "%s\n",
|
|
|
|
+ access_state_name ? access_state_name : "unknown");
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(access_state, S_IRUGO, sdev_show_access_state, NULL);
|
|
|
|
+
|
|
|
|
+static ssize_t
|
|
|
|
+sdev_show_preferred_path(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct scsi_device *sdev = to_scsi_device(dev);
|
|
|
|
+
|
|
|
|
+ if (!sdev->handler)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ if (sdev->access_state & SCSI_ACCESS_STATE_PREFERRED)
|
|
|
|
+ return sprintf(buf, "1\n");
|
|
|
|
+ else
|
|
|
|
+ return sprintf(buf, "0\n");
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(preferred_path, S_IRUGO, sdev_show_preferred_path, NULL);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
static ssize_t
|
|
static ssize_t
|
|
@@ -1020,6 +1084,14 @@ static umode_t scsi_sdev_attr_is_visible(struct kobject *kobj,
|
|
!sdev->host->hostt->change_queue_depth)
|
|
!sdev->host->hostt->change_queue_depth)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
|
|
+#ifdef CONFIG_SCSI_DH
|
|
|
|
+ if (attr == &dev_attr_access_state.attr &&
|
|
|
|
+ !sdev->handler)
|
|
|
|
+ return 0;
|
|
|
|
+ if (attr == &dev_attr_preferred_path.attr &&
|
|
|
|
+ !sdev->handler)
|
|
|
|
+ return 0;
|
|
|
|
+#endif
|
|
return attr->mode;
|
|
return attr->mode;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1063,6 +1135,8 @@ static struct attribute *scsi_sdev_attrs[] = {
|
|
&dev_attr_wwid.attr,
|
|
&dev_attr_wwid.attr,
|
|
#ifdef CONFIG_SCSI_DH
|
|
#ifdef CONFIG_SCSI_DH
|
|
&dev_attr_dh_state.attr,
|
|
&dev_attr_dh_state.attr,
|
|
|
|
+ &dev_attr_access_state.attr,
|
|
|
|
+ &dev_attr_preferred_path.attr,
|
|
#endif
|
|
#endif
|
|
&dev_attr_queue_ramp_up_period.attr,
|
|
&dev_attr_queue_ramp_up_period.attr,
|
|
REF_EVT(media_change),
|
|
REF_EVT(media_change),
|