Author: smh
Date: Fri Jun  7 12:24:15 2013
New Revision: 251491
URL: http://svnweb.freebsd.org/changeset/base/251491

Log:
  Enhanced BIO_DELETE support for CAM SCSI to add ATA_TRIM support.
  
  Disable CAM BIO queue sorting for non-rotating media by default.
  
  MFC r245253 Use sysctl tunable values for scsi_da & retry_count (stable/8 
only)
  MFC r249939 Added available delete methods discovery during device probe
  MFC r249941 Automatically disable BIO queue sorting for non-rotating media
  MFC r250033 Correct comment typo's
  MFC r250179 Update probe flow so that devices with lbp can also disable 
disksort
  MFC r250181 Check for ATA Information VPD before querying for ATA
  MFC r250183 Enable CAM SCSI to choice ATA TRIM during autodetection
  MFC r250967 Enforce validation on the selected delete method via sysctl

Modified:
  stable/8/sys/cam/ata/ata_da.c
  stable/8/sys/cam/scsi/scsi_all.h
  stable/8/sys/cam/scsi/scsi_da.c
Directory Properties:
  stable/8/sys/   (props changed)
  stable/8/sys/cam/   (props changed)

Modified: stable/8/sys/cam/ata/ata_da.c
==============================================================================
--- stable/8/sys/cam/ata/ata_da.c       Fri Jun  7 10:27:50 2013        
(r251490)
+++ stable/8/sys/cam/ata/ata_da.c       Fri Jun  7 12:24:15 2013        
(r251491)
@@ -1102,7 +1102,11 @@ adaregister(struct cam_periph *periph, v
        snprintf(announce_buf, sizeof(announce_buf),
            "kern.cam.ada.%d.write_cache", periph->unit_number);
        TUNABLE_INT_FETCH(announce_buf, &softc->write_cache);
-       softc->sort_io_queue = -1;
+       /* Disable queue sorting for non-rotational media by default. */
+       if (cgd->ident_data.media_rotation_rate == 1)
+               softc->sort_io_queue = 0;
+       else
+               softc->sort_io_queue = -1;
        adagetparams(periph, cgd);
        softc->disk = disk_alloc();
        softc->disk->d_devstat = devstat_new_entry(periph->periph_name,

Modified: stable/8/sys/cam/scsi/scsi_all.h
==============================================================================
--- stable/8/sys/cam/scsi/scsi_all.h    Fri Jun  7 10:27:50 2013        
(r251490)
+++ stable/8/sys/cam/scsi/scsi_all.h    Fri Jun  7 12:24:15 2013        
(r251491)
@@ -891,6 +891,36 @@ struct scsi_vpd_unit_serial_number
 };
 
 /*
+ * ATA Information VPD Page based on
+ * T10/2126-D Revision 04
+ */
+#define SVPD_ATA_INFORMATION           0x89
+
+/*
+ * Block Device Characteristics VPD Page based on
+ * T10/1799-D Revision 31
+ */
+struct scsi_vpd_block_characteristics
+{
+       u_int8_t device;
+       u_int8_t page_code;
+#define SVPD_BDC                       0xB1
+       u_int8_t page_length[2];
+       u_int8_t medium_rotation_rate[2];
+#define SVPD_BDC_RATE_NOT_REPORTED     0x00
+#define SVPD_BDC_RATE_NONE_ROTATING    0x01
+       u_int8_t reserved1;
+       u_int8_t nominal_form_factor;
+#define SVPD_BDC_FORM_NOT_REPORTED     0x00
+#define SVPD_BDC_FORM_5_25INCH         0x01
+#define SVPD_BDC_FORM_3_5INCH          0x02
+#define SVPD_BDC_FORM_2_5INCH          0x03
+#define SVPD_BDC_FORM_1_5INCH          0x04
+#define SVPD_BDC_FORM_LESSTHAN_1_5INCH 0x05
+       u_int8_t reserved2[56];
+};
+
+/*
  * Logical Block Provisioning VPD Page based on
  * T10/1799-D Revision 31
  */

Modified: stable/8/sys/cam/scsi/scsi_da.c
==============================================================================
--- stable/8/sys/cam/scsi/scsi_da.c     Fri Jun  7 10:27:50 2013        
(r251490)
+++ stable/8/sys/cam/scsi/scsi_da.c     Fri Jun  7 12:24:15 2013        
(r251491)
@@ -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_disk.h>
 #endif /* _KERNEL */
 
@@ -66,8 +67,12 @@ __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_BDC,
+       DA_STATE_PROBE_ATA,
        DA_STATE_NORMAL
 } da_state;
 
@@ -93,28 +98,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_PROBE_RC         = 0x01,
+       DA_CCB_PROBE_RC16       = 0x02,
+       DA_CCB_PROBE_LBP        = 0x03,
+       DA_CCB_PROBE_BLK_LIMITS = 0x04,
+       DA_CCB_PROBE_BDC        = 0x05,
+       DA_CCB_PROBE_ATA        = 0x06,
+       DA_CCB_BUFFER_IO        = 0x07,
+       DA_CCB_WAITING          = 0x08,
+       DA_CCB_DUMP             = 0x0A,
+       DA_CCB_DELETE           = 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_ATA_TRIM,
+       DA_DELETE_MAX = DA_DELETE_ZERO
 } da_delete_methods;
 
 static const char *da_delete_method_names[] =
