@@ -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: Mar, 2023
+KernelVersion: v6.4
+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.
@@ -641,6 +641,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
@@ -1222,6 +1222,36 @@ 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 (!capable(CAP_SYS_ADMIN))
+ return -EACCES;
+
+ 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 +1332,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),
@@ -217,6 +217,7 @@ struct scsi_device {
unsigned silence_suspend:1; /* Do not print runtime PM related messages */
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 */
@@ -365,6 +366,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 *);