svn commit: r251491 - in stable/8/sys/cam: ata scsi

Steven Hartland smh at FreeBSD.org
Fri Jun 7 12:24:16 UTC 2013


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 ***


More information about the svn-src-stable-8 mailing list