-    { "NONE", "DISABLE", "ZERO", "WS10", "WS16", "UNMAP" };
+    { "NONE", "DISABLE", "ATA_TRIM", "UNMAP", "WS16", "WS10", "ZERO" };
+static const char *da_delete_method_desc[] =
+    { "NONE", "DISABLED", "ATA TRIM", "UNMAP", "WRITE SAME(16) with UNMAP",
+      "WRITE SAME(10) with UNMAP", "ZERO" };
 
 /* Offsets into our private area for storing information */
 #define ccb_state      ppriv_field0
@@ -130,7 +153,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;
@@ -146,10 +179,13 @@ 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;
-       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;
@@ -158,9 +194,16 @@ 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];
 };
 
+#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;
@@ -859,6 +902,10 @@ 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 void            daprobedone(struct cam_periph *periph, union ccb *ccb);
+
 static periph_ctor_t   daregister;
 static periph_dtor_t   dacleanup;
 static periph_start_t  dastart;
@@ -980,10 +1027,6 @@ daopen(struct disk *dp)
                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;
 
                if ((softc->flags & DA_FLAG_PACK_REMOVABLE) != 0 &&
                    (softc->quirks & DA_Q_NO_PREVENT) == 0)
@@ -1512,6 +1555,82 @@ dadeletemethodset(struct da_softc *softc
                softc->disk->d_flags &= ~DISKFLAG_CANDELETE;
 }
 
