@@ -170,7 +170,7 @@ scsi_mod-$(CONFIG_BLK_DEV_BSG) += scsi_bsg.o
hv_storvsc-y := storvsc_drv.o
-sd_mod-objs := sd.o
+sd_mod-objs := sd.o sd_cdl.o
sd_mod-$(CONFIG_BLK_DEV_INTEGRITY) += sd_dif.o
sd_mod-$(CONFIG_BLK_DEV_ZONED) += sd_zbc.o
@@ -3306,6 +3306,7 @@ static int sd_revalidate_disk(struct gendisk *disk)
sd_read_write_same(sdkp, buffer);
sd_read_security(sdkp, buffer);
sd_config_protection(sdkp);
+ sd_read_cdl(sdkp, buffer);
}
/*
@@ -3626,6 +3627,7 @@ static void scsi_disk_release(struct device *dev)
ida_free(&sd_index_ida, sdkp->index);
sd_zbc_free_zone_info(sdkp);
+ sd_cdl_release(sdkp);
put_device(&sdkp->device->sdev_gendev);
free_opal_dev(sdkp->opal_dev);
@@ -81,6 +81,62 @@ struct zoned_disk_info {
u32 zone_blocks;
};
+/*
+ * Command duration limits sub-pages for the control mode page 0Ah.
+ */
+enum sd_cdlp {
+ SD_CDLP_A,
+ SD_CDLP_B,
+ SD_CDLP_T2A,
+ SD_CDLP_T2B,
+ SD_CDLP_NONE,
+
+ SD_CDL_MAX_PAGES = SD_CDLP_NONE,
+};
+
+enum sd_cdl_cmd {
+ SD_CDL_READ_16,
+ SD_CDL_WRITE_16,
+ SD_CDL_READ_32,
+ SD_CDL_WRITE_32,
+
+ SD_CDL_CMD_MAX,
+};
+
+enum sd_cdl_rw {
+ SD_CDL_READ,
+ SD_CDL_WRITE,
+ SD_CDL_RW,
+};
+
+struct sd_cdl_desc {
+ struct kobject kobj;
+ u64 max_inactive_time;
+ u64 max_active_time;
+ u64 duration;
+ u8 max_inactive_policy;
+ u8 max_active_policy;
+ u8 duration_policy;
+ u8 cdlp;
+};
+
+#define SD_CDL_MAX_DESC 7
+
+struct sd_cdl_page {
+ struct kobject kobj;
+ bool sysfs_registered;
+ enum sd_cdl_rw rw;
+ enum sd_cdlp cdlp;
+ struct sd_cdl_desc descs[SD_CDL_MAX_DESC];
+};
+
+struct sd_cdl {
+ struct kobject kobj;
+ bool sysfs_registered;
+ u8 perf_vs_duration_guideline;
+ struct sd_cdl_page pages[SD_CDL_RW];
+};
+
struct scsi_disk {
struct scsi_device *device;
@@ -131,6 +187,7 @@ struct scsi_disk {
u8 provisioning_mode;
u8 zeroing_mode;
u8 nr_actuators; /* Number of actuators */
+ struct sd_cdl *cdl;
unsigned ATO : 1; /* state of disk ATO bit */
unsigned cache_override : 1; /* temp override of WCE,RCD */
unsigned WCE : 1; /* state of disk WCE bit */
@@ -295,4 +352,8 @@ static inline blk_status_t sd_zbc_prepare_zone_append(struct scsi_cmnd *cmd,
void sd_print_sense_hdr(struct scsi_disk *sdkp, struct scsi_sense_hdr *sshdr);
void sd_print_result(const struct scsi_disk *sdkp, const char *msg, int result);
+/* Command duration limits support (in sd_cdl.c) */
+void sd_read_cdl(struct scsi_disk *sdkp, unsigned char *buf);
+void sd_cdl_release(struct scsi_disk *sdkp);
+
#endif /* _SCSI_DISK_H */
new file mode 100644
@@ -0,0 +1,764 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SCSI Command Duration Limits (CDL)
+ *
+ * Copyright (C) 2021 Western Digital Corporation or its affiliates.
+ */
+#include <linux/vmalloc.h>
+#include <linux/mutex.h>
+
+#include <asm/unaligned.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+
+#include "sd.h"
+
+/*
+ * Command duration limits sub-pages for the control mode page 0Ah.
+ */
+static const struct sd_cdlp_info {
+ u8 subpage;
+ char *name;
+} cdl_page[SD_CDL_MAX_PAGES + 1] = {
+ { 0x03, "A" },
+ { 0x04, "B" },
+ { 0x07, "T2A" },
+ { 0x08, "T2B" },
+ { 0x00, "none" },
+};
+
+static const struct sd_cdl_cmd_info {
+ u8 opcode;
+ u16 sa;
+ char *name;
+} cdl_cmd[SD_CDL_CMD_MAX] = {
+ { READ_16, 0, "READ_16" },
+ { WRITE_16, 0, "WRITE_16" },
+ { VARIABLE_LENGTH_CMD, READ_32, "READ_32" },
+ { VARIABLE_LENGTH_CMD, WRITE_32, "WRITE_32" },
+};
+
+static const char *sd_cdl_perf_name(u8 val)
+{
+ switch (val) {
+ case 0x00:
+ return "0";
+ case 0x01:
+ return "0.5";
+ case 0x02:
+ return "1.0";
+ case 0x03:
+ return "1.5";
+ case 0x04:
+ return "2.0";
+ case 0x05:
+ return "2.5";
+ case 0x06:
+ return "3";
+ case 0x07:
+ return "4";
+ case 0x08:
+ return "5";
+ case 0x09:
+ return "8";
+ case 0x0A:
+ return "10";
+ case 0x0B:
+ return "15";
+ case 0x0C:
+ return "20";
+ default:
+ return "?";
+ }
+}
+
+static const char *sd_cdl_policy_name(u8 policy)
+{
+ switch (policy) {
+ case 0x00:
+ return "complete-earliest";
+ case 0x01:
+ return "continue-next-limit";
+ case 0x02:
+ return "continue-no-limit";
+ case 0x0d:
+ return "complete-unavailable";
+ case 0x0e:
+ return "abort-recovery";
+ case 0x0f:
+ return "abort";
+ default:
+ return "?";
+ }
+}
+
+/*
+ * Command duration limits descriptors sysfs plumbing.
+ */
+struct sd_cdl_desc_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct sd_cdl_desc *desc, char *buf);
+};
+
+#define CDL_DESC_ATTR_RO(_name) \
+ static struct sd_cdl_desc_sysfs_entry \
+ cdl_desc_##_name##_entry = { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = cdl_desc_##_name##_show, \
+ }
+
+static ssize_t cdl_desc_max_inactive_time_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", desc->max_inactive_time);
+}
+CDL_DESC_ATTR_RO(max_inactive_time);
+
+static ssize_t cdl_desc_max_inactive_time_policy_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ sd_cdl_policy_name(desc->max_inactive_policy));
+}
+CDL_DESC_ATTR_RO(max_inactive_time_policy);
+
+static ssize_t cdl_desc_max_active_time_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", desc->max_active_time);
+}
+CDL_DESC_ATTR_RO(max_active_time);
+
+static ssize_t cdl_desc_max_active_time_policy_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ sd_cdl_policy_name(desc->max_active_policy));
+}
+CDL_DESC_ATTR_RO(max_active_time_policy);
+
+static ssize_t cdl_desc_duration_guideline_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%llu\n", desc->duration);
+}
+CDL_DESC_ATTR_RO(duration_guideline);
+
+static ssize_t cdl_desc_duration_guideline_policy_show(struct sd_cdl_desc *desc,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ sd_cdl_policy_name(desc->duration_policy));
+}
+CDL_DESC_ATTR_RO(duration_guideline_policy);
+
+static umode_t sd_cdl_desc_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct sd_cdl_desc *desc = container_of(kobj, struct sd_cdl_desc, kobj);
+
+ /*
+ * Descriptors in pages A and B only have the duration guideline
+ * field.
+ */
+ if ((desc->cdlp == SD_CDLP_A || desc->cdlp == SD_CDLP_B) &&
+ (attr != &cdl_desc_duration_guideline_entry.attr))
+ return 0;
+
+ return attr->mode;
+}
+
+static struct attribute *sd_cdl_desc_attrs[] = {
+ &cdl_desc_max_inactive_time_entry.attr,
+ &cdl_desc_max_inactive_time_policy_entry.attr,
+ &cdl_desc_max_active_time_entry.attr,
+ &cdl_desc_max_active_time_policy_entry.attr,
+ &cdl_desc_duration_guideline_entry.attr,
+ &cdl_desc_duration_guideline_policy_entry.attr,
+ NULL,
+};
+
+static const struct attribute_group sd_cdl_desc_group = {
+ .attrs = sd_cdl_desc_attrs,
+ .is_visible = sd_cdl_desc_attr_visible,
+};
+__ATTRIBUTE_GROUPS(sd_cdl_desc);
+
+static ssize_t sd_cdl_desc_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct sd_cdl_desc_sysfs_entry *entry =
+ container_of(attr, struct sd_cdl_desc_sysfs_entry, attr);
+ struct sd_cdl_desc *desc = container_of(kobj, struct sd_cdl_desc, kobj);
+
+ return entry->show(desc, buf);
+}
+
+static const struct sysfs_ops sd_cdl_desc_sysfs_ops = {
+ .show = sd_cdl_desc_sysfs_show,
+};
+
+static void sd_cdl_sysfs_nop_release(struct kobject *kobj) { }
+
+static struct kobj_type sd_cdl_desc_ktype = {
+ .sysfs_ops = &sd_cdl_desc_sysfs_ops,
+ .default_groups = sd_cdl_desc_groups,
+ .release = sd_cdl_sysfs_nop_release,
+};
+
+/*
+ * Duration limits page sysfs plumbing.
+ */
+struct sd_cdl_page_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct sd_cdl_page *page, char *buf);
+};
+
+#define CDL_PAGE_ATTR_RO(_name) \
+ static struct sd_cdl_page_sysfs_entry \
+ cdl_page_##_name##_entry = { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = cdl_page_##_name##_show, \
+ }
+
+static ssize_t cdl_page_page_show(struct sd_cdl_page *page, char *buf)
+{
+ return sysfs_emit(buf, "%s\n", cdl_page[page->cdlp].name);
+}
+CDL_PAGE_ATTR_RO(page);
+
+static struct attribute *sd_cdl_page_attrs[] = {
+ &cdl_page_page_entry.attr,
+ NULL,
+};
+
+static const struct attribute_group sd_cdl_page_group = {
+ .attrs = sd_cdl_page_attrs,
+};
+__ATTRIBUTE_GROUPS(sd_cdl_page);
+
+static ssize_t sd_cdl_page_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct sd_cdl_page_sysfs_entry *entry =
+ container_of(attr, struct sd_cdl_page_sysfs_entry, attr);
+ struct sd_cdl_page *page = container_of(kobj, struct sd_cdl_page, kobj);
+
+ return entry->show(page, buf);
+}
+
+static const struct sysfs_ops sd_cdl_page_sysfs_ops = {
+ .show = sd_cdl_page_sysfs_show,
+};
+
+static struct kobj_type sd_cdl_page_ktype = {
+ .sysfs_ops = &sd_cdl_page_sysfs_ops,
+ .default_groups = sd_cdl_page_groups,
+ .release = sd_cdl_sysfs_nop_release,
+};
+
+static void sd_cdl_sysfs_unregister_page(struct sd_cdl_page *page)
+{
+ int i;
+
+ for (i = 0; i < SD_CDL_MAX_DESC; i++) {
+ if (page->sysfs_registered)
+ kobject_del(&page->descs[i].kobj);
+ kobject_put(&page->descs[i].kobj);
+ }
+ if (page->sysfs_registered)
+ kobject_del(&page->kobj);
+ kobject_put(&page->kobj);
+
+ page->cdlp = SD_CDLP_NONE;
+ page->sysfs_registered = false;
+}
+
+static int sd_cdl_sysfs_register_page(struct scsi_disk *sdkp,
+ struct sd_cdl_page *page)
+{
+ int i, ret;
+
+ /*
+ * If the page is already registered, the updated page descriptors
+ * are already exported.
+ */
+ if (page->sysfs_registered)
+ return 0;
+
+ ret = kobject_add(&page->kobj, &sdkp->cdl->kobj,
+ "%s", page->rw ? "write" : "read");
+ if (ret) {
+ kobject_put(&page->kobj);
+ return ret;
+ }
+
+ for (i = 0; i < SD_CDL_MAX_DESC; i++) {
+ ret = kobject_add(&page->descs[i].kobj, &page->kobj,
+ "%d", i + 1);
+ if (ret) {
+ int j;
+
+ kobject_put(&page->descs[i].kobj);
+ for (j = 0; j < SD_CDL_MAX_DESC; j++) {
+ if (j < i)
+ kobject_del(&page->descs[j].kobj);
+ kobject_put(&page->descs[j].kobj);
+ }
+ kobject_del(&page->kobj);
+ kobject_put(&page->kobj);
+ return ret;
+ }
+ }
+
+ page->sysfs_registered = true;
+
+ return 0;
+}
+
+/*
+ * Command duration limits sysfs plumbing, top level (duration limits directory
+ * under the "device" sysfs directory.
+ */
+struct sd_cdl_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct sd_cdl *cdl, char *buf);
+};
+
+#define CDL_ATTR_RO(_name) \
+ static struct sd_cdl_sysfs_entry cdl_##_name##_entry = { \
+ .attr = { .name = __stringify(_name), .mode = 0444 }, \
+ .show = cdl_##_name##_show, \
+ }
+
+static ssize_t cdl_perf_vs_duration_guideline_show(struct sd_cdl *cdl,
+ char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ sd_cdl_perf_name(cdl->perf_vs_duration_guideline));
+}
+CDL_ATTR_RO(perf_vs_duration_guideline);
+
+static struct attribute *sd_cdl_attrs[] = {
+ &cdl_perf_vs_duration_guideline_entry.attr,
+ NULL,
+};
+
+static umode_t sd_cdl_attr_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct sd_cdl *cdl = container_of(kobj, struct sd_cdl, kobj);
+
+ /* perf_vs_duration_guideline exists only if page T2A is supported */
+ if (attr == &cdl_perf_vs_duration_guideline_entry.attr &&
+ cdl->pages[SD_CDL_READ].cdlp != SD_CDLP_T2A &&
+ cdl->pages[SD_CDL_WRITE].cdlp != SD_CDLP_T2A)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group sd_cdl_group = {
+ .attrs = sd_cdl_attrs,
+ .is_visible = sd_cdl_attr_visible,
+};
+__ATTRIBUTE_GROUPS(sd_cdl);
+
+static ssize_t sd_cdl_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *page)
+{
+ struct sd_cdl_sysfs_entry *entry =
+ container_of(attr, struct sd_cdl_sysfs_entry, attr);
+ struct sd_cdl *cdl = container_of(kobj, struct sd_cdl, kobj);
+
+ return entry->show(cdl, page);
+}
+
+static const struct sysfs_ops sd_cdl_sysfs_ops = {
+ .show = sd_cdl_sysfs_show,
+};
+
+static void sd_cdl_sysfs_release(struct kobject *kobj)
+{
+ struct sd_cdl *cdl = container_of(kobj, struct sd_cdl, kobj);
+
+ kfree(cdl);
+}
+
+static struct kobj_type sd_cdl_ktype = {
+ .sysfs_ops = &sd_cdl_sysfs_ops,
+ .default_groups = sd_cdl_groups,
+ .release = sd_cdl_sysfs_release,
+};
+
+static void sd_cdl_sysfs_unregister(struct scsi_disk *sdkp)
+{
+ struct sd_cdl *cdl = NULL;
+ int i;
+
+ swap(sdkp->cdl, cdl);
+ if (!cdl)
+ return;
+
+ if (!cdl->sysfs_registered) {
+ kfree(cdl);
+ return;
+ }
+
+ for (i = 0; i < SD_CDL_RW; i++) {
+ if (cdl->pages[i].sysfs_registered)
+ sd_cdl_sysfs_unregister_page(&cdl->pages[i]);
+ }
+
+ kobject_del(&cdl->kobj);
+ kobject_put(&cdl->kobj);
+}
+
+static void sd_cdl_sysfs_register(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdev = sdkp->device;
+ struct sd_cdl *cdl = sdkp->cdl;
+ struct sd_cdl_page *page;
+ int i, ret;
+
+ if (!cdl->sysfs_registered) {
+ ret = kobject_add(&cdl->kobj, &sdev->sdev_gendev.kobj,
+ "duration_limits");
+ if (ret) {
+ kobject_put(&cdl->kobj);
+ goto unregister;
+ }
+
+ cdl->sysfs_registered = true;
+ }
+
+ /* Check if the pages changed */
+ for (i = 0; i < SD_CDL_RW; i++) {
+ page = &cdl->pages[i];
+ if (page->cdlp == SD_CDLP_NONE) {
+ sd_cdl_sysfs_unregister_page(page);
+ continue;
+ }
+
+ ret = sd_cdl_sysfs_register_page(sdkp, page);
+ if (ret) {
+ page->cdlp = SD_CDLP_NONE;
+ goto unregister;
+ }
+ }
+
+ return;
+
+unregister:
+ sd_cdl_sysfs_unregister(sdkp);
+}
+
+/*
+ * CDL pages A and B time limits in microseconds.
+ */
+static u64 sd_cdl_time(u8 *buf, u8 cdlunit)
+{
+ u64 val = get_unaligned_be16(buf);
+ u64 factor;
+
+ switch (cdlunit) {
+ case 0x00:
+ return 0;
+ case 0x04:
+ /* 1 microsecond */
+ factor = NSEC_PER_USEC;
+ break;
+ case 0x05:
+ /* 10 milliseconds */
+ factor = 10ULL * USEC_PER_MSEC;
+ break;
+ case 0x06:
+ /* 500 milliseconds */
+ factor = 500ULL * USEC_PER_MSEC;
+ break;
+ default:
+ return 0;
+ }
+
+ return val * factor;
+}
+
+/*
+ * CDL pages T2A and T2B time limits in microseconds.
+ */
+static u64 sd_cdl_t2time(u8 *buf, u8 t2cdlunits)
+{
+ u64 val = get_unaligned_be16(buf);
+ u64 factor;
+
+ switch (t2cdlunits) {
+ case 0x00:
+ return 0;
+ case 0x06:
+ /* 500 nanoseconds */
+ factor = 500;
+ break;
+ case 0x08:
+ /* 1 microsecond */
+ factor = NSEC_PER_USEC;
+ break;
+ case 0x0A:
+ /* 10 milliseconds */
+ factor = 10ULL * NSEC_PER_MSEC;
+ break;
+ case 0x0E:
+ /* 500 milliseconds */
+ factor = 500ULL * NSEC_PER_MSEC;
+ break;
+ default:
+ return 0;
+ }
+
+ val *= factor;
+ do_div(val, NSEC_PER_USEC);
+
+ return val;
+}
+
+static int sd_cdl_read_page(struct scsi_disk *sdkp, struct sd_cdl_page *page,
+ unsigned char *buf)
+{
+ struct sd_cdl *cdl = sdkp->cdl;
+ struct sd_cdl_desc *desc = &page->descs[0];
+ u8 cdlp = page->cdlp;
+ struct scsi_mode_data data;
+ int i, ret;
+
+ ret = scsi_mode_sense(sdkp->device, 0x08, 0x0a, cdl_page[cdlp].subpage,
+ buf, SD_BUF_SIZE, SD_TIMEOUT, sdkp->max_retries,
+ &data, NULL);
+ if (ret) {
+ sd_printk(KERN_ERR, sdkp,
+ "Command duration limits: read CDL page %s failed\n",
+ cdl_page[cdlp].name);
+ return ret;
+ }
+ buf += data.header_length + data.block_descriptor_length;
+
+ if (cdlp == SD_CDLP_A || cdlp == SD_CDLP_B) {
+ buf += 8;
+
+ for (i = 0; i < SD_CDL_MAX_DESC; i++, buf += 4, desc++) {
+ u8 cdlunit = (buf[0] & 0xe0) >> 5;
+
+ desc->duration = sd_cdl_time(&buf[2], cdlunit);
+ desc->cdlp = cdlp;
+ }
+ } else {
+ /* T2A and T2B limits page */
+ if (cdlp == SD_CDLP_T2A)
+ cdl->perf_vs_duration_guideline = buf[7] >> 4;
+
+ buf += 8;
+
+ for (i = 0; i < SD_CDL_MAX_DESC; i++, buf += 32, desc++) {
+ u8 t2cdlunits = buf[0] & 0x0f;
+
+ desc->max_inactive_time =
+ sd_cdl_t2time(&buf[2], t2cdlunits);
+ desc->max_active_time =
+ sd_cdl_t2time(&buf[4], t2cdlunits);
+ desc->duration =
+ sd_cdl_t2time(&buf[10], t2cdlunits);
+ desc->max_inactive_policy = (buf[6] >> 4) & 0x0f;
+ desc->max_active_policy = buf[6] & 0x0f;
+ desc->duration_policy = buf[14] & 0x0f;
+ desc->cdlp = cdlp;
+ }
+ }
+
+ return 0;
+}
+
+static int sd_cdl_read_pages(struct scsi_disk *sdkp, enum sd_cdlp *rw_cdlp,
+ unsigned char *buf)
+{
+ struct sd_cdl *cdl = sdkp->cdl;
+ struct sd_cdl_page *page;
+ int i, ret;
+
+ /* Read supported pages */
+ for (i = 0; i < SD_CDL_RW; i++) {
+ page = &cdl->pages[i];
+ page->cdlp = rw_cdlp[i];
+ if (page->cdlp == SD_CDLP_NONE)
+ continue;
+
+ ret = sd_cdl_read_page(sdkp, page, buf);
+ if (ret) {
+ page->cdlp = SD_CDLP_NONE;
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static u8 sd_cdl_check_cmd_support(struct scsi_disk *sdkp,
+ enum sd_cdl_cmd cmd, unsigned char *buf)
+{
+ u8 opcode = cdl_cmd[cmd].opcode;
+ u16 sa = cdl_cmd[cmd].sa;
+ u8 cdlp;
+
+ /*
+ * READ 32 and WRITE 32 are used only for disks that also support
+ * type 2 data protection. If the disk does not have such feature,
+ * ignore these commands.
+ */
+ if ((sa == READ_32 || sa == WRITE_32) &&
+ sdkp->protection_type != T10_PI_TYPE2_PROTECTION)
+ return SD_CDLP_NONE;
+
+ /* Check operation code */
+ if (scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE, opcode, sa) < 0)
+ return SD_CDLP_NONE;
+
+ if ((buf[1] & 0x03) != 0x03)
+ return SD_CDLP_NONE;
+
+ /* See SPC-6, one command format of REPORT SUPPORTED OPERATION CODES */
+ cdlp = (buf[1] & 0x18) >> 3;
+ if (buf[0] & 0x01) {
+ /* rwcdlp == 1 */
+ switch (cdlp) {
+ case 0x01:
+ return SD_CDLP_T2A;
+ case 0x02:
+ return SD_CDLP_T2B;
+ }
+ } else {
+ /* rwcdlp == 0 */
+ switch (cdlp) {
+ case 0x01:
+ return SD_CDLP_A;
+ case 0x02:
+ return SD_CDLP_B;
+ }
+ }
+
+ return SD_CDLP_NONE;
+}
+
+static bool sd_cdl_supported(struct scsi_disk *sdkp, enum sd_cdlp *rw_cdlp,
+ unsigned char *buf)
+{
+ enum sd_cdlp cmd_cdlp[SD_CDL_CMD_MAX];
+ int i;
+
+ /*
+ * Command duration limits is supported for READ 16, WRITE 16,
+ * READ 32 and WRITE 32. Go through all these commands one at a time
+ * and check if any support duration limits.
+ */
+ for (i = 0; i < SD_CDL_CMD_MAX; i++)
+ cmd_cdlp[i] = sd_cdl_check_cmd_support(sdkp, i, buf);
+
+ /*
+ * Allow support only for drives that report the same CDL page for the
+ * read 16 and 32 variants and the same page for the write 16 and 32
+ * variants.
+ */
+ if (cmd_cdlp[SD_CDL_READ_32] != SD_CDLP_NONE &&
+ cmd_cdlp[SD_CDL_READ_16] != SD_CDLP_NONE) {
+ if (cmd_cdlp[SD_CDL_READ_32] != cmd_cdlp[SD_CDL_READ_16])
+ rw_cdlp[SD_CDL_READ] = SD_CDLP_NONE;
+ else
+ rw_cdlp[SD_CDL_READ] = cmd_cdlp[SD_CDL_READ_16];
+ } else {
+ rw_cdlp[SD_CDL_READ] = cmd_cdlp[SD_CDL_READ_16];
+ }
+
+ if (cmd_cdlp[SD_CDL_WRITE_32] != SD_CDLP_NONE &&
+ cmd_cdlp[SD_CDL_WRITE_16] != SD_CDLP_NONE) {
+ if (cmd_cdlp[SD_CDL_WRITE_32] != cmd_cdlp[SD_CDL_WRITE_16])
+ rw_cdlp[SD_CDL_WRITE] = SD_CDLP_NONE;
+ else
+ rw_cdlp[SD_CDL_WRITE] = cmd_cdlp[SD_CDL_WRITE_16];
+ } else {
+ rw_cdlp[SD_CDL_WRITE] = cmd_cdlp[SD_CDL_WRITE_16];
+ }
+
+ return rw_cdlp[SD_CDL_READ] != SD_CDLP_NONE ||
+ rw_cdlp[SD_CDL_WRITE] != SD_CDLP_NONE;
+}
+
+static struct sd_cdl *sd_cdl_alloc(void)
+{
+ struct sd_cdl *cdl;
+ struct sd_cdl_page *page;
+ int i, j;
+
+ cdl = kzalloc(sizeof(struct sd_cdl), GFP_KERNEL);
+ if (!cdl)
+ return NULL;
+
+ kobject_init(&cdl->kobj, &sd_cdl_ktype);
+ for (i = 0; i < SD_CDL_RW; i++) {
+ page = &cdl->pages[i];
+ kobject_init(&page->kobj, &sd_cdl_page_ktype);
+ page->rw = i;
+ page->cdlp = SD_CDLP_NONE;
+ for (j = 0; j < SD_CDL_MAX_DESC; j++)
+ kobject_init(&page->descs[j].kobj, &sd_cdl_desc_ktype);
+ }
+
+ return cdl;
+}
+
+void sd_read_cdl(struct scsi_disk *sdkp, unsigned char *buf)
+{
+ struct sd_cdl *cdl = sdkp->cdl;
+ enum sd_cdlp rw_cdlp[SD_CDL_RW];
+
+ /*
+ * Check for CDL support. If the disk does not support duration limits,
+ * clear any support information that was previously registered.
+ */
+ if (!sd_cdl_supported(sdkp, rw_cdlp, buf))
+ goto unregister;
+
+ if (!cdl) {
+ cdl = sd_cdl_alloc();
+ if (!cdl)
+ return;
+ }
+
+ /*
+ * We have CDL support: force the use of READ16/WRITE16.
+ * READ32 and WRITE32 will be used automatically for disks with
+ * T10_PI_TYPE2_PROTECTION support.
+ */
+ sdkp->device->use_16_for_rw = 1;
+ sdkp->device->use_10_for_rw = 0;
+
+ if (!sdkp->cdl) {
+ sd_printk(KERN_NOTICE, sdkp,
+ "Command duration limits supported, reads: %s, writes: %s\n",
+ cdl_page[rw_cdlp[SD_CDL_READ]].name,
+ cdl_page[rw_cdlp[SD_CDL_WRITE]].name);
+ sdkp->cdl = cdl;
+ }
+
+ /* Update duration limits descriptor pages */
+ if (sd_cdl_read_pages(sdkp, rw_cdlp, buf))
+ goto unregister;
+
+ sd_cdl_sysfs_register(sdkp);
+
+ return;
+
+unregister:
+ sd_cdl_sysfs_unregister(sdkp);
+}
+
+void sd_cdl_release(struct scsi_disk *sdkp)
+{
+ sd_cdl_sysfs_unregister(sdkp);
+}