summaryrefslogtreecommitdiffstats
path: root/sys/cam/scsi
diff options
context:
space:
mode:
authormjacob <mjacob@FreeBSD.org>2010-06-08 16:17:25 +0000
committermjacob <mjacob@FreeBSD.org>2010-06-08 16:17:25 +0000
commitdcb98911e2e5ea29226094837697a14d867cecf8 (patch)
tree5d896e3380a4808ce8a95c2d9f0c057108a3c048 /sys/cam/scsi
parente4e19d26b06529000e3d84472de4c46e4d09e37f (diff)
downloadFreeBSD-src-dcb98911e2e5ea29226094837697a14d867cecf8.zip
FreeBSD-src-dcb98911e2e5ea29226094837697a14d867cecf8.tar.gz
Implement the usage of Report Luns as part of SCSI probing for SCP3 or
better devices. This can be disabled on a per-device basis using quirks as well. This also handles the case where there is actually no connected LUN 0 (which can definitely be the case for storage arrays). Reviewed by: scsi@ MFC after: 1 month
Diffstat (limited to 'sys/cam/scsi')
-rw-r--r--sys/cam/scsi/scsi_xpt.c377
1 files changed, 351 insertions, 26 deletions
diff --git a/sys/cam/scsi/scsi_xpt.c b/sys/cam/scsi/scsi_xpt.c
index c4043ad..979e0a0 100644
--- a/sys/cam/scsi/scsi_xpt.c
+++ b/sys/cam/scsi/scsi_xpt.c
@@ -71,6 +71,7 @@ struct scsi_quirk_entry {
#define CAM_QUIRK_NOSERIAL 0x02
#define CAM_QUIRK_HILUNS 0x04
#define CAM_QUIRK_NOHILUNS 0x08
+#define CAM_QUIRK_NORPTLUNS 0x10
u_int mintags;
u_int maxtags;
};
@@ -84,6 +85,21 @@ SYSCTL_PROC(_kern_cam, OID_AUTO, cam_srch_hi, CTLTYPE_INT|CTLFLAG_RW, 0, 0,
"allow search above LUN 7 for SCSI3 and greater devices");
#define CAM_SCSI2_MAXLUN 8
+#define CAM_CAN_GET_SIMPLE_LUN(x, i) \
+ ((((x)->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \
+ RPL_LUNDATA_ATYP_PERIPH) || \
+ (((x)->luns[i].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \
+ RPL_LUNDATA_ATYP_FLAT))
+#define CAM_GET_SIMPLE_LUN(lp, i, lval) \
+ if (((lp)->luns[(i)].lundata[0] & RPL_LUNDATA_ATYP_MASK) == \
+ RPL_LUNDATA_ATYP_PERIPH) { \
+ (lval) = (lp)->luns[(i)].lundata[1]; \
+ } else { \
+ (lval) = (lp)->luns[(i)].lundata[0]; \
+ (lval) &= RPL_LUNDATA_FLAT_LUN_MASK; \
+ (lval) <<= 8; \
+ (lval) |= (lp)->luns[(i)].lundata[1]; \
+ }
/*
* If we're not quirked to search <= the first 8 luns
* and we are either quirked to search above lun 8,
@@ -116,6 +132,7 @@ typedef enum {
PROBE_TUR,
PROBE_INQUIRY, /* this counts as DV0 for Basic Domain Validation */
PROBE_FULL_INQUIRY,
+ PROBE_REPORT_LUNS,
PROBE_MODE_SENSE,
PROBE_SERIAL_NUM_0,
PROBE_SERIAL_NUM_1,
@@ -130,6 +147,7 @@ static char *probe_action_text[] = {
"PROBE_TUR",
"PROBE_INQUIRY",
"PROBE_FULL_INQUIRY",
+ "PROBE_REPORT_LUNS",
"PROBE_MODE_SENSE",
"PROBE_SERIAL_NUM_0",
"PROBE_SERIAL_NUM_1",
@@ -527,6 +545,10 @@ static void proberequestdefaultnegotiation(struct cam_periph *periph);
static int proberequestbackoff(struct cam_periph *periph,
struct cam_ed *device);
static void probedone(struct cam_periph *periph, union ccb *done_ccb);
+static int probe_strange_rpl_data(struct scsi_report_luns_data *rp,
+ uint32_t maxlun);
+static void probe_purge_old(struct cam_path *path,
+ struct scsi_report_luns_data *new);
static void probecleanup(struct cam_periph *periph);
static void scsi_find_quirk(struct cam_ed *device);
static void scsi_scan_bus(struct cam_periph *periph, union ccb *ccb);
@@ -685,6 +707,7 @@ probestart(struct cam_periph *periph, union ccb *start_ccb)
softc = (probe_softc *)periph->softc;
csio = &start_ccb->csio;
+again:
switch (softc->action) {
case PROBE_TUR:
@@ -773,6 +796,28 @@ probestart(struct cam_periph *periph, union ccb *start_ccb)
/*timeout*/60 * 1000);
break;
}
+ case PROBE_REPORT_LUNS:
+ {
+ void *rp;
+
+ rp = malloc(periph->path->target->rpl_size,
+ M_CAMXPT, M_NOWAIT | M_ZERO);
+ if (rp == NULL) {
+ struct scsi_inquiry_data *inq_buf;
+ inq_buf = &periph->path->device->inq_data;
+ xpt_print(periph->path,
+ "Unable to alloc report luns storage\n");
+ if (INQ_DATA_TQ_ENABLED(inq_buf))
+ PROBE_SET_ACTION(softc, PROBE_MODE_SENSE);
+ else
+ PROBE_SET_ACTION(softc, PROBE_SERIAL_NUM_0);
+ goto again;
+ }
+ scsi_report_luns(csio, 5, probedone, MSG_SIMPLE_Q_TAG,
+ RPL_REPORT_DEFAULT, rp, periph->path->target->rpl_size,
+ SSD_FULL_SIZE, 60000); break;
+ break;
+ }
case PROBE_MODE_SENSE:
{
void *mode_buf;
@@ -1045,9 +1090,7 @@ probedone(struct cam_periph *periph, union ccb *done_ccb)
periph_qual = SID_QUAL(inq_buf);
- switch(periph_qual) {
- case SID_QUAL_LU_CONNECTED:
- {
+ if (periph_qual == SID_QUAL_LU_CONNECTED) {
u_int8_t len;
/*
@@ -1074,10 +1117,23 @@ probedone(struct cam_periph *periph, union ccb *done_ccb)
scsi_find_quirk(path->device);
scsi_devise_transport(path);
- if (INQ_DATA_TQ_ENABLED(inq_buf))
- PROBE_SET_ACTION(softc, PROBE_MODE_SENSE);
+
+ if (path->device->lun_id == 0 &&
+ SID_ANSI_REV(inq_buf) > SCSI_REV_SPC2 &&
+ (SCSI_QUIRK(path->device)->quirks &
+ CAM_QUIRK_NORPTLUNS) == 0) {
+ PROBE_SET_ACTION(softc,
+ PROBE_REPORT_LUNS);
+ /*
+ * Start with room for *one* lun.
+ */
+ periph->path->target->rpl_size = 16;
+ } else if (INQ_DATA_TQ_ENABLED(inq_buf))
+ PROBE_SET_ACTION(softc,
+ PROBE_MODE_SENSE);
else
- PROBE_SET_ACTION(softc, PROBE_SERIAL_NUM_0);
+ PROBE_SET_ACTION(softc,
+ PROBE_SERIAL_NUM_0);
if (path->device->flags & CAM_DEV_UNCONFIGURED) {
path->device->flags &= ~CAM_DEV_UNCONFIGURED;
@@ -1086,9 +1142,21 @@ probedone(struct cam_periph *periph, union ccb *done_ccb)
xpt_release_ccb(done_ccb);
xpt_schedule(periph, priority);
return;
- }
- default:
- break;
+ } else if (path->device->lun_id == 0 &&
+ SID_ANSI_REV(inq_buf) > SCSI_REV_SPC2 &&
+ (SCSI_QUIRK(path->device)->quirks &
+ CAM_QUIRK_NORPTLUNS) == 0) {
+ if (path->device->flags &
+ CAM_DEV_UNCONFIGURED) {
+ path->device->flags &=
+ ~CAM_DEV_UNCONFIGURED;
+ xpt_acquire_device(path->device);
+ }
+ PROBE_SET_ACTION(softc, PROBE_REPORT_LUNS);
+ periph->path->target->rpl_size = 16;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
}
} else if (cam_periph_error(done_ccb, 0,
done_ccb->ccb_h.target_lun > 0
@@ -1118,6 +1186,120 @@ probedone(struct cam_periph *periph, union ccb *done_ccb)
xpt_release_ccb(done_ccb);
break;
}
+ case PROBE_REPORT_LUNS:
+ {
+ struct ccb_scsiio *csio;
+ struct scsi_report_luns_data *lp;
+ u_int nlun, maxlun;
+
+ csio = &done_ccb->csio;
+
+ lp = (struct scsi_report_luns_data *)csio->data_ptr;
+ nlun = scsi_4btoul(lp->length) / 8;
+ maxlun = (csio->dxfer_len / 8) - 1;
+
+ if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ if (cam_periph_error(done_ccb, 0,
+ done_ccb->ccb_h.target_lun > 0 ?
+ SF_RETRY_UA|SF_QUIET_IR : SF_RETRY_UA,
+ &softc->saved_ccb) == ERESTART) {
+ return;
+ }
+ if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) {
+ xpt_release_devq(done_ccb->ccb_h.path, 1,
+ TRUE);
+ }
+ free(lp, M_CAMXPT);
+ lp = NULL;
+ } else if (nlun > maxlun) {
+ /*
+ * Reallocate and retry to cover all luns
+ */
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE, path,
+ ("reallocating REPORT_LUNS for %u luns\n", nlun));
+ free(lp, M_CAMXPT);
+ path->target->rpl_size = (nlun << 3) + 8;
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ } else if (nlun == 0) {
+ /*
+ * If there don't appear to be any luns, bail.
+ */
+ free(lp, M_CAMXPT);
+ lp = NULL;
+ } else if (probe_strange_rpl_data(lp, maxlun)) {
+ /*
+ * If we can't understand the lun format
+ * of any entry, bail.
+ */
+ free(lp, M_CAMXPT);
+ lp = NULL;
+ } else {
+ lun_id_t lun;
+ int idx;
+
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE, path,
+ ("%u luns reported\n", nlun));
+
+ CAM_GET_SIMPLE_LUN(lp, 0, lun);
+ /*
+ * If the first lun is not lun 0, then either there
+ * is no lun 0 in the list, or the list is unsorted.
+ */
+ if (lun != 0) {
+ for (idx = 0; idx < nlun; idx++) {
+ CAM_GET_SIMPLE_LUN(lp, idx, lun);
+ if (lun == 0) {
+ break;
+ }
+ }
+ if (idx != nlun) {
+ uint8_t tlun[8];
+ memcpy(tlun,
+ lp->luns[0].lundata, 8);
+ memcpy(lp->luns[0].lundata,
+ lp->luns[idx].lundata, 8);
+ memcpy(lp->luns[idx].lundata,
+ tlun, 8);
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE,
+ path, ("lun 0 in position %u\n", idx));
+ } else {
+ /*
+ * There is no lun 0 in our list. Destroy
+ * the validity of the inquiry data so we
+ * bail here and now.
+ */
+ path->device->flags &=
+ ~CAM_DEV_INQUIRY_DATA_VALID;
+ }
+ }
+ /*
+ * If we have an old lun list, We can either
+ * retest luns that appear to have been dropped,
+ * or just nuke them. We'll opt for the latter.
+ * This function will also install the new list
+ * in the target structure.
+ */
+ probe_purge_old(path, lp);
+ lp = NULL;
+ }
+ if (path->device->flags & CAM_DEV_INQUIRY_DATA_VALID) {
+ struct scsi_inquiry_data *inq_buf;
+ inq_buf = &path->device->inq_data;
+ if (INQ_DATA_TQ_ENABLED(inq_buf))
+ PROBE_SET_ACTION(softc, PROBE_MODE_SENSE);
+ else
+ PROBE_SET_ACTION(softc, PROBE_SERIAL_NUM_0);
+ xpt_release_ccb(done_ccb);
+ xpt_schedule(periph, priority);
+ return;
+ }
+ if (lp) {
+ free(lp, M_CAMXPT);
+ }
+ break;
+ }
case PROBE_MODE_SENSE:
{
struct ccb_scsiio *csio;
@@ -1432,6 +1614,80 @@ probedone(struct cam_periph *periph, union ccb *done_ccb)
}
}
+static int
+probe_strange_rpl_data(struct scsi_report_luns_data *rp, uint32_t maxlun)
+{
+ uint32_t idx;
+ uint32_t nlun = MIN(maxlun, (scsi_4btoul(rp->length) / 8));
+
+ for (idx = 0; idx < nlun; idx++) {
+ if (!CAM_CAN_GET_SIMPLE_LUN(rp, idx)) {
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+static void
+probe_purge_old(struct cam_path *path, struct scsi_report_luns_data *new)
+{
+ struct cam_path *tp;
+ struct scsi_report_luns_data *old;
+ u_int idx1, idx2, nlun_old, nlun_new, this_lun;
+ u_int8_t *ol, *nl;
+
+ if (path->target == NULL) {
+ return;
+ }
+ if (path->target->luns == NULL) {
+ path->target->luns = new;
+ return;
+ }
+ old = path->target->luns;
+ nlun_old = scsi_4btoul(old->length) / 8;
+ nlun_new = scsi_4btoul(new->length) / 8;
+
+ /*
+ * We are not going to assume sorted lists. Deal.
+ */
+ for (idx1 = 0; idx1 < nlun_old; idx1++) {
+ ol = old->luns[idx1].lundata;
+ for (idx2 = 0; idx2 < nlun_new; idx2++) {
+ nl = new->luns[idx2].lundata;
+ if (memcmp(nl, ol, 8) == 0) {
+ break;
+ }
+ }
+ if (idx2 < nlun_new) {
+ continue;
+ }
+ /*
+ * An 'old' item not in the 'new' list.
+ * Nuke it. Except that if it is lun 0,
+ * that would be what the probe state
+ * machine is currently working on,
+ * so we won't do that.
+ *
+ * We also cannot nuke it if it is
+ * not in a lun format we understand.
+ */
+ if (!CAM_CAN_GET_SIMPLE_LUN(old, idx1)) {
+ continue;
+ }
+ CAM_GET_SIMPLE_LUN(old, idx1, this_lun);
+ if (this_lun == 0) {
+ continue;
+ }
+ if (xpt_create_path(&tp, NULL, xpt_path_path_id(path),
+ xpt_path_target_id(path), this_lun) == CAM_REQ_CMP) {
+ xpt_async(AC_LOST_DEVICE, tp, NULL);
+ xpt_free_path(tp);
+ }
+ }
+ free(old, M_CAMXPT);
+ path->target->luns = new;
+}
+
static void
probecleanup(struct cam_periph *periph)
{
@@ -1480,6 +1736,7 @@ typedef struct {
union ccb *request_ccb;
struct ccb_pathinq *cpi;
int counter;
+ int lunindex[0];
} scsi_scan_bus_info;
/*
@@ -1553,13 +1810,15 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
}
/* Save some state for use while we probe for devices */
- scan_info = (scsi_scan_bus_info *)
- malloc(sizeof(scsi_scan_bus_info), M_CAMXPT, M_NOWAIT);
+ scan_info = (scsi_scan_bus_info *) malloc(sizeof(scsi_scan_bus_info) +
+ (work_ccb->cpi.max_target * sizeof (u_int)), M_CAMXPT, M_ZERO|M_NOWAIT);
if (scan_info == NULL) {
request_ccb->ccb_h.status = CAM_RESRC_UNAVAIL;
xpt_done(request_ccb);
return;
}
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE, request_ccb->ccb_h.path,
+ ("SCAN start for %p\n", scan_info));
scan_info->request_ccb = request_ccb;
scan_info->cpi = &work_ccb->cpi;
@@ -1626,26 +1885,76 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
case XPT_SCAN_LUN:
{
cam_status status;
- struct cam_path *path;
+ struct cam_path *path, *oldpath;
scsi_scan_bus_info *scan_info;
+ struct cam_et *target;
+ struct cam_ed *device;
+ int next_target;
path_id_t path_id;
target_id_t target_id;
lun_id_t lun_id;
+ oldpath = request_ccb->ccb_h.path;
+
+ status = request_ccb->ccb_h.status & CAM_STATUS_MASK;
/* Reuse the same CCB to query if a device was really found */
scan_info = (scsi_scan_bus_info *)request_ccb->ccb_h.ppriv_ptr0;
xpt_setup_ccb(&request_ccb->ccb_h, request_ccb->ccb_h.path,
request_ccb->ccb_h.pinfo.priority);
request_ccb->ccb_h.func_code = XPT_GDEV_TYPE;
+
path_id = request_ccb->ccb_h.path_id;
target_id = request_ccb->ccb_h.target_id;
lun_id = request_ccb->ccb_h.target_lun;
xpt_action(request_ccb);
- if (request_ccb->ccb_h.status != CAM_REQ_CMP) {
- struct cam_ed *device;
- struct cam_et *target;
+ target = request_ccb->ccb_h.path->target;
+ next_target = 1;
+
+ if (target->luns) {
+ uint32_t first;
+ u_int nluns = scsi_4btoul(target->luns->length) / 8;
+
+ /*
+ * Make sure we skip over lun 0 if it's the first member
+ * of the list as we've actually just finished probing
+ * it.
+ */
+ CAM_GET_SIMPLE_LUN(target->luns, 0, first);
+ if (first == 0 && scan_info->lunindex[target_id] == 0) {
+ scan_info->lunindex[target_id]++;
+ }
+
+ if (scan_info->lunindex[target_id] < nluns) {
+ CAM_GET_SIMPLE_LUN(target->luns,
+ scan_info->lunindex[target_id], lun_id);
+ next_target = 0;
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE,
+ request_ccb->ccb_h.path,
+ ("next lun to try at index %u is %u\n",
+ scan_info->lunindex[target_id], lun_id));
+ scan_info->lunindex[target_id]++;
+ } else {
+ /*
+ * We're done with scanning all luns.
+ *
+ * Nuke the bogus device for lun 0 if lun 0
+ * wasn't on the list.
+ */
+ if (first != 0) {
+ TAILQ_FOREACH(device,
+ &target->ed_entries, links) {
+ if (device->lun_id == 0) {
+ break;
+ }
+ }
+ if (device) {
+ xpt_release_device(device);
+ }
+ }
+ }
+ } else if (request_ccb->ccb_h.status != CAM_REQ_CMP) {
int phl;
/*
@@ -1654,7 +1963,6 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
* target that might have "gone away", go onto
* the next lun.
*/
- target = request_ccb->ccb_h.path->target;
/*
* We may touch devices that we don't
* hold references too, so ensure they
@@ -1670,11 +1978,15 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
device = TAILQ_NEXT(device, links);
}
if ((lun_id != 0) || (device != NULL)) {
- if (lun_id < (CAM_SCSI2_MAXLUN-1) || phl)
+ if (lun_id < (CAM_SCSI2_MAXLUN-1) || phl) {
lun_id++;
+ next_target = 0;
+ }
}
+ if (lun_id == request_ccb->ccb_h.target_lun
+ || lun_id > scan_info->cpi->max_lun)
+ next_target = 1;
} else {
- struct cam_ed *device;
device = request_ccb->ccb_h.path->device;
@@ -1682,23 +1994,26 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
CAM_QUIRK_NOLUNS) == 0) {
/* Try the next lun */
if (lun_id < (CAM_SCSI2_MAXLUN-1)
- || CAN_SRCH_HI_DENSE(device))
+ || CAN_SRCH_HI_DENSE(device)) {
lun_id++;
+ next_target = 0;
+ }
}
+ if (lun_id == request_ccb->ccb_h.target_lun
+ || lun_id > scan_info->cpi->max_lun)
+ next_target = 1;
}
/*
- * Free the current request path- we're done with it.
- */
- xpt_free_path(request_ccb->ccb_h.path);
-
- /*
* Check to see if we scan any further luns.
*/
- if (lun_id == request_ccb->ccb_h.target_lun
- || lun_id > scan_info->cpi->max_lun) {
+ if (next_target) {
int done;
+ /*
+ * Free the current request path- we're done with it.
+ */
+ xpt_free_path(oldpath);
hop_again:
done = 0;
if (scan_info->request_ccb->ccb_h.func_code == XPT_SCAN_TGT) {
@@ -1723,6 +2038,9 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
xpt_free_ccb(request_ccb);
xpt_free_ccb((union ccb *)scan_info->cpi);
request_ccb = scan_info->request_ccb;
+ CAM_DEBUG_PATH_PRINT(CAM_DEBUG_PROBE,
+ request_ccb->ccb_h.path,
+ ("SCAN done for %p\n", scan_info));
free(scan_info, M_CAMXPT);
request_ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(request_ccb);
@@ -1758,6 +2076,13 @@ scsi_scan_bus(struct cam_periph *periph, union ccb *request_ccb)
} else {
status = xpt_create_path(&path, xpt_periph,
path_id, target_id, lun_id);
+ /*
+ * Free the old request path- we're done with it. We
+ * do this *after* creating the new path so that
+ * we don't remove a target that has our lun list
+ * in the case that lun 0 is not present.
+ */
+ xpt_free_path(oldpath);
if (status != CAM_REQ_CMP) {
printf("scsi_scan_bus: xpt_create_path failed "
"with status %#x, halting LUN scan\n",
OpenPOWER on IntegriCloud