+static void
+daprobedone(struct cam_periph *periph, union ccb *ccb)
+{
+       struct da_softc *softc;
+
+       softc = (struct da_softc *)periph->softc;
+
+       dadeletemethodchoose(softc, DA_DELETE_NONE);
+
+       if (bootverbose) {
+               char buf[80];
+               int i, sep;
+
+               snprintf(buf, sizeof(buf), "Delete methods: <");
+               sep = 0;
+               for (i = DA_DELETE_MIN; i <= DA_DELETE_MAX; i++) {
+                       if (softc->delete_available & (1 << i)) {
+                               if (sep) {
+                                       strlcat(buf, ",", sizeof(buf));
+                               } else {
+                                   sep = 1;
+                               }
+                               strlcat(buf, da_delete_method_names[i],
+                                   sizeof(buf));
+                               if (i == softc->delete_method) {
+                                       strlcat(buf, "(*)", sizeof(buf));
+                               }
+                       }
+               }
+               if (sep == 0) {
+                       if (softc->delete_method == DA_DELETE_NONE)
+                               strlcat(buf, "NONE(*)", sizeof(buf));
+                       else
+                               strlcat(buf, "DISABLED(*)", sizeof(buf));
+               }
+               strlcat(buf, ">", sizeof(buf));
+               printf("%s%d: %s\n", periph->periph_name,
+                   periph->unit_number, buf);
+       }
+
+       /*
+        * Since our peripheral may be invalidated by an error
+        * above or an external event, we must release our CCB
+        * before releasing the probe lock on the peripheral.
+        * The peripheral will only go away once the last lock
+        * is removed, and we need it around for the CCB release
+        * operation.
+        */
+       xpt_release_ccb(ccb);
+       softc->state = DA_STATE_NORMAL;
+       cam_periph_unhold(periph);
+
+       /* May have more work to do, so ensure we stay scheduled. */
+       daschedule(periph);
+}
+
+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)
 {
@@ -1532,7 +1651,8 @@ dadeletemethodsysctl(SYSCTL_HANDLER_ARGS
        if (error != 0 || req->newptr == NULL)
                return (error);
        for (i = 0; i <= DA_DELETE_MAX; i++) {
-               if (strcmp(buf, da_delete_method_names[i]) != 0)
+               if (!(softc->delete_available & (1 << i)) ||
+                   strcmp(buf, da_delete_method_names[i]) != 0)
                        continue;
                dadeletemethodset(softc, i);
                return (0);
@@ -1570,14 +1690,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;
@@ -1665,7 +1787,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;
        }
 
        /*
@@ -1713,6 +1835,7 @@ dastart(struct cam_periph *periph, union
 
        CAM_DEBUG(periph->path, CAM_DEBUG_TRACE, ("dastart\n"));
 
+skipstate:
        switch (softc->state) {
        case DA_STATE_NORMAL:
        {
@@ -1737,13 +1860,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));
@@ -1757,22 +1903,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);
@@ -1790,9 +1958,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;
@@ -1802,11 +2048,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);
 
@@ -1910,7 +2164,7 @@ out:
                daschedule(periph);
                break;
        }
-       case DA_STATE_PROBE:
+       case DA_STATE_PROBE_RC:
        {
                struct scsi_read_capacity_data *rcap;
 
@@ -1929,11 +2183,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;
 
@@ -1955,8 +2209,148 @@ 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_BDC;
+                       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_BDC:
+       {
+               struct scsi_vpd_block_characteristics *bdc;
+
+               if (!scsi_vpd_supported_page(periph, SVPD_BDC)) {
+                       softc->state = DA_STATE_PROBE_ATA;
+                       goto skipstate;
+               }
+
+               bdc = (struct scsi_vpd_block_characteristics *)
+                       malloc(sizeof(*bdc), M_SCSIDA, M_NOWAIT|M_ZERO);
+
+               if (bdc == NULL) {
+                       printf("dastart: Couldn't malloc bdc 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 *)bdc,
+                            /*inq_len*/sizeof(*bdc),
+                            /*evpd*/TRUE,
+                            /*page_code*/SVPD_BDC,
+                            /*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_BDC;
+               xpt_action(start_ccb);
+               break;
+       }
+       case DA_STATE_PROBE_ATA:
+       {
+               struct ata_params *ata_params;
+
+               if (!scsi_vpd_supported_page(periph, SVPD_ATA_INFORMATION)) {
+                       daprobedone(periph, start_ccb);
+                       break;
+               }
+
+               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;
        }
        }
@@ -1976,27 +2370,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))
@@ -2078,7 +2476,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;
@@ -2175,16 +2573,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 (softc->state == DA_STATE_PROBE)
+               if (softc->state == DA_STATE_PROBE_RC)
                        rdcap =(struct scsi_read_capacity_data *)csio->data_ptr;
                else
                        rcaplong = (struct scsi_read_capacity_data_long *)
@@ -2197,7 +2597,7 @@ dadone(struct cam_periph *periph, union 
                        u_int lbppbe;   /* LB per physical block exponent. */
                        u_int lalba;    /* Lowest aligned LBA. */
 
-                       if (softc->state == DA_STATE_PROBE) {
+                       if (softc->state == DA_STATE_PROBE_RC) {
                                block_size = scsi_4btoul(rdcap->length);
                                maxsector = scsi_4btoul(rdcap->addr);
                                lbppbe = 0;
@@ -2212,9 +2612,9 @@ dadone(struct cam_periph *periph, union 
                                 * with the short version of the command.
                                 */
                                if (maxsector == 0xffffffff) {
-                                       softc->state = DA_STATE_PROBE2;
                                        free(rdcap, M_SCSIDA);
                                        xpt_release_ccb(done_ccb);
+                                       softc->state = DA_STATE_PROBE_RC16;
                                        xpt_schedule(periph, priority);
                                        return;
                                }
@@ -2242,9 +2642,7 @@ dadone(struct cam_periph *periph, union 
                        } else {
                                dasetgeom(periph, block_size, maxsector,
                                    lbppbe, lalba & SRC16_LALBA);
-                               if ((lalba & SRC16_LBPME) &&
-                                   softc->delete_method == DA_DELETE_NONE)
-                                       dadeletemethodset(softc, 
DA_DELETE_UNMAP);
+                               lbp = (lalba & SRC16_LBPME);
                                dp = &softc->params;
                                snprintf(announce_buf, sizeof(announce_buf),
                                        "%juMB (%ju %u byte sectors: %dH %dS/T "
@@ -2314,7 +2712,7 @@ dadone(struct cam_periph *periph, union 
                                 * If we tried READ CAPACITY(16) and failed,
                                 * fallback to READ CAPACITY(10).
                                 */
-                               if ((softc->state == DA_STATE_PROBE2) &&
+                               if ((softc->state == DA_STATE_PROBE_RC16) &&
                                    (softc->flags & DA_FLAG_CAN_RC16) &&
                                    (((csio->ccb_h.status & CAM_STATUS_MASK) ==
                                        CAM_REQ_INVALID) ||
@@ -2322,9 +2720,9 @@ 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;
                                        free(rdcap, M_SCSIDA);
                                        xpt_release_ccb(done_ccb);
+                                       softc->state = DA_STATE_PROBE_RC;
                                        xpt_schedule(periph, priority);
                                        return;
                                } else
@@ -2386,17 +2784,242 @@ dadone(struct cam_periph *periph, union 
                        }
                                
                }
-               softc->state = DA_STATE_NORMAL; 
-               /*
-                * Since our peripheral may be invalidated by an error
-                * above or an external event, we must release our CCB
-                * before releasing the probe lock on the peripheral.
-                * The peripheral will only go away once the last lock
-                * is removed, and we need it around for the CCB release
-                * operation.
-                */
+
+               /* Ensure re-probe doesn't see old delete. */
+               softc->delete_available = 0;
+               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);
+
+                       xpt_release_ccb(done_ccb);
+                       softc->state = DA_STATE_PROBE_LBP;
+                       xpt_schedule(periph, priority);
+                       return;
+               }
+
+               xpt_release_ccb(done_ccb);
+               softc->state = DA_STATE_PROBE_BDC;
+               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);
+                               xpt_release_ccb(done_ccb);
+                               softc->state = DA_STATE_PROBE_BLK_LIMITS;
+                               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_BDC;
+               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);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
_______________________________________________
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