scsi: ata: libata-scsi: Add support for CDL pages mode sense
authorDamien Le Moal <dlemoal@kernel.org>
Thu, 11 May 2023 01:13:49 +0000 (03:13 +0200)
committerMartin K. Petersen <martin.petersen@oracle.com>
Mon, 22 May 2023 21:05:20 +0000 (17:05 -0400)
Modify ata_scsiop_mode_sense() and ata_msense_control() to support mode
sense access to the T2A and T2B sub-pages of the control mode page.
ata_msense_control() is modified to support sub-pages. The T2A sub-page is
generated using the read descriptors of the command duration limits log
page 18h. The T2B sub-page is generated using the write descriptors of the
same log page. With the addition of these sub-pages, getting all sub-pages
of the control mode page is also supported by increasing the value of
ATA_SCSI_RBUF_SIZE from 576B up to 2048B to ensure that all sub-pages fit
in the fill buffer.

Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Reviewed-by: Christoph Hellwig <hch@lst.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-17-nks@flawful.org
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/ata/libata-scsi.c

index 4245242..4a4c640 100644 (file)
@@ -37,7 +37,7 @@
 #include "libata.h"
 #include "libata-transport.h"
 
-#define ATA_SCSI_RBUF_SIZE     576
+#define ATA_SCSI_RBUF_SIZE     2048
 
 static DEFINE_SPINLOCK(ata_scsi_rbuf_lock);
 static u8 ata_scsi_rbuf[ATA_SCSI_RBUF_SIZE];
@@ -55,6 +55,9 @@ static struct ata_device *__ata_scsi_find_dev(struct ata_port *ap,
 #define CONTROL_MPAGE_LEN              12
 #define ALL_MPAGES                     0x3f
 #define ALL_SUB_MPAGES                 0xff
+#define CDL_T2A_SUB_MPAGE              0x07
+#define CDL_T2B_SUB_MPAGE              0x08
+#define CDL_T2_SUB_MPAGE_LEN           232
 
 static const u8 def_rw_recovery_mpage[RW_RECOVERY_MPAGE_LEN] = {
        RW_RECOVERY_MPAGE,
@@ -2196,10 +2199,98 @@ static unsigned int ata_msense_caching(u16 *id, u8 *buf, bool changeable)
        return sizeof(def_cache_mpage);
 }
 
+/*
+ * Simulate MODE SENSE control mode page, sub-page 0.
+ */
+static unsigned int ata_msense_control_spg0(struct ata_device *dev, u8 *buf,
+                                           bool changeable)
+{
+       modecpy(buf, def_control_mpage,
+               sizeof(def_control_mpage), changeable);
+       if (changeable) {
+               /* ata_mselect_control() */
+               buf[2] |= (1 << 2);
+       } else {
+               bool d_sense = (dev->flags & ATA_DFLAG_D_SENSE);
+
+               /* descriptor format sense data */
+               buf[2] |= (d_sense << 2);
+       }
+
+       return sizeof(def_control_mpage);
+}
+
+/*
+ * Translate an ATA duration limit in microseconds to a SCSI duration limit
+ * using the t2cdlunits 0xa (10ms). Since the SCSI duration limits are 2-bytes
+ * only, take care of overflows.
+ */
+static inline u16 ata_xlat_cdl_limit(u8 *buf)
+{
+       u32 limit = get_unaligned_le32(buf);
+
+       return min_t(u32, limit / 10000, 65535);
+}
+
+/*
+ * Simulate MODE SENSE control mode page, sub-pages 07h and 08h
+ * (command duration limits T2A and T2B mode pages).
+ */
+static unsigned int ata_msense_control_spgt2(struct ata_device *dev, u8 *buf,
+                                            u8 spg)
+{
+       u8 *b, *cdl = dev->cdl, *desc;
+       u32 policy;
+       int i;
+
+       /*
+        * Fill the subpage. The first four bytes of the T2A/T2B mode pages
+        * are a header. The PAGE LENGTH field is the size of the page
+        * excluding the header.
+        */
+       buf[0] = CONTROL_MPAGE;
+       buf[1] = spg;
+       put_unaligned_be16(CDL_T2_SUB_MPAGE_LEN - 4, &buf[2]);
+       if (spg == CDL_T2A_SUB_MPAGE) {
+               /*
+                * Read descriptors map to the T2A page:
+                * set perf_vs_duration_guidleine.
+                */
+               buf[7] = (cdl[0] & 0x03) << 4;
+               desc = cdl + 64;
+       } else {
+               /* Write descriptors map to the T2B page */
+               desc = cdl + 288;
+       }
+
+       /* Fill the T2 page descriptors */
+       b = &buf[8];
+       policy = get_unaligned_le32(&cdl[0]);
+       for (i = 0; i < 7; i++, b += 32, desc += 32) {
+               /* t2cdlunits: fixed to 10ms */
+               b[0] = 0x0a;
+
+               /* Max inactive time and its policy */
+               put_unaligned_be16(ata_xlat_cdl_limit(&desc[8]), &b[2]);
+               b[6] = ((policy >> 8) & 0x0f) << 4;
+
+               /* Max active time and its policy */
+               put_unaligned_be16(ata_xlat_cdl_limit(&desc[4]), &b[4]);
+               b[6] |= (policy >> 4) & 0x0f;
+
+               /* Command duration guideline and its policy */
+               put_unaligned_be16(ata_xlat_cdl_limit(&desc[16]), &b[10]);
+               b[14] = policy & 0x0f;
+       }
+
+       return CDL_T2_SUB_MPAGE_LEN;
+}
+
 /**
  *     ata_msense_control - Simulate MODE SENSE control mode page
  *     @dev: ATA device of interest
  *     @buf: output buffer
+ *     @spg: sub-page code
  *     @changeable: whether changeable parameters are requested
  *
  *     Generate a generic MODE SENSE control mode page.
@@ -2208,17 +2299,24 @@ static unsigned int ata_msense_caching(u16 *id, u8 *buf, bool changeable)
  *     None.
  */
 static unsigned int ata_msense_control(struct ata_device *dev, u8 *buf,
-                                       bool changeable)
+                                      u8 spg, bool changeable)
 {
-       modecpy(buf, def_control_mpage, sizeof(def_control_mpage), changeable);
-       if (changeable) {
-               buf[2] |= (1 << 2);     /* ata_mselect_control() */
-       } else {
-               bool d_sense = (dev->flags & ATA_DFLAG_D_SENSE);
-
-               buf[2] |= (d_sense << 2);       /* descriptor format sense data */
+       unsigned int n;
+
+       switch (spg) {
+       case 0:
+               return ata_msense_control_spg0(dev, buf, changeable);
+       case CDL_T2A_SUB_MPAGE:
+       case CDL_T2B_SUB_MPAGE:
+               return ata_msense_control_spgt2(dev, buf, spg);
+       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);
+               return n;
+       default:
+               return 0;
        }
-       return sizeof(def_control_mpage);
 }
 
 /**
@@ -2291,13 +2389,24 @@ static unsigned int ata_scsiop_mode_sense(struct ata_scsi_args *args, u8 *rbuf)
 
        pg = scsicmd[2] & 0x3f;
        spg = scsicmd[3];
+
        /*
-        * No mode subpages supported (yet) but asking for _all_
-        * subpages may be valid
+        * Supported subpages: all subpages and sub-pages 07h and 08h of
+        * the control page.
         */
