Author: smh
Date: Fri Apr 26 16:17:04 2013
New Revision: 249939
URL: http://svnweb.freebsd.org/changeset/base/249939

Log:
  Added available delete methods discovery during device probe, including the
  maximum sizes for said methods, which are used when processing BIO_DELETE
  requests. This includes updating UNMAP support discovery to be based on
  SBC-3 T10/1799-D Revision 31 specification.
  
  Added ATA TRIM support to cam scsi devices via ATA Pass-Through(16)
  
  sys/cam/scsi/scsi_da.c:
          - Added ATA Data Set Management TRIM support via ATA Pass-Through(16)
            as a delete_method
  
          - Added four new probe states used to identity available methods and 
their
            limits for the processing of BIO_DELETE commands via both UNMAP and 
the
            new ATA TRIM commands.
  
          - Renamed Probe states to better indicate their use
  
          - Added delete method descriptions used when informing user of issues.
  
          - Added automatic calculation of the optimum delete mode based on 
which
            method presents the largest maximum request size as this is most 
likely
            to result in the best performance.
  
          - Added WRITE SAME max block limits
  
          - Updated UNMAP range generation to mirror that used by ATA TRIM, this
            optimises the generation of ranges and fixes a potential overflow
            issue in the count when combining multiple BIO_DELETE requests
  
          - Added output of warnings about short deletes. This should only ever
            be triggered on devices that fail to correctly advertise their 
supported
            delete modes / max sizes.
  
          - Fixed WS16 requests being incorrectly limited to 65535 in length.
  
  Reviewed by:  mav
  Approved by:  pjd (mentor)
  MFC after:    2 weeks

Modified:
  head/sys/cam/scsi/scsi_da.c

Modified: head/sys/cam/scsi/scsi_da.c
==============================================================================
--- head/sys/cam/scsi/scsi_da.c Fri Apr 26 16:11:07 2013        (r249938)
+++ head/sys/cam/scsi/scsi_da.c Fri Apr 26 16:17:04 2013        (r249939)
@@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/eventhandler.h>
 #include <sys/malloc.h>
 #include <sys/cons.h>
+#include <sys/endian.h>
 #include <geom/geom.h>
 #include <geom/geom_disk.h>
 #endif /* _KERNEL */
@@ -67,8 +68,11 @@ __FBSDID("$FreeBSD$");
 
 #ifdef _KERNEL
 typedef enum {
-       DA_STATE_PROBE,
-       DA_STATE_PROBE2,
+       DA_STATE_PROBE_RC,
+       DA_STATE_PROBE_RC16,
+       DA_STATE_PROBE_LBP,
+       DA_STATE_PROBE_BLK_LIMITS,
+       DA_STATE_PROBE_ATA,
        DA_STATE_NORMAL
 } da_state;
 
@@ -96,29 +100,46 @@ typedef enum {
 } da_quirks;
 
 typedef enum {
-       DA_CCB_PROBE            = 0x01,
-       DA_CCB_PROBE2           = 0x02,
-       DA_CCB_BUFFER_IO        = 0x03,
-       DA_CCB_WAITING          = 0x04,
-       DA_CCB_DUMP             = 0x05,
-       DA_CCB_DELETE           = 0x06,
-       DA_CCB_TUR              = 0x07,
+       DA_CCB_PROBE_RC         = 0x01,
+       DA_CCB_PROBE_RC16       = 0x02,
+       DA_CCB_PROBE_LBP        = 0x03,
+       DA_CCB_PROBE_BLK_LIMITS = 0x04,
+       DA_CCB_PROBE_ATA        = 0x05,
+       DA_CCB_BUFFER_IO        = 0x06,
+       DA_CCB_WAITING          = 0x07,
+       DA_CCB_DUMP             = 0x08,
+       DA_CCB_DELETE           = 0x0A,
+       DA_CCB_TUR              = 0x0B,
        DA_CCB_TYPE_MASK        = 0x0F,
        DA_CCB_RETRY_UA         = 0x10
 } da_ccb_state;
 
+/*
+ * Order here is important for method choice
+ *
+ * We prefer ATA_TRIM as tests run against a Sandforce 2281 SSD attached to
+ * LSI 2008 (mps) controller (FW: v12, Drv: v14) resulted 20% quicker deletes
+ * using ATA_TRIM than the corresponding UNMAP results for a real world mysql
+ * import taking 5mins.
+ *
+ */
 typedef enum {
        DA_DELETE_NONE,
        DA_DELETE_DISABLE,
-       DA_DELETE_ZERO,
-       DA_DELETE_WS10,
-       DA_DELETE_WS16,
+       DA_DELETE_ATA_TRIM,
        DA_DELETE_UNMAP,
-       DA_DELETE_MAX = DA_DELETE_UNMAP
+       DA_DELETE_WS16,
+       DA_DELETE_WS10,
+       DA_DELETE_ZERO,
+       DA_DELETE_MIN = DA_DELETE_UNMAP,
+       DA_DELETE_MAX = DA_DELETE_ZERO
 } da_delete_methods;
 
 static const char *da_delete_method_names[] =
