scsi: core: Allow enabling and disabling command duration limits
authorDamien Le Moal <dlemoal@kernel.org>
Thu, 11 May 2023 01:13:42 +0000 (03:13 +0200)
committerMartin K. Petersen <martin.petersen@oracle.com>
Mon, 22 May 2023 21:05:19 +0000 (17:05 -0400)
Add the sysfs scsi_device attribute cdl_enable to allow a user to enable or
disable a device command duration limits feature. CDL is disabled by
default. This feature must be explicitly enabled by a user by setting the
cdl_enable attribute to 1.

The new function scsi_cdl_enable() does not do anything beside setting the
cdl_enable field of struct scsi_device in the case of a (real) SCSI device
(e.g. a SAS HDD). For ATA devices, the command duration limits feature
needs to be enabled/disabled using the ATA feature sub-page of the control
mode page. To do so, the scsi_cdl_enable() function checks if this mode
page is supported using scsi_mode_sense(). If it is, scsi_mode_select() is
used to enable and disable CDL.

Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Co-developed-by: Niklas Cassel <niklas.cassel@wdc.com>
Signed-off-by: Niklas Cassel <niklas.cassel@wdc.com>
Link: https://lore.kernel.org/r/20230511011356.227789-10-nks@flawful.org
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Documentation/ABI/testing/sysfs-block-device
drivers/scsi/scsi.c
drivers/scsi/scsi_sysfs.c
include/scsi/scsi_device.h

index ffc3358cba570dc889c74eac6ffe2f5c6dd6dff2..2d543cfa4079af03909d25a29f8ab0ad7416f6fd 100644 (file)
@@ -104,3 +104,16 @@ Contact:   linux-scsi@vger.kernel.org
 Description:
                (RO) Indicates if the device supports the command duration
                limits feature found in some ATA and SCSI devices.
+
+
+What:          /sys/block/*/device/cdl_enable
+Date:          May, 2023
+KernelVersion: v6.5
+Contact:       linux-scsi@vger.kernel.org
+Description:
+               (RW) For a device supporting the command duration limits
+               feature, write to the file to turn on or off the feature.
+               By default this feature is turned off.
+               Writing "1" to this file enables the use of command duration
+               limits for read and write commands in the kernel and turns on
+               the feature on the device. Writing "0" disables the feature.
index c03814ce23ca0473cc21fd7dd766ac3dd0f59586..c4bf99a842f366d75c4b54c88dd49225c492af9a 100644 (file)
@@ -651,6 +651,68 @@ void scsi_cdl_check(struct scsi_device *sdev)
        kfree(buf);
 }
 
+/**
+ * scsi_cdl_enable - Enable or disable a SCSI device supports for Command
+ *                   Duration Limits
+ * @sdev: The target device
+ * @enable: the target state
+ */
+int scsi_cdl_enable(struct scsi_device *sdev, bool enable)
+{
+       struct scsi_mode_data data;
+       struct scsi_sense_hdr sshdr;
+       struct scsi_vpd *vpd;
+       bool is_ata = false;
+       char buf[64];
+       int ret;
+
+       if (!sdev->cdl_supported)
+               return -EOPNOTSUPP;
+
+       rcu_read_lock();
+       vpd = rcu_dereference(sdev->vpd_pg89);
+       if (vpd)
+               is_ata = true;
+       rcu_read_unlock();
+
+       /*
+        * For ATA devices, CDL needs to be enabled with a SET FEATURES command.
+        */
+       if (is_ata) {
+               char *buf_data;
+               int len;
+
+               ret = scsi_mode_sense(sdev, 0x08, 0x0a, 0xf2, buf, sizeof(buf),
+                                     5 * HZ, 3, &data, NULL);
+               if (ret)
+                       return -EINVAL;
+
+               /* Enable CDL using the ATA feature page */
+               len = min_t(size_t, sizeof(buf),
+                           data.length - data.header_length -
+                           data.block_descriptor_length);
+               buf_data = buf + data.header_length +
+                       data.block_descriptor_length;
+               if (enable)
+                       buf_data[4] = 0x02;
+               else
+                       buf_data[4] = 0;
+
+               ret = scsi_mode_select(sdev, 1, 0, buf_data, len, 5 * HZ, 3,
+                                      &data, &sshdr);
+               if (ret) {
+                       if (scsi_sense_valid(&sshdr))
+                               scsi_print_sense_hdr(sdev,
+                                       dev_name(&sdev->sdev_gendev), &sshdr);
+                       return ret;
+               }
+       }
+
+       sdev->cdl_enable = enable;
+
+       return 0;
+}
+
 /**
  * scsi_device_get  -  get an additional reference to a scsi_device
  * @sdev:      device to get a reference to
index 98fcbbf1c1e36b62a17517832ab1e850d975a881..60317676e45f19bf7328d84422cb7f5f079bcb13 100644 (file)
@@ -1222,6 +1222,33 @@ static DEVICE_ATTR(queue_ramp_up_period, S_IRUGO | S_IWUSR,
                   sdev_show_queue_ramp_up_period,
                   sdev_store_queue_ramp_up_period);
 
+static ssize_t sdev_show_cdl_enable(struct device *dev,
+                                   struct device_attribute *attr, char *buf)
+{
+       struct scsi_device *sdev = to_scsi_device(dev);
+
+       return sysfs_emit(buf, "%d\n", (int)sdev->cdl_enable);
+}
+
+static ssize_t sdev_store_cdl_enable(struct device *dev,
+                                    struct device_attribute *attr,
+                                    const char *buf, size_t count)
+{
+       int ret;
+       bool v;
+
+       if (kstrtobool(buf, &v))
+               return -EINVAL;
+
+       ret = scsi_cdl_enable(to_scsi_device(dev), v);
+       if (ret)
+               return ret;
+
+       return count;
+}
+static DEVICE_ATTR(cdl_enable, S_IRUGO | S_IWUSR,
+                  sdev_show_cdl_enable, sdev_store_cdl_enable);
+
 static umode_t scsi_sdev_attr_is_visible(struct kobject *kobj,
                                         struct attribute *attr, int i)
 {
@@ -1302,6 +1329,7 @@ static struct attribute *scsi_sdev_attrs[] = {
 #endif
        &dev_attr_queue_ramp_up_period.attr,
        &dev_attr_cdl_supported.attr,
+       &dev_attr_cdl_enable.attr,
        REF_EVT(media_change),
        REF_EVT(inquiry_change_reported),
        REF_EVT(capacity_change_reported),
index 6b8df9e253a0f45633959363345c894783022a6d..b2cdb078b7bda99a31e5f568fbdbbc461c6ff4ba 100644 (file)
@@ -219,6 +219,7 @@ struct scsi_device {
        unsigned no_vpd_size:1;         /* No VPD size reported in header */
 
        unsigned cdl_supported:1;       /* Command duration limits supported */
+       unsigned cdl_enable:1;          /* Enable/disable Command duration limits */
 
        unsigned int queue_stopped;     /* request queue is quiesced */
        bool offline_already;           /* Device offline message logged */
@@ -367,6 +368,7 @@ extern void scsi_remove_device(struct scsi_device *);
 extern int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh);
 void scsi_attach_vpd(struct scsi_device *sdev);
 void scsi_cdl_check(struct scsi_device *sdev);
+int scsi_cdl_enable(struct scsi_device *sdev, bool enable);
 
 extern struct scsi_device *scsi_device_from_queue(struct request_queue *q);
 extern int __must_check scsi_device_get(struct scsi_device *);