summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormav <mav@FreeBSD.org>2011-12-23 19:12:02 +0000
committermav <mav@FreeBSD.org>2011-12-23 19:12:02 +0000
commitb9b25a56e024475d9de6039ea0b453e2ba078011 (patch)
tree7d85bb3946b0dcc3e54c5c86a58205c18aa3255b
parentb24bbc2146be01e81cdac625d127f4f93cf9219e (diff)
downloadFreeBSD-src-b9b25a56e024475d9de6039ea0b453e2ba078011.zip
FreeBSD-src-b9b25a56e024475d9de6039ea0b453e2ba078011.tar.gz
Use READ CAPACITY(16) to get information about device physical sectors.
As soon as not all devices support READ CAPACITY(16), automatically fall back to READ CAPACITY(10) if CAM_REQ_INVALID or SSD_KEY_ILLEGAL_REQUEST status returned. It also provides first bits of information about Logical Block Provisioning (aka UNMAP/TRIM) support by the device.
-rw-r--r--sys/cam/scsi/scsi_all.h11
-rw-r--r--sys/cam/scsi/scsi_da.c124
2 files changed, 125 insertions, 10 deletions
diff --git a/sys/cam/scsi/scsi_all.h b/sys/cam/scsi/scsi_all.h
index 552c51b..309afe0 100644
--- a/sys/cam/scsi/scsi_all.h
+++ b/sys/cam/scsi/scsi_all.h
@@ -1349,6 +1349,17 @@ struct scsi_read_capacity_data_long
{
uint8_t addr[8];
uint8_t length[4];
+#define SRC16_PROT_EN 0x01
+#define SRC16_P_TYPE 0x0e
+ uint8_t prot;
+#define SRC16_LBPPBE 0x0f
+#define SRC16_PI_EXPONENT 0xf0
+#define SRC16_PI_EXPONENT_SHIFT 4
+ uint8_t prot_lbppbe;
+#define SRC16_LALBA 0x3fff
+#define SRC16_LBPRZ 0x4000
+#define SRC16_LBPME 0x8000
+ uint8_t lalba_lbp[2];
};
struct scsi_report_luns
diff --git a/sys/cam/scsi/scsi_da.c b/sys/cam/scsi/scsi_da.c
index 0171712..2f5a005 100644
--- a/sys/cam/scsi/scsi_da.c
+++ b/sys/cam/scsi/scsi_da.c
@@ -82,7 +82,9 @@ typedef enum {
DA_FLAG_WENT_IDLE = 0x040,
DA_FLAG_RETRY_UA = 0x080,
DA_FLAG_OPEN = 0x100,
- DA_FLAG_SCTX_INIT = 0x200
+ DA_FLAG_SCTX_INIT = 0x200,
+ DA_FLAG_CAN_RC16 = 0x400,
+ DA_FLAG_CAN_LBPME = 0x800
} da_flags;
typedef enum {
@@ -1533,6 +1535,14 @@ daregister(struct cam_periph *periph, void *arg)
else if (softc->minimum_cmd_size > 12)
softc->minimum_cmd_size = 16;
+ /* Predict whether device may support READ CAPACITY(16). */
+ if (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC3 ||
+ (SID_ANSI_REV(&cgd->inq_data) >= SCSI_REV_SPC &&
+ (cgd->inq_data.spc3_flags & SPC3_SID_PROTECT))) {
+ softc->flags |= DA_FLAG_CAN_RC16;
+ softc->state = DA_STATE_PROBE2;
+ }
+
/*
* Register this media as a disk.
*/
@@ -1922,10 +1932,14 @@ dadone(struct cam_periph *periph, union ccb *done_ccb)
struct disk_params *dp;
uint32_t block_size;
uint64_t maxsector;
+ u_int lbppbe; /* LB per physical block exponent. */
+ u_int lalba; /* Lowest aligned LBA. */
if (softc->state == DA_STATE_PROBE) {
block_size = scsi_4btoul(rdcap->length);
maxsector = scsi_4btoul(rdcap->addr);
+ lbppbe = 0;
+ lalba = 0;
/*
* According to SBC-2, if the standard 10
@@ -1945,6 +1959,8 @@ dadone(struct cam_periph *periph, union ccb *done_ccb)
} else {
block_size = scsi_4btoul(rcaplong->length);
maxsector = scsi_8btou64(rcaplong->addr);
+ lbppbe = rcaplong->prot_lbppbe & SRC16_LBPPBE;
+ lalba = scsi_2btoul(rcaplong->lalba_lbp);
}
/*
@@ -1962,7 +1978,12 @@ dadone(struct cam_periph *periph, union ccb *done_ccb)
announce_buf[0] = '\0';
cam_periph_invalidate(periph);
} else {
- dasetgeom(periph, block_size, maxsector, 0, 0);
+ dasetgeom(periph, block_size, maxsector,
+ lbppbe, lalba & SRC16_LALBA);
+ if (lalba & SRC16_LBPME)
+ softc->flags |= DA_FLAG_CAN_LBPME;
+ else
+ softc->flags &= ~DA_FLAG_CAN_LBPME;
dp = &softc->params;
snprintf(announce_buf, sizeof(announce_buf),
"%juMB (%ju %u byte sectors: %dH %dS/T "
@@ -2029,6 +2050,24 @@ dadone(struct cam_periph *periph, union ccb *done_ccb)
&ascq, /*show_errors*/ 1);
}
/*
+ * If we tried READ CAPACITY(16) and failed,
+ * fallback to READ CAPACITY(10).
+ */
+ if ((softc->state == DA_STATE_PROBE2) &&
+ (softc->flags & DA_FLAG_CAN_RC16) &&
+ (((csio->ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_REQ_INVALID) ||
+ ((have_sense) &&
+ (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);
+ xpt_schedule(periph, priority);
+ return;
+ } else
+ /*
* Attach to anything that claims to be a
* direct access or optical disk device,
* as long as it doesn't return a "Logical
@@ -2198,13 +2237,18 @@ dagetcapacity(struct cam_periph *periph)
struct scsi_read_capacity_data_long *rcaplong;
uint32_t block_len;
uint64_t maxsector;
- int error;
+ int error, rc16failed;
u_int32_t sense_flags;
+ u_int lbppbe; /* Logical blocks per physical block exponent. */
+ u_int lalba; /* Lowest aligned LBA. */
softc = (struct da_softc *)periph->softc;
block_len = 0;
maxsector = 0;
+ lbppbe = 0;
+ lalba = 0;
error = 0;
+ rc16failed = 0;
sense_flags = SF_RETRY_UA;
if (softc->flags & DA_FLAG_PACK_REMOVABLE)
sense_flags |= SF_NO_PRINT;
@@ -2212,11 +2256,63 @@ dagetcapacity(struct cam_periph *periph)
/* Do a read capacity */
rcap = (struct scsi_read_capacity_data *)malloc(sizeof(*rcaplong),
M_SCSIDA,
- M_NOWAIT);
+ M_NOWAIT | M_ZERO);
if (rcap == NULL)
return (ENOMEM);
-
+ rcaplong = (struct scsi_read_capacity_data_long *)rcap;
+
ccb = cam_periph_getccb(periph, CAM_PRIORITY_NORMAL);
+
+ /* Try READ CAPACITY(16) first if we think it should work. */
+ if (softc->flags & DA_FLAG_CAN_RC16) {
+ scsi_read_capacity_16(&ccb->csio,
+ /*retries*/ 4,
+ /*cbfcnp*/ dadone,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*lba*/ 0,
+ /*reladr*/ 0,
+ /*pmi*/ 0,
+ rcaplong,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ 60000);
+ ccb->ccb_h.ccb_bp = NULL;
+
+ error = cam_periph_runccb(ccb, daerror,
+ /*cam_flags*/CAM_RETRY_SELTO,
+ sense_flags,
+ softc->disk->d_devstat);
+
+ if ((ccb->ccb_h.status & CAM_DEV_QFRZN) != 0)
+ cam_release_devq(ccb->ccb_h.path,
+ /*relsim_flags*/0,
+ /*reduction*/0,
+ /*timeout*/0,
+ /*getcount_only*/0);
+
+ if (error == 0)
+ goto rc16ok;
+
+ /* If we got ILLEGAL REQUEST, do not prefer RC16 any more. */
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_REQ_INVALID) {
+ softc->flags &= ~DA_FLAG_CAN_RC16;
+ } else if (((ccb->ccb_h.status & CAM_STATUS_MASK) ==
+ CAM_SCSI_STATUS_ERROR) &&
+ (ccb->csio.scsi_status == SCSI_STATUS_CHECK_COND) &&
+ (ccb->ccb_h.status & CAM_AUTOSNS_VALID) &&
+ ((ccb->ccb_h.flags & CAM_SENSE_PHYS) == 0) &&
+ ((ccb->ccb_h.flags & CAM_SENSE_PTR) == 0)) {
+ int sense_key, error_code, asc, ascq;
+
+ scsi_extract_sense(&ccb->csio.sense_data,
+ &error_code, &sense_key, &asc, &ascq);
+ if (sense_key == SSD_KEY_ILLEGAL_REQUEST)
+ softc->flags &= ~DA_FLAG_CAN_RC16;
+ }
+ rc16failed = 1;
+ }
+
+ /* Do READ CAPACITY(10). */
scsi_read_capacity(&ccb->csio,
/*retries*/4,
/*cbfncp*/dadone,
@@ -2242,13 +2338,12 @@ dagetcapacity(struct cam_periph *periph)
block_len = scsi_4btoul(rcap->length);
maxsector = scsi_4btoul(rcap->addr);
- if (maxsector != 0xffffffff)
+ if (maxsector != 0xffffffff || rc16failed)
goto done;
} else
goto done;
- rcaplong = (struct scsi_read_capacity_data_long *)rcap;
-
+ /* If READ CAPACITY(10) returned overflow, use READ CAPACITY(16) */
scsi_read_capacity_16(&ccb->csio,
/*retries*/ 4,
/*cbfcnp*/ dadone,
@@ -2274,8 +2369,11 @@ dagetcapacity(struct cam_periph *periph)
/*getcount_only*/0);
if (error == 0) {
+rc16ok:
block_len = scsi_4btoul(rcaplong->length);
maxsector = scsi_8btou64(rcaplong->addr);
+ lbppbe = rcaplong->prot_lbppbe & SRC16_LBPPBE;
+ lalba = scsi_2btoul(rcaplong->lalba_lbp);
}
done:
@@ -2286,8 +2384,14 @@ done:
"unsupportable block size %ju\n",
(uintmax_t) block_len);
error = EINVAL;
- } else
- dasetgeom(periph, block_len, maxsector, 0, 0);
+ } else {
+ dasetgeom(periph, block_len, maxsector,
+ lbppbe, lalba & SRC16_LALBA);
+ if (lalba & SRC16_LBPME)
+ softc->flags |= DA_FLAG_CAN_LBPME;
+ else
+ softc->flags &= ~DA_FLAG_CAN_LBPME;
+ }
}
xpt_release_ccb(ccb);
OpenPOWER on IntegriCloud