-    { "NONE", "DISABLE", "ZERO", "WS10", "WS16", "UNMAP" };
+    { "NONE", "DISABLE", "UNMAP", "ATA_TRIM", "WS16", "WS10", "ZERO" };
+static const char *da_delete_method_desc[] =
+    { "NONE", "DISABLED", "UNMAP", "ATA TRIM", "WRITE SAME(16) with UNMAP",
+      "WRITE SAME(10) with UNMAP", "ZERO" };
 
 /* Offsets into our private area for storing information */
 #define ccb_state      ppriv_field0
@@ -134,7 +155,17 @@ struct disk_params {
        u_int     stripeoffset;
 };
 
-#define UNMAP_MAX_RANGES       512
+#define UNMAP_RANGE_MAX                0xffffffff
+#define UNMAP_HEAD_SIZE                8
+#define UNMAP_RANGE_SIZE       16
+#define UNMAP_MAX_RANGES       2048 /* Protocol Max is 4095 */
+#define UNMAP_BUF_SIZE         ((UNMAP_MAX_RANGES * UNMAP_RANGE_SIZE) + \
+                               UNMAP_HEAD_SIZE)
+
+#define WS10_MAX_BLKS          0xffff
+#define WS16_MAX_BLKS          0xffffffff
+#define ATA_TRIM_MAX_RANGES    ((UNMAP_BUF_SIZE / \
+       (ATA_DSM_RANGE_SIZE * ATA_DSM_BLK_SIZE)) * ATA_DSM_BLK_SIZE)
 
 struct da_softc {
        struct   bio_queue_head bio_queue;
@@ -150,11 +181,14 @@ struct da_softc {
        int      error_inject;
        int      ordered_tag_count;
        int      outstanding_cmds;
-       int      unmap_max_ranges;
-       int      unmap_max_lba;
+       int      trim_max_ranges;
        int      delete_running;
        int      tur;
-       da_delete_methods        delete_method;
+       int      delete_available;      /* Delete methods possibly available */
+       uint32_t                unmap_max_ranges;
+       uint32_t                unmap_max_lba;
+       uint64_t                ws_max_blks;
+       da_delete_methods       delete_method;
        struct   disk_params params;
        struct   disk *disk;
        union    ccb saved_ccb;
@@ -163,11 +197,18 @@ struct da_softc {
        struct sysctl_oid       *sysctl_tree;
        struct callout          sendordered_c;
        uint64_t wwpn;
-       uint8_t  unmap_buf[UNMAP_MAX_RANGES * 16 + 8];
+       uint8_t  unmap_buf[UNMAP_BUF_SIZE];
        struct scsi_read_capacity_data_long rcaplong;
        struct callout          mediapoll_c;
 };
 
+#define dadeleteflag(softc, delete_method, enable)                     \
+       if (enable) {                                                   \
+               softc->delete_available |= (1 << delete_method);        \
+       } else {                                                        \
+               softc->delete_available &= ~(1 << delete_method);       \
+       }
+
 struct da_quirk_entry {
        struct scsi_inquiry_pattern inq_pat;
        da_quirks quirks;
@@ -870,6 +911,9 @@ static      int             dacmdsizesysctl(SYSCTL_HANDL
 static int             dadeletemethodsysctl(SYSCTL_HANDLER_ARGS);
 static void            dadeletemethodset(struct da_softc *softc,
                                          da_delete_methods delete_method);
+static void            dadeletemethodchoose(struct da_softc *softc,
+                                            da_delete_methods default_method);
+
 static periph_ctor_t   daregister;
 static periph_dtor_t   dacleanup;
 static periph_start_t  dastart;
@@ -1581,6 +1625,26 @@ dadeletemethodset(struct da_softc *softc
                softc->disk->d_flags &= ~DISKFLAG_CANDELETE;
 }
 
+static void
+dadeletemethodchoose(struct da_softc *softc, da_delete_methods default_method)
+{
+       int i, delete_method;
+
+       delete_method = default_method;
+
+       /*
+        * Use the pre-defined order to choose the best
+        * performing delete.
+        */
+       for (i = DA_DELETE_MIN; i <= DA_DELETE_MAX; i++) {
+               if (softc->delete_available & (1 << i)) {
+                       dadeletemethodset(softc, i);
+                       return;
+               }
+       }
+       dadeletemethodset(softc, delete_method);
+}
+
 static int
 dadeletemethodsysctl(SYSCTL_HANDLER_ARGS)
 {
@@ -1634,14 +1698,16 @@ daregister(struct cam_periph *periph, vo
        }
 
        LIST_INIT(&softc->pending_ccbs);
-       softc->state = DA_STATE_PROBE;
+       softc->state = DA_STATE_PROBE_RC;
        bioq_init(&softc->bio_queue);
        bioq_init(&softc->delete_queue);
        bioq_init(&softc->delete_run_queue);
        if (SID_IS_REMOVABLE(&cgd->inq_data))
                softc->flags |= DA_FLAG_PACK_REMOVABLE;
        softc->unmap_max_ranges = UNMAP_MAX_RANGES;
-       softc->unmap_max_lba = 1024*1024*2;
+       softc->unmap_max_lba = UNMAP_RANGE_MAX;
+       softc->ws_max_blks = WS16_MAX_BLKS;
+       softc->trim_max_ranges = ATA_TRIM_MAX_RANGES;
        softc->sort_io_queue = -1;
 
        periph->softc = softc;
@@ -1718,7 +1784,7 @@ daregister(struct cam_periph *periph, vo
        /* Predict whether device may support READ CAPACITY(16). */
        if (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC3) {
                softc->flags |= DA_FLAG_CAN_RC16;
-               softc->state = DA_STATE_PROBE2;
+               softc->state = DA_STATE_PROBE_RC16;
        }
 
        /*
@@ -1820,6 +1886,7 @@ dastart(struct cam_periph *periph, union
 
        CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastart\n"));
 
+skipstate:
        switch (softc->state) {
        case DA_STATE_NORMAL:
        {
@@ -1844,13 +1911,36 @@ dastart(struct cam_periph *periph, union
                if (!softc->delete_running &&
                    (bp = bioq_first(&softc->delete_queue)) != NULL) {
                    uint64_t lba;
-                   u_int count;
+                   uint64_t count; /* forward compat with WS32 */
+
+                   /*
+                    * In each of the methods below, while its the caller's
+                    * responsibility to ensure the request will fit into a
+                    * single device request, we might have changed the delete
+                    * method due to the device incorrectly advertising either
+                    * its supported methods or limits.
+                    * 
+                    * To prevent this causing further issues we validate the
+                    * against the methods limits, and warn which would
+                    * otherwise be unnecessary.
+                    */
 
                    if (softc->delete_method == DA_DELETE_UNMAP) {
                        uint8_t *buf = softc->unmap_buf;
                        uint64_t lastlba = (uint64_t)-1;
-                       uint32_t lastcount = 0;
-                       int blocks = 0, off, ranges = 0;
+                       uint32_t lastcount = 0, c;
+                       uint64_t totalcount = 0;
+                       uint32_t off, ranges = 0;
+
+                       /*
+                        * Currently this doesn't take the UNMAP
+                        * Granularity and Granularity Alignment
+                        * fields into account.
+                        *
+                        * This could result in both unoptimal unmap
+                        * requests as as well as UNMAP calls unmapping
+                        * fewer LBA's than requested.
+                        */
 
                        softc->delete_running = 1;
                        bzero(softc->unmap_buf, sizeof(softc->unmap_buf));
@@ -1864,22 +1954,44 @@ dastart(struct cam_periph *periph, union
 
                                /* Try to extend the previous range. */
                                if (lba == lastlba) {
-                                       lastcount += count;
-                                       off = (ranges - 1) * 16 + 8;
+                                       c = min(count, softc->unmap_max_lba -
+                                               lastcount);
+                                       lastcount += c;
+                                       off = ((ranges - 1) * UNMAP_RANGE_SIZE) 
+
+                                             UNMAP_HEAD_SIZE;
                                        scsi_ulto4b(lastcount, &buf[off + 8]);
-                               } else if (count > 0) {
-                                       off = ranges * 16 + 8;
+                                       count -= c;
+                                       lba +=c;
+                                       totalcount += c;
+                               }
+
+                               while (count > 0) {
+                                       c = min(count, softc->unmap_max_lba);
+                                       if (totalcount + c > 
softc->unmap_max_lba ||
+                                           ranges >= softc->unmap_max_ranges) {
+                                               xpt_print(periph->path,
+                                                 "%s issuing short delete %ld 
> %ld"
+                                                 "|| %d >= %d",
+                                                 
da_delete_method_desc[softc->delete_method],
+                                                 totalcount + c, 
softc->unmap_max_lba,
+                                                 ranges, 
softc->unmap_max_ranges);
+                                               break;
+                                       }
+                                       off = (ranges * UNMAP_RANGE_SIZE) +
+                                             UNMAP_HEAD_SIZE;
                                        scsi_u64to8b(lba, &buf[off + 0]);
-                                       scsi_ulto4b(count, &buf[off + 8]);
-                                       lastcount = count;
+                                       scsi_ulto4b(c, &buf[off + 8]);
+                                       lba += c;
+                                       totalcount += c;
                                        ranges++;
+                                       count -= c;
+                                       lastcount = c;
                                }
-                               blocks += count;
-                               lastlba = lba + count;
+                               lastlba = lba;
                                bp1 = bioq_first(&softc->delete_queue);
                                if (bp1 == NULL ||
                                    ranges >= softc->unmap_max_ranges ||
-                                   blocks + bp1->bio_bcount /
+                                   totalcount + bp1->bio_bcount /
                                     softc->params.secsize > 
softc->unmap_max_lba)
                                        break;
                        } while (1);
@@ -1897,9 +2009,87 @@ dastart(struct cam_periph *periph, union
                                        da_default_timeout * 1000);
                        start_ccb->ccb_h.ccb_state = DA_CCB_DELETE;
                        goto out;
+                   } else if (softc->delete_method == DA_DELETE_ATA_TRIM) {
+                               uint8_t *buf = softc->unmap_buf;
+                               uint64_t lastlba = (uint64_t)-1;
+                               uint32_t lastcount = 0, c, requestcount;
+                               int ranges = 0, off, block_count;
+
+                               softc->delete_running = 1;
+                               bzero(softc->unmap_buf, 
sizeof(softc->unmap_buf));
+                               bp1 = bp;
+                               do {
+                                       bioq_remove(&softc->delete_queue, bp1);
+                                       if (bp1 != bp)
+                                               
bioq_insert_tail(&softc->delete_run_queue, bp1);
+                                       lba = bp1->bio_pblkno;
+                                       count = bp1->bio_bcount / 
softc->params.secsize;
+                                       requestcount = count;
+
+                                       /* Try to extend the previous range. */
+                                       if (lba == lastlba) {
+                                               c = min(count, 
ATA_DSM_RANGE_MAX - lastcount);
+                                               lastcount += c;
+                                               off = (ranges - 1) * 8;
+                                               buf[off + 6] = lastcount & 0xff;
+                                               buf[off + 7] = (lastcount >> 8) 
& 0xff;
+                                               count -= c;
+                                               lba += c;
+                                       }
+
+                                       while (count > 0) {
+                                               c = min(count, 
ATA_DSM_RANGE_MAX);
+                                               off = ranges * 8;
+
+                                               buf[off + 0] = lba & 0xff;
+                                               buf[off + 1] = (lba >> 8) & 
0xff;
+                                               buf[off + 2] = (lba >> 16) & 
0xff;
+                                               buf[off + 3] = (lba >> 24) & 
0xff;
+                                               buf[off + 4] = (lba >> 32) & 
0xff;
+                                               buf[off + 5] = (lba >> 40) & 
0xff;
+                                               buf[off + 6] = c & 0xff;
+                                               buf[off + 7] = (c >> 8) & 0xff;
+                                               lba += c;
+                                               ranges++;
+                                               count -= c;
+                                               lastcount = c;
+                                               if (count != 0 && ranges == 
softc->trim_max_ranges) {
+                                                       xpt_print(periph->path,
+                                                         "%s issuing short 
delete %ld > %ld",
+                                                         
da_delete_method_desc[softc->delete_method],
+                                                         requestcount,
+                                                         
(softc->trim_max_ranges - ranges) *
+                                                         ATA_DSM_RANGE_MAX);
+                                                       break;
+                                               }
+                                       }
+                                       lastlba = lba;
+                                       bp1 = bioq_first(&softc->delete_queue);
+                                       if (bp1 == NULL ||
+                                           bp1->bio_bcount / 
softc->params.secsize >
+                                           (softc->trim_max_ranges - ranges) *
+                                                   ATA_DSM_RANGE_MAX)
+                                               break;
+                               } while (1);
+
+                               block_count = (ranges + ATA_DSM_BLK_RANGES - 1) 
/
+                                             ATA_DSM_BLK_RANGES;
+                               scsi_ata_trim(&start_ccb->csio,
+                                               /*retries*/da_retry_count,
+                                               /*cbfcnp*/dadone,
+                                               /*tag_action*/MSG_SIMPLE_Q_TAG,
+                                               block_count,
+                                               /*data_ptr*/buf,
+                                               /*dxfer_len*/block_count * 
ATA_DSM_BLK_SIZE,
+                                               /*sense_len*/SSD_FULL_SIZE,
+                                               da_default_timeout * 1000);
+                               start_ccb->ccb_h.ccb_state = DA_CCB_DELETE;
+                               goto out;
                    } else if (softc->delete_method == DA_DELETE_ZERO ||
                               softc->delete_method == DA_DELETE_WS10 ||
                               softc->delete_method == DA_DELETE_WS16) {
+                       uint64_t ws_max_blks;
+                       ws_max_blks = softc->ws_max_blks / 
softc->params.secsize;
                        softc->delete_running = 1;
                        lba = bp->bio_pblkno;
                        count = 0;
@@ -1909,11 +2099,19 @@ dastart(struct cam_periph *periph, union
                                if (bp1 != bp)
                                        
bioq_insert_tail(&softc->delete_run_queue, bp1);
                                count += bp1->bio_bcount / 
softc->params.secsize;
+                               if (count > ws_max_blks) {
+                                       count = min(count, ws_max_blks);
+                                       xpt_print(periph->path,
+                                         "%s issuing short delete %ld > %ld",
+                                         
da_delete_method_desc[softc->delete_method],
+                                         count, ws_max_blks);
+                                       break;
+                               }
                                bp1 = bioq_first(&softc->delete_queue);
                                if (bp1 == NULL ||
                                    lba + count != bp1->bio_pblkno ||
                                    count + bp1->bio_bcount /
-                                    softc->params.secsize > 0xffff)
+                                    softc->params.secsize > ws_max_blks)
                                        break;
                        } while (1);
 
@@ -2037,7 +2235,7 @@ out:
                daschedule(periph);
                break;
        }
-       case DA_STATE_PROBE:
+       case DA_STATE_PROBE_RC:
        {
                struct scsi_read_capacity_data *rcap;
 
@@ -2056,11 +2254,11 @@ out:
                                   SSD_FULL_SIZE,
                                   /*timeout*/5000);
                start_ccb->ccb_h.ccb_bp = NULL;
-               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE;
+               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_RC;
                xpt_action(start_ccb);
                break;
        }
-       case DA_STATE_PROBE2:
+       case DA_STATE_PROBE_RC16:
        {
                struct scsi_read_capacity_data_long *rcaplong;
 
@@ -2083,8 +2281,110 @@ out:
                                      /*sense_len*/ SSD_FULL_SIZE,
                                      /*timeout*/ da_default_timeout * 1000);
                start_ccb->ccb_h.ccb_bp = NULL;
-               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE2;
-               xpt_action(start_ccb);  
+               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_RC16;
+               xpt_action(start_ccb);
+               break;
+       }
+       case DA_STATE_PROBE_LBP:
+       {
+               struct scsi_vpd_logical_block_prov *lbp;
+
+               if (!scsi_vpd_supported_page(periph, SVPD_LBP)) {
+                       /*
+                        * If we get here we don't support any SBC-3 delete
+                        * methods with UNMAP as the Logical Block Provisioning
+                        * VPD page support is required for devices which
+                        * support it according to T10/1799-D Revision 31
+                        * however older revisions of the spec don't mandate
+                        * this so we currently don't remove these methods
+                        * from the available set.
+                        */
+                       softc->state = DA_STATE_PROBE_BLK_LIMITS;
+                       goto skipstate;
+               }
+
+               lbp = (struct scsi_vpd_logical_block_prov *)
+                       malloc(sizeof(*lbp), M_SCSIDA, M_NOWAIT|M_ZERO);
+
+               if (lbp == NULL) {
+                       printf("dastart: Couldn't malloc lbp data\n");
+                       /* da_free_periph??? */
+                       break;
+               }
+
+               scsi_inquiry(&start_ccb->csio,
+                            /*retries*/da_retry_count,
+                            /*cbfcnp*/dadone,
+                            /*tag_action*/MSG_SIMPLE_Q_TAG,
+                            /*inq_buf*/(u_int8_t *)lbp,
+                            /*inq_len*/sizeof(*lbp),
+                            /*evpd*/TRUE,
+                            /*page_code*/SVPD_LBP,
+                            /*sense_len*/SSD_MIN_SIZE,
+                            /*timeout*/da_default_timeout * 1000);
+               start_ccb->ccb_h.ccb_bp = NULL;
+               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_LBP;
+               xpt_action(start_ccb);
+               break;
+       }
+       case DA_STATE_PROBE_BLK_LIMITS:
+       {
+               struct scsi_vpd_block_limits *block_limits;
+
+               if (!scsi_vpd_supported_page(periph, SVPD_BLOCK_LIMITS)) {
+                       /* Not supported skip to next probe */
+                       softc->state = DA_STATE_PROBE_ATA;
+                       goto skipstate;
+               }
+
+               block_limits = (struct scsi_vpd_block_limits *)
+                       malloc(sizeof(*block_limits), M_SCSIDA, 
M_NOWAIT|M_ZERO);
+
+               if (block_limits == NULL) {
+                       printf("dastart: Couldn't malloc block_limits data\n");
+                       /* da_free_periph??? */
+                       break;
+               }
+
+               scsi_inquiry(&start_ccb->csio,
+                            /*retries*/da_retry_count,
+                            /*cbfcnp*/dadone,
+                            /*tag_action*/MSG_SIMPLE_Q_TAG,
+                            /*inq_buf*/(u_int8_t *)block_limits,
+                            /*inq_len*/sizeof(*block_limits),
+                            /*evpd*/TRUE,
+                            /*page_code*/SVPD_BLOCK_LIMITS,
+                            /*sense_len*/SSD_MIN_SIZE,
+                            /*timeout*/da_default_timeout * 1000);
+               start_ccb->ccb_h.ccb_bp = NULL;
+               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_BLK_LIMITS;
+               xpt_action(start_ccb);
+               break;
+       }
+       case DA_STATE_PROBE_ATA:
+       {
+               struct ata_params *ata_params;
+
+               ata_params = (struct ata_params*)
+                       malloc(sizeof(*ata_params), M_SCSIDA, M_NOWAIT|M_ZERO);
+
+               if (ata_params == NULL) {
+                       printf("dastart: Couldn't malloc ata_params data\n");
+                       /* da_free_periph??? */
+                       break;
+               }
+
+               scsi_ata_identify(&start_ccb->csio,
+                                 /*retries*/da_retry_count,
+                                 /*cbfcnp*/dadone,
+                                  /*tag_action*/MSG_SIMPLE_Q_TAG,
+                                 /*data_ptr*/(u_int8_t *)ata_params,
+                                 /*dxfer_len*/sizeof(*ata_params),
+                                 /*sense_len*/SSD_FULL_SIZE,
+                                 /*timeout*/da_default_timeout * 1000);
+               start_ccb->ccb_h.ccb_bp = NULL;
+               start_ccb->ccb_h.ccb_state = DA_CCB_PROBE_ATA;
+               xpt_action(start_ccb);
                break;
        }
        }
@@ -2104,27 +2404,31 @@ cmd6workaround(union ccb *ccb)
        softc = (struct da_softc *)xpt_path_periph(ccb->ccb_h.path)->softc;
 
        if (ccb->ccb_h.ccb_state == DA_CCB_DELETE) {
-               if (softc->delete_method == DA_DELETE_UNMAP) {
-                       xpt_print(ccb->ccb_h.path, "UNMAP is not supported, "
-                           "switching to WRITE SAME(16) with UNMAP.\n");
-                       dadeletemethodset(softc, DA_DELETE_WS16);
-               } else if (softc->delete_method == DA_DELETE_WS16) {
-                       xpt_print(ccb->ccb_h.path,
-                           "WRITE SAME(16) with UNMAP is not supported, "
-                           "disabling BIO_DELETE.\n");
-                       dadeletemethodset(softc, DA_DELETE_DISABLE);
-               } else if (softc->delete_method == DA_DELETE_WS10) {
+               da_delete_methods old_method = softc->delete_method;
+
+               /*
+                * Typically there are two reasons for failure here
+                * 1. Delete method was detected as supported but isn't
+                * 2. Delete failed due to invalid params e.g. too big
+                *
+                * While we will attempt to choose an alternative delete method
+                * this may result in short deletes if the existing delete
+                * requests from geom are big for the new method choosen.
+                *
+                * This method assumes that the error which triggered this
+                * will not retry the io otherwise a panic will occur
+                */
+               dadeleteflag(softc, old_method, 0);
+               dadeletemethodchoose(softc, DA_DELETE_DISABLE);
+               if (softc->delete_method == DA_DELETE_DISABLE)
                        xpt_print(ccb->ccb_h.path,
-                           "WRITE SAME(10) with UNMAP is not supported, "
-                           "disabling BIO_DELETE.\n");
-                       dadeletemethodset(softc, DA_DELETE_DISABLE);
-               } else if (softc->delete_method == DA_DELETE_ZERO) {
+                                 "%s failed, disabling BIO_DELETE\n",
+                                 da_delete_method_desc[old_method]);
+               else
                        xpt_print(ccb->ccb_h.path,
-                           "WRITE SAME(10) is not supported, "
-                           "disabling BIO_DELETE.\n");
-                       dadeletemethodset(softc, DA_DELETE_DISABLE);
-               } else
-                       dadeletemethodset(softc, DA_DELETE_DISABLE);
+                                 "%s failed, switching to %s BIO_DELETE\n",
+                                 da_delete_method_desc[old_method],
+                                 da_delete_method_desc[softc->delete_method]);
 
                if (DA_SIO) {
                        while ((bp = bioq_takefirst(&softc->delete_run_queue))
@@ -2208,7 +2512,7 @@ dadone(struct cam_periph *periph, union 
                        error = daerror(done_ccb, CAM_RETRY_SELTO, sf);
                        if (error == ERESTART) {
                                /*
-                                * A retry was scheuled, so
+                                * A retry was scheduled, so
                                 * just return.
                                 */
                                return;
@@ -2304,16 +2608,18 @@ dadone(struct cam_periph *periph, union 
                        biodone(bp);
                break;
        }
-       case DA_CCB_PROBE:
-       case DA_CCB_PROBE2:
+       case DA_CCB_PROBE_RC:
+       case DA_CCB_PROBE_RC16:
        {
                struct     scsi_read_capacity_data *rdcap;
                struct     scsi_read_capacity_data_long *rcaplong;
                char       announce_buf[80];
+               int        lbp;
 
+               lbp = 0;
                rdcap = NULL;
                rcaplong = NULL;
-               if (state == DA_CCB_PROBE)
+               if (state == DA_CCB_PROBE_RC)
                        rdcap =(struct scsi_read_capacity_data *)csio->data_ptr;
                else
                        rcaplong = (struct scsi_read_capacity_data_long *)
@@ -2326,7 +2632,7 @@ dadone(struct cam_periph *periph, union 
                        u_int lbppbe;   /* LB per physical block exponent. */
                        u_int lalba;    /* Lowest aligned LBA. */
 
-                       if (state == DA_CCB_PROBE) {
+                       if (state == DA_CCB_PROBE_RC) {
                                block_size = scsi_4btoul(rdcap->length);
                                maxsector = scsi_4btoul(rdcap->addr);
                                lbppbe = 0;
@@ -2341,7 +2647,7 @@ dadone(struct cam_periph *periph, union 
                                 * with the short version of the command.
                                 */
                                if (maxsector == 0xffffffff) {
-                                       softc->state = DA_STATE_PROBE2;
+                                       softc->state = DA_STATE_PROBE_RC16;
                                        free(rdcap, M_SCSIDA);
                                        xpt_release_ccb(done_ccb);
                                        xpt_schedule(periph, priority);
@@ -2376,9 +2682,7 @@ dadone(struct cam_periph *periph, union 
                                 */
                                dasetgeom(periph, block_size, maxsector,
                                          rcaplong, sizeof(*rcaplong));
-                               if ((lalba & SRC16_LBPME_A)
-                                && softc->delete_method == DA_DELETE_NONE)
-                                       dadeletemethodset(softc, 
DA_DELETE_UNMAP);
+                               lbp = (lalba & SRC16_LBPME_A);
                                dp = &softc->params;
                                snprintf(announce_buf, sizeof(announce_buf),
                                        "%juMB (%ju %u byte sectors: %dH %dS/T "
@@ -2439,7 +2743,7 @@ dadone(struct cam_periph *periph, union 
                                 * If we tried READ CAPACITY(16) and failed,
                                 * fallback to READ CAPACITY(10).
                                 */
-                               if ((state == DA_CCB_PROBE2) &&
+                               if ((state == DA_CCB_PROBE_RC16) &&
                                    (softc->flags & DA_FLAG_CAN_RC16) &&
                                    (((csio->ccb_h.status & CAM_STATUS_MASK) ==
                                        CAM_REQ_INVALID) ||
@@ -2447,7 +2751,7 @@ dadone(struct cam_periph *periph, union 
                                      (error_code == SSD_CURRENT_ERROR) &&
                                      (sense_key == SSD_KEY_ILLEGAL_REQUEST)))) 
{
                                        softc->flags &= ~DA_FLAG_CAN_RC16;
-                                       softc->state = DA_STATE_PROBE;
+                                       softc->state = DA_STATE_PROBE_RC;
                                        free(rdcap, M_SCSIDA);
                                        xpt_release_ccb(done_ccb);
                                        xpt_schedule(periph, priority);
@@ -2505,11 +2809,201 @@ dadone(struct cam_periph *periph, union 
                                taskqueue_enqueue(taskqueue_thread,
                                                  &softc->sysctl_task);
                                xpt_announce_periph(periph, announce_buf);
+
+                               if (lbp) {
+                                       /*
+                                        * Based on older SBC-3 spec revisions
+                                        * any of the UNMAP methods "may" be
+                                        * available via LBP given this flag so
+                                        * we flag all of them as availble and
+                                        * then remove those which further
+                                        * probes confirm aren't available
+                                        * later.
+                                        *
+                                        * We could also check readcap(16) 
p_type
+                                        * flag to exclude one or more invalid
+                                        * write same (X) types here
+                                        */
+                                       dadeleteflag(softc, DA_DELETE_WS16, 1);
+                                       dadeleteflag(softc, DA_DELETE_WS10, 1);
+                                       dadeleteflag(softc, DA_DELETE_ZERO, 1);
+                                       dadeleteflag(softc, DA_DELETE_UNMAP, 1);
+
+                                       softc->state = DA_STATE_PROBE_LBP;
+                                       xpt_release_ccb(done_ccb);
+                                       xpt_schedule(periph, priority);
+                                       return;
+                               }
                        } else {
                                xpt_print(periph->path, "fatal error, "
                                    "could not acquire reference count\n");
                        }
                }
+
+               xpt_release_ccb(done_ccb);
+               softc->state = DA_STATE_PROBE_ATA;
+               xpt_schedule(periph, priority);
+               return;
+       }
+       case DA_CCB_PROBE_LBP:
+       {
+               struct scsi_vpd_logical_block_prov *lbp;
+
+               lbp = (struct scsi_vpd_logical_block_prov *)csio->data_ptr;
+
+               if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+                       /*
+                        * T10/1799-D Revision 31 states at least one of these
+                        * must be supported but we don't currently enforce 
this.
+                        */
+                       dadeleteflag(softc, DA_DELETE_WS16,
+                                    (lbp->flags & SVPD_LBP_WS16));
+                       dadeleteflag(softc, DA_DELETE_WS10,
+                                    (lbp->flags & SVPD_LBP_WS10));
+                       dadeleteflag(softc, DA_DELETE_ZERO,
+                                    (lbp->flags & SVPD_LBP_WS10));
+                       dadeleteflag(softc, DA_DELETE_UNMAP,
+                                    (lbp->flags & SVPD_LBP_UNMAP));
+
+                       if (lbp->flags & SVPD_LBP_UNMAP) {
+                               free(lbp, M_SCSIDA);
+                               softc->state = DA_STATE_PROBE_BLK_LIMITS;
+                               xpt_release_ccb(done_ccb);
+                               xpt_schedule(periph, priority);
+                               return;
+                       }
+               } else {
+                       int error;
+                       error = daerror(done_ccb, CAM_RETRY_SELTO,
+                                       SF_RETRY_UA|SF_NO_PRINT);
+                       if (error == ERESTART)
+                               return;
+                       else if (error != 0) {
+                               if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 
0) {
+                                       /* Don't wedge this device's queue */
+                                       cam_release_devq(done_ccb->ccb_h.path,
+                                                        /*relsim_flags*/0,
+                                                        /*reduction*/0,
+                                                        /*timeout*/0,
+                                                        /*getcount_only*/0);
+                               }
+
+                               /*
+                                * Failure indicates we don't support any SBC-3
+                                * delete methods with UNMAP
+                                */
+                       }
+               }
+
+               free(lbp, M_SCSIDA);
+               xpt_release_ccb(done_ccb);
+               softc->state = DA_STATE_PROBE_ATA;
+               xpt_schedule(periph, priority);
+               return;
+       }
+       case DA_CCB_PROBE_BLK_LIMITS:
+       {
+               struct scsi_vpd_block_limits *block_limits;
+
+               block_limits = (struct scsi_vpd_block_limits *)csio->data_ptr;
+
+               if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+                       uint32_t max_unmap_lba_cnt = scsi_4btoul(
+                               block_limits->max_unmap_lba_cnt);
+                       uint32_t max_unmap_blk_cnt = scsi_4btoul(
+                               block_limits->max_unmap_blk_cnt);
+                       uint64_t ws_max_blks = scsi_8btou64(
+                               block_limits->max_write_same_length);
+                       /*
+                        * We should already support UNMAP but we check lba
+                        * and block count to be sure
+                        */
+                       if (max_unmap_lba_cnt != 0x00L &&
+                           max_unmap_blk_cnt != 0x00L) {
+                               softc->unmap_max_lba = max_unmap_lba_cnt;
+                               softc->unmap_max_ranges = min(max_unmap_blk_cnt,
+                                       UNMAP_MAX_RANGES);
+                       } else {
+                               /*
+                                * Unexpected UNMAP limits which means the
+                                * device doesn't actually support UNMAP
+                                */
+                               dadeleteflag(softc, DA_DELETE_UNMAP, 0);
+                       }
+
+                       if (ws_max_blks != 0x00L)
+                               softc->ws_max_blks = ws_max_blks;
+               } else {
+                       int error;
+                       error = daerror(done_ccb, CAM_RETRY_SELTO,
+                                       SF_RETRY_UA|SF_NO_PRINT);
+                       if (error == ERESTART)
+                               return;
+                       else if (error != 0) {
+                               if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 
0) {
+                                       /* Don't wedge this device's queue */
+                                       cam_release_devq(done_ccb->ccb_h.path,
+                                                        /*relsim_flags*/0,
+                                                        /*reduction*/0,
+                                                        /*timeout*/0,
+                                                        /*getcount_only*/0);
+                               }
+
+                               /*
+                                * Failure here doesn't mean UNMAP is not
+                                * supported as this is an optional page.
+                                */
+                               softc->unmap_max_lba = 1;
+                               softc->unmap_max_ranges = 1;
+                       }
+               }
+
+               free(block_limits, M_SCSIDA);
+               xpt_release_ccb(done_ccb);
+               softc->state = DA_STATE_PROBE_ATA;
+               xpt_schedule(periph, priority);
+               return;
+       }
+       case DA_CCB_PROBE_ATA:
+       {
+               int i;
+               struct ata_params *ata_params;
+               int16_t *ptr;
+
+               ata_params = (struct ata_params *)csio->data_ptr;
+               ptr = (uint16_t *)ata_params;
+
+               if ((csio->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
+                       for (i = 0; i < sizeof(*ata_params) / 2; i++)
+                               ptr[i] = le16toh(ptr[i]);
+                       if (ata_params->support_dsm & ATA_SUPPORT_DSM_TRIM) {
+                               dadeleteflag(softc, DA_DELETE_ATA_TRIM, 1);
+                               if (ata_params->max_dsm_blocks != 0)
+                                       softc->trim_max_ranges = min(
+                                         softc->trim_max_ranges,
+                                         ata_params->max_dsm_blocks *
+                                         ATA_DSM_BLK_RANGES);
+                       }
+               } else {
+                       int error;
+                       error = daerror(done_ccb, CAM_RETRY_SELTO,
+                                       SF_RETRY_UA|SF_QUIET_IR);
+                       if (error == ERESTART)
+                               return;
+                       else if (error != 0) {
+                               if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 
0) {
+                                       /* Don't wedge this device's queue */
+                                       cam_release_devq(done_ccb->ccb_h.path,
+                                                        /*relsim_flags*/0,
+                                                        /*reduction*/0,
+                                                        /*timeout*/0,
+                                                        /*getcount_only*/0);
+                               }
+                       }
+               }
+
+               free(ata_params, M_SCSIDA);
+               dadeletemethodchoose(softc, DA_DELETE_NONE);
                /*
                 * Since our peripheral may be invalidated by an error
                 * above or an external event, we must release our CCB
@@ -2580,9 +3074,9 @@ dareprobe(struct cam_periph *periph)
            ("dareprobe: cam_periph_acquire failed"));
 
        if (softc->flags & DA_FLAG_CAN_RC16)
-               softc->state = DA_STATE_PROBE2;
+               softc->state = DA_STATE_PROBE_RC16;
        else
-               softc->state = DA_STATE_PROBE;
+               softc->state = DA_STATE_PROBE_RC;
 
        xpt_schedule(periph, CAM_PRIORITY_DEV);
 }
@@ -2805,10 +3299,6 @@ dasetgeom(struct cam_periph *periph, uin
        softc->disk->d_fwheads = softc->params.heads;
        softc->disk->d_devstat->block_size = softc->params.secsize;
        softc->disk->d_devstat->flags &= ~DEVSTAT_BS_UNAVAILABLE;
-       if (softc->delete_method > DA_DELETE_DISABLE)
-               softc->disk->d_flags |= DISKFLAG_CANDELETE;
-       else
-               softc->disk->d_flags &= ~DISKFLAG_CANDELETE;
 
        error = disk_resize(softc->disk, M_NOWAIT);
        if (error != 0)
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to