scsi: ata: libata: Add ATA feature control sub-page translation
authorDamien Le Moal <dlemoal@kernel.org>
Thu, 11 May 2023 01:13:50 +0000 (03:13 +0200)
committerMartin K. Petersen <martin.petersen@oracle.com>
Mon, 22 May 2023 21:05:20 +0000 (17:05 -0400)
Add support for the ATA feature control sub-page of the control mode page
to enable/disable the command duration limits feature using the cdl_ctrl
field of the ATA feature control sub-page.

Both mode sense and mode select translation are supported. For mode sense,
the ata device flag ATA_DFLAG_CDL_ENABLED is used to cache the status of
the command duration limits feature. Enabling this feature is done using a
SET FEATURES command with a cdl action set to 1 when the page cdl_ctrl
field value is 0x2 (T2A and T2B pages supported). If this field is 0, CDL
is disabled using the SET FEATURES command with a cdl action set to 0.

Since a device CDL and NCQ priority features should not be used
simultaneously, ata_mselect_control_ata_feature() returns an error when
attempting to enable CDL with the device priority feature enabled.
Conversely, the function ata_ncq_prio_enable_store() used to enable the use
of the device NCQ priority feature through sysfs is modified to return an
error if the device CDL feature is enabled.

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-18-nks@flawful.org
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/ata/libata-core.c
drivers/ata/libata-sata.c
drivers/ata/libata-scsi.c
include/linux/ata.h
include/linux/libata.h