-       if (spg && (spg != ALL_SUB_MPAGES)) {
-               fp = 3;
-               goto invalid_fld;
+       if (spg) {
+               switch (spg) {
+               case ALL_SUB_MPAGES:
+                       break;
+               case CDL_T2A_SUB_MPAGE:
+               case CDL_T2B_SUB_MPAGE:
+                       if (dev->flags & ATA_DFLAG_CDL && pg == CONTROL_MPAGE)
+                               break;
+                       fallthrough;
+               default:
+                       fp = 3;
+                       goto invalid_fld;
+               }
        }
 
        switch(pg) {
@@ -2310,13 +2419,13 @@ static unsigned int ata_scsiop_mode_sense(struct ata_scsi_args *args, u8 *rbuf)
                break;
 
        case CONTROL_MPAGE:
-               p += ata_msense_control(args->dev, p, page_control == 1);
+               p += ata_msense_control(args->dev, p, spg, page_control == 1);
                break;
 
        case ALL_MPAGES:
                p += ata_msense_rw_recovery(p, page_control == 1);
                p += ata_msense_caching(args->id, p, page_control == 1);
-               p += ata_msense_control(args->dev, p, page_control == 1);
+               p += ata_msense_control(args->dev, p, spg, page_control == 1);
                break;
 
        default:                /* invalid page code */
@@ -2335,10 +2444,7 @@ static unsigned int ata_scsiop_mode_sense(struct ata_scsi_args *args, u8 *rbuf)
                        memcpy(rbuf + 4, sat_blk_desc, sizeof(sat_blk_desc));
                }
        } else {
-               unsigned int output_len = p - rbuf - 2;
-
-               rbuf[0] = output_len >> 8;
-               rbuf[1] = output_len;
+               put_unaligned_be16(p - rbuf - 2, &rbuf[0]);
                rbuf[3] |= dpofua;
                if (ebd) {
                        rbuf[7] = sizeof(sat_blk_desc);
@@ -3637,7 +3743,7 @@ static int ata_mselect_control(struct ata_queued_cmd *qc,
        /*
         * Check that read-only bits are not modified.
         */
-       ata_msense_control(dev, mpage, false);
+       ata_msense_control_spg0(dev, mpage, false);
        for (i = 0; i < CONTROL_MPAGE_LEN - 2; i++) {
                if (i == 0)
                        continue;