index 83fe037..cd7aaf2 100644 (file)
@@ -2371,13 +2371,15 @@ static void ata_dev_config_cdl(struct ata_device *dev)
 {
        struct ata_port *ap = dev->link->ap;
        unsigned int err_mask;
+       bool cdl_enabled;
        u64 val;
 
        if (ata_id_major_version(dev->id) < 12)
                goto not_supported;
 
        if (!ata_log_supported(dev, ATA_LOG_IDENTIFY_DEVICE) ||
-           !ata_identify_page_supported(dev, ATA_LOG_SUPPORTED_CAPABILITIES))
+           !ata_identify_page_supported(dev, ATA_LOG_SUPPORTED_CAPABILITIES) ||
+           !ata_identify_page_supported(dev, ATA_LOG_CURRENT_SETTINGS))
                goto not_supported;
 
        err_mask = ata_read_log_page(dev, ATA_LOG_IDENTIFY_DEVICE,
@@ -2396,6 +2398,40 @@ static void ata_dev_config_cdl(struct ata_device *dev)
                ata_dev_warn(dev,
                        "Command duration guideline is not supported\n");
 
+       /*
+        * If CDL is marked as enabled, make sure the feature is enabled too.
+        * Conversely, if CDL is disabled, make sure the feature is turned off.
+        */
+       err_mask = ata_read_log_page(dev, ATA_LOG_IDENTIFY_DEVICE,
+                                    ATA_LOG_CURRENT_SETTINGS,
+                                    ap->sector_buf, 1);
+       if (err_mask)
+               goto not_supported;
+
+       val = get_unaligned_le64(&ap->sector_buf[8]);
+       cdl_enabled = val & BIT_ULL(63) && val & BIT_ULL(21);
+       if (dev->flags & ATA_DFLAG_CDL_ENABLED) {
+               if (!cdl_enabled) {
+                       /* Enable CDL on the device */
+                       err_mask = ata_dev_set_feature(dev, SETFEATURES_CDL, 1);
+                       if (err_mask) {
+                               ata_dev_err(dev,
+                                           "Enable CDL feature failed\n");
+                               goto not_supported;
+                       }
+               }
+       } else {
+               if (cdl_enabled) {
+                       /* Disable CDL on the device */
+                       err_mask = ata_dev_set_feature(dev, SETFEATURES_CDL, 0);
+                       if (err_mask) {
+                               ata_dev_err(dev,
+                                           "Disable CDL feature failed\n");
+                               goto not_supported;
+                       }
+               }
+       }
+
        /*
         * Command duration limits is supported: cache the CDL log page 18h
         * (command duration descriptors).
@@ -2412,7 +2448,7 @@ static void ata_dev_config_cdl(struct ata_device *dev)
        return;
 
 not_supported:
-       dev->flags &= ~ATA_DFLAG_CDL;
+       dev->flags &= ~(ATA_DFLAG_CDL | ATA_DFLAG_CDL_ENABLED);
 }
 
 static int ata_dev_config_lba(struct ata_device *dev)
index f3e7396..57cb330 100644 (file)
@@ -907,10 +907,17 @@ static ssize_t ata_ncq_prio_enable_store(struct device *device,
                goto unlock;
        }
 
-       if (input)
+       if (input) {
+               if (dev->flags & ATA_DFLAG_CDL_ENABLED) {
+                       ata_dev_err(dev,
+                               "CDL must be disabled to enable NCQ priority\n");
+                       rc = -EINVAL;
+                       goto unlock;
+               }
                dev->flags |= ATA_DFLAG_NCQ_PRIO_ENABLED;
-       else
+       } else {
                dev->flags &= ~ATA_DFLAG_NCQ_PRIO_ENABLED;
+       }
 
 unlock:
        spin_unlock_irq(ap->lock);
index 4a4c640..91db4e7 100644 (file)
@@ -58,6 +58,8 @@ static struct ata_device *__ata_scsi_find_dev(struct ata_port *ap,
 #define CDL_T2A_SUB_MPAGE              0x07
 #define CDL_T2B_SUB_MPAGE              0x08
 #define CDL_T2_SUB_MPAGE_LEN           232
+#define ATA_FEATURE_SUB_MPAGE          0xf2
+#define ATA_FEATURE_SUB_MPAGE_LEN      16
 
 static const u8 def_rw_recovery_mpage[RW_RECOVERY_MPAGE_LEN] = {
        RW_RECOVERY_MPAGE,
@@ -2286,6 +2288,31 @@ static unsigned int ata_msense_control_spgt2(struct ata_device *dev, u8 *buf,
        return CDL_T2_SUB_MPAGE_LEN;
 }
 
+/*
+ * Simulate MODE SENSE control mode page, sub-page f2h
+ * (ATA feature control mode page).
+ */
+static unsigned int ata_msense_control_ata_feature(struct ata_device *dev,
+                                                  u8 *buf)
+{
+       /* PS=0, SPF=1 */
+       buf[0] = CONTROL_MPAGE | (1 << 6);
+       buf[1] = ATA_FEATURE_SUB_MPAGE;
+
+       /*
+        * The first four bytes of ATA Feature Control mode page are a header.
+        * The PAGE LENGTH field is the size of the page excluding the header.
+        */
+       put_unaligned_be16(ATA_FEATURE_SUB_MPAGE_LEN - 4, &buf[2]);
+
+       if (dev->flags & ATA_DFLAG_CDL)
+               buf[4] = 0x02; /* Support T2A and T2B pages */
+       else
+               buf[4] = 0;
+
+       return ATA_FEATURE_SUB_MPAGE_LEN;
+}
+
 /**
  *     ata_msense_control - Simulate MODE SENSE control mode page
  *     @dev: ATA device of interest
@@ -2309,10 +2336,13 @@ static unsigned int ata_msense_control(struct ata_device *dev, u8 *buf,
        case CDL_T2A_SUB_MPAGE:
        case CDL_T2B_SUB_MPAGE:
                return ata_msense_control_spgt2(dev, buf, spg);
+       case ATA_FEATURE_SUB_MPAGE:
+               return ata_msense_control_ata_feature(dev, buf);
        case ALL_SUB_MPAGES:
                n = ata_msense_control_spg0(dev, buf, changeable);
                n += ata_msense_control_spgt2(dev, buf + n, CDL_T2A_SUB_MPAGE);
                n += ata_msense_control_spgt2(dev, buf + n, CDL_T2A_SUB_MPAGE);
+               n += ata_msense_control_ata_feature(dev, buf + n);
                return n;
        default:
                return 0;
@@ -2391,7 +2421,7 @@ static unsigned int ata_scsiop_mode_sense(struct ata_scsi_args *args, u8 *rbuf)
        spg = scsicmd[3];
 
        /*
-        * Supported subpages: all subpages and sub-pages 07h and 08h of
+        * Supported subpages: all subpages and sub-pages 07h, 08h and f2h of
         * the control page.
         */
        if (spg) {
@@ -2400,6 +2430,7 @@ static unsigned int ata_scsiop_mode_sense(struct ata_scsi_args *args, u8 *rbuf)
                        break;
                case CDL_T2A_SUB_MPAGE:
                case CDL_T2B_SUB_MPAGE:
+               case ATA_FEATURE_SUB_MPAGE:
                        if (dev->flags & ATA_DFLAG_CDL && pg == CONTROL_MPAGE)
                                break;
                        fallthrough;
@@ -3708,20 +3739,11 @@ static int ata_mselect_caching(struct ata_queued_cmd *qc,
        return 0;
 }
 
-/**
- *     ata_mselect_control - Simulate MODE SELECT for control page
- *     @qc: Storage for translated ATA taskfile
- *     @buf: input buffer
- *     @len: number of valid bytes in the input buffer
- *     @fp: out parameter for the failed field on error
- *
- *     Prepare a taskfile to modify caching information for the device.
- *
- *     LOCKING:
- *     None.
+/*
+ * Simulate MODE SELECT control mode page, sub-page 0.
  */
-static int ata_mselect_control(struct ata_queued_cmd *qc,
-                              const u8 *buf, int len, u16 *fp)
+static int ata_mselect_control_spg0(struct ata_queued_cmd *qc,
+                                   const u8 *buf, int len, u16 *fp)
 {
        struct ata_device *dev = qc->dev;
        u8 mpage[CONTROL_MPAGE_LEN];
@@ -3759,6 +3781,83 @@ static int ata_mselect_control(struct ata_queued_cmd *qc,
        return 0;
 }
 
+/*
+ * Translate MODE SELECT control mode page, sub-pages f2h (ATA feature mode
+ * page) into a SET FEATURES command.
+ */
+static unsigned int ata_mselect_control_ata_feature(struct ata_queued_cmd *qc,
+                                                   const u8 *buf, int len,
+                                                   u16 *fp)
+{
+       struct ata_device *dev = qc->dev;
+       struct ata_taskfile *tf = &qc->tf;
+       u8 cdl_action;
+
+       /*
+        * The first four bytes of ATA Feature Control mode page are a header,
+        * so offsets in mpage are off by 4 compared to buf.  Same for len.
+        */
+       if (len != ATA_FEATURE_SUB_MPAGE_LEN - 4) {
+               *fp = min(len, ATA_FEATURE_SUB_MPAGE_LEN - 4);
+               return -EINVAL;
+       }
+
+       /* Check cdl_ctrl */
+       switch (buf[0] & 0x03) {
+       case 0:
+               /* Disable CDL */
+               cdl_action = 0;
+               dev->flags &= ~ATA_DFLAG_CDL_ENABLED;
+               break;
+       case 0x02:
+               /* Enable CDL T2A/T2B: NCQ priority must be disabled */
+               if (dev->flags & ATA_DFLAG_NCQ_PRIO_ENABLED) {
+                       ata_dev_err(dev,
+                               "NCQ priority must be disabled to enable CDL\n");
+                       return -EINVAL;
+               }
+               cdl_action = 1;
+               dev->flags |= ATA_DFLAG_CDL_ENABLED;
+               break;
+       default:
+               *fp = 0;
+               return -EINVAL;
+       }
+
+       tf->flags |= ATA_TFLAG_DEVICE | ATA_TFLAG_ISADDR;
+       tf->protocol = ATA_PROT_NODATA;
+       tf->command = ATA_CMD_SET_FEATURES;
+       tf->feature = SETFEATURES_CDL;
+       tf->nsect = cdl_action;
+
+       return 1;
+}
+
+/**
+ *     ata_mselect_control - Simulate MODE SELECT for control page
+ *     @qc: Storage for translated ATA taskfile
+ *     @buf: input buffer
+ *     @len: number of valid bytes in the input buffer
+ *     @fp: out parameter for the failed field on error
+ *
+ *     Prepare a taskfile to modify caching information for the device.
+ *
+ *     LOCKING:
+ *     None.
+ */
+static int ata_mselect_control(struct ata_queued_cmd *qc, u8 spg,
+                              const u8 *buf, int len, u16 *fp)
+{
+       switch (spg) {
+       case 0:
+               return ata_mselect_control_spg0(qc, buf, len, fp);
+       case ATA_FEATURE_SUB_MPAGE:
+               return ata_mselect_control_ata_feature(qc, buf, len, fp);
+       default:
+               return -EINVAL;
+       }
+}
+
 /**
  *     ata_scsi_mode_select_xlat - Simulate MODE SELECT 6, 10 commands
  *     @qc: Storage for translated ATA taskfile
@@ -3776,7 +3875,7 @@ static unsigned int ata_scsi_mode_select_xlat(struct ata_queued_cmd *qc)
        const u8 *cdb = scmd->cmnd;
        u8 pg, spg;
        unsigned six_byte, pg_len, hdr_len, bd_len;
-       int len;
+       int len, ret;
        u16 fp = (u16)-1;
        u8 bp = 0xff;
        u8 buffer[64];
@@ -3861,13 +3960,29 @@ static unsigned int ata_scsi_mode_select_xlat(struct ata_queued_cmd *qc)
        }
 
        /*
-        * No mode subpages supported (yet) but asking for _all_
-        * subpages may be valid
+        * Supported subpages: all subpages and ATA feature sub-page f2h of
+        * the control page.
         */
-       if (spg && (spg != ALL_SUB_MPAGES)) {
-               fp = (p[0] & 0x40) ? 1 : 0;
-               fp += hdr_len + bd_len;
-               goto invalid_param;
+       if (spg) {
+               switch (spg) {
+               case ALL_SUB_MPAGES:
+                       /* All subpages is not supported for the control page */
+                       if (pg == CONTROL_MPAGE) {
+                               fp = (p[0] & 0x40) ? 1 : 0;
+                               fp += hdr_len + bd_len;
+                               goto invalid_param;
+                       }
+                       break;
+               case ATA_FEATURE_SUB_MPAGE:
+                       if (qc->dev->flags & ATA_DFLAG_CDL &&
+                           pg == CONTROL_MPAGE)
+                               break;
+                       fallthrough;
+               default:
+                       fp = (p[0] & 0x40) ? 1 : 0;
+                       fp += hdr_len + bd_len;
+                       goto invalid_param;
+               }
        }
        if (pg_len > len)
                goto invalid_param_len;
@@ -3880,14 +3995,16 @@ static unsigned int ata_scsi_mode_select_xlat(struct ata_queued_cmd *qc)
                }
                break;
        case CONTROL_MPAGE:
-               if (ata_mselect_control(qc, p, pg_len, &fp) < 0) {
+               ret = ata_mselect_control(qc, spg, p, pg_len, &fp);
+               if (ret < 0) {
                        fp += hdr_len + bd_len;
                        goto invalid_param;
-               } else {
-                       goto skip; /* No ATA command to send */
                }
+               if (!ret)
+                       goto skip; /* No ATA command to send */
                break;
-       default:                /* invalid page code */
+       default:
+               /* Invalid page code */
                fp = bd_len + hdr_len;
                goto invalid_param;
        }
index 1eda46b..2110847 100644 (file)
@@ -329,6 +329,7 @@ enum {
 
        /* Identify device log pages: */
        ATA_LOG_SUPPORTED_CAPABILITIES  = 0x03,
+       ATA_LOG_CURRENT_SETTINGS  = 0x04,
        ATA_LOG_SECURITY          = 0x06,
        ATA_LOG_SATA_SETTINGS     = 0x08,
        ATA_LOG_ZONED_INFORMATION = 0x09,
@@ -418,6 +419,8 @@ enum {
        SETFEATURES_SATA_ENABLE = 0x10, /* Enable use of SATA feature */
        SETFEATURES_SATA_DISABLE = 0x90, /* Disable use of SATA feature */
 
+       SETFEATURES_CDL         = 0x0d, /* Enable/disable cmd duration limits */
+
        /* SETFEATURE Sector counts for SATA features */
        SATA_FPDMA_OFFSET       = 0x01, /* FPDMA non-zero buffer offsets */
        SATA_FPDMA_AA           = 0x02, /* FPDMA Setup FIS Auto-Activate */
index e8a45f7..385ca23 100644 (file)
@@ -106,6 +106,7 @@ enum {
        ATA_DFLAG_INIT_MASK     = (1 << 20) - 1,
 
        ATA_DFLAG_NCQ_PRIO_ENABLED = (1 << 20), /* Priority cmds sent to dev */
+       ATA_DFLAG_CDL_ENABLED   = (1 << 21), /* cmd duration limits is enabled */
        ATA_DFLAG_DETACH        = (1 << 24),
        ATA_DFLAG_DETACHED      = (1 << 25),
        ATA_DFLAG_DA            = (1 << 26), /* device supports Device Attention */