blk2scsa.c revision f6715e51fdff2380ce05731fe0aafd1924e2efcb
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* We implement the following SCSI-2 commands on behalf of targets:
*
* SCMD_DOORLOCK
* SCMD_FORMAT
* SCMD_INQUIRY
* SCMD_MODE_SENSE
* SCMD_READ
* SCMD_READ_G1
* SCMD_READ_CAPACITY
* SCMD_RELEASE
* SCMD_REQUEST_SENSE
* SCMD_RESERVE
* SCMD_SDIAG
* SCMD_START_STOP
* SCMD_TEST_UNIT_READY
* SCMD_WRITE
* SCMD_WRITE_G1
*
* We really should, at some point in the future, investigate offering
* more complete SCSI-3 commands, including the G4 and G5 variants of
* READ and WRITE, MODE_SELECT, PERSISTENT_RESERVE_IN,
* PERSISTENT_RESERVE_OUT, SYNCHRONIZE_CACHE, READ_MEDIAL_SERIAL,
* REPORT_LUNS, etc.
*/
typedef struct b2s_request_impl b2s_request_impl_t;
struct b2s_request_impl {
struct scsi_arq_status *ri_sts;
void (*ri_done)(struct b2s_request_impl *);
};
struct b2s_nexus {
struct scsi_hba_tran *n_tran;
void *n_private;
};
#define B2S_NEXUS_ATTACHED (1U << 0)
struct b2s_leaf {
char *l_uuid;
struct scsi_inquiry l_inq;
};
/*
* This copies a string into a target buf, obeying the size limits
* of the target. It does not null terminate, ever.
*/
/*
* Thank you SCSA, for making it a PITA to deal with a single byte
* value by turning it into a bitfield!
*/
struct b2s_error {
};
"SCSA Block Device Emulation",
};
static struct modlinkage modlinkage = {
};
/*
* For layers that don't provide a DMA attribute, we offer a default
* one. Such devices probably just want to do mapin, all of the time,
* but since SCSI doesn't give us a way to indicate that, we have to
* provide a fake attribute. Slightly wasteful, but PIO-only disk
* devices are going to have some performance issues anyway.
*
* For such devices, we only want to commit to transferring 64K at a time,
* and let the SCSA layer break it up for us.
*/
static struct ddi_dma_attr b2s_default_dma_attr = {
0, /* lo address */
0xffffffffffffffffULL, /* high address */
0xffffU, /* DMA counter max */
1, /* alignment */
0x0c, /* burst sizes */
1, /* minimum transfer size */
0xffffU, /* maximum transfer size */
0xffffU, /* maximum segment size */
1, /* granularity */
0 /* DMA flags */
};
/*
* Private prototypes.
*/
scsi_hba_tran_t *, struct scsi_device *);
scsi_hba_tran_t *, struct scsi_device *);
static int b2s_tran_getcap(struct scsi_address *, char *, int);
static int b2s_tran_setcap(struct scsi_address *, char *, int, int);
static void b2s_tran_teardown_pkt(struct scsi_pkt *);
static int b2s_tran_reset(struct scsi_address *, int);
dev_info_t **);
static void b2s_inquiry_done(b2s_request_impl_t *);
static int b2s_inquiry(b2s_leaf_t *);
static void b2s_init_err_table(void);
static int b2s_scmd_inq(b2s_request_impl_t *);
static int b2s_scmd_tur(b2s_request_impl_t *);
static int b2s_scmd_doorlock(b2s_request_impl_t *);
static int b2s_scmd_format(b2s_request_impl_t *);
static int b2s_scmd_readcap(b2s_request_impl_t *);
static int b2s_scmd_rw(b2s_request_impl_t *);
static int b2s_scmd_rqs(b2s_request_impl_t *);
static int b2s_scmd_sdiag(b2s_request_impl_t *);
static int b2s_scmd_start_stop(b2s_request_impl_t *);
static int b2s_scmd_mode_sense(b2s_request_impl_t *);
static int b2s_scmd_reserve_release(b2s_request_impl_t *);
static void b2s_scmd_readcap_done(b2s_request_impl_t *);
static void b2s_scmd_mode_sense_done(b2s_request_impl_t *);
static void b2s_warn(b2s_leaf_t *, const char *, ...);
int
_init(void)
{
int rv;
return (rv);
}
int
_fini(void)
{
int rv;
return (rv);
}
int
{
}
int
{
return (scsi_hba_init(modlp));
}
void
{
}
void
b2s_init_err_table(void)
{
int i;
/* fill up most of them with defaults */
for (i = 0; i < B2S_NERRS; i++) {
}
/* now flesh out real values */
/*
* This one, SYSTEM_RESOURCE_FAILURE, is not really legal for
* DTYPE_DIRECT in SCSI-2, but sd doesn't care, and reporting
* it this way may help diagnosis. sd will retry it in any
* case.
*/
}
/*
* called with the nexus lock held.
*/
{
b2s_leaf_t *l;
while (l != NULL) {
break;
}
}
return (l);
}
/*
* nexus lock must *NOT* be held.
*/
{
b2s_leaf_t *l;
mutex_enter(&n->n_lock);
if (l != NULL) {
l->l_refcnt++;
}
mutex_exit(&n->n_lock);
return (l);
}
/*
* Drop the hold on the leaf.
*/
void
{
b2s_nexus_t *n = l->l_nexus;
mutex_enter(&n->n_lock);
l->l_refcnt--;
if (l->l_refcnt == 0) {
list_remove(&n->n_leaves, l);
kmem_free(l, sizeof (*l));
}
mutex_exit(&n->n_lock);
}
/*
* This is used to walk the list of leaves safely, without requiring the
* nexus lock to be held. The returned leaf is held. (If the passed in
* lastl is not NULL, then it is released as well.)
*
* Pass NULL for lastl to start the walk.
*/
{
b2s_leaf_t *l;
mutex_enter(&n->n_lock);
} else {
}
if (l != NULL) {
l->l_refcnt++;
}
mutex_exit(&n->n_lock);
}
return (l);
}
void
{
/*
* This uses some undocumented fields of the SCSI
* packet, but it is what is required to get the
* kernel virtual addresses.
*/
}
} else {
*addrp = 0;
*lenp = 0;
}
}
void
{
}
void
{
/*
* Make sure that the status is in range of our known errs. If we
* don't know it, then just cobble up a bogus one.
*/
} else {
}
if (status == STATUS_CHECK) {
/*
* Contingent allegiance. We need to do the
* ARQ thing.
*/
sts->sts_rqpkt_resid = 0;
/*
* Stash any residue information.
*/
}
} else {
}
/*
* N.B.: Obviously not all commands actually have a SCSI
* DATA-IN or DATA-OUT phase. But it doesn't matter, since
* sd.c only bothers to look at this flag for request sense
* traffic, which is always correct within our emulation.
*
* We go ahead and set it on all good packets however, since
* there may in the future be some additional checks to make
* sure a data transfer occurred. This seems safer (since
* then sd should examine pkt_resid) rather than leaving it
* off by default.
*/
}
/*
* Finally, execute the callback (unless running POLLED)
*/
}
}
void
{
/*
* Post process... this is used for massaging results into
* what SCSI wants.
*/
/*
* Undo the effect of any specific mapin that may have been done to
* process the request.
*/
}
/*
* For SCSI packets, we have special completion handling. For
* internal requests, we just mark the request done so the caller
* can free it.
*/
mutex_enter(&n->n_lock);
cv_broadcast(&n->n_cv);
mutex_exit(&n->n_lock);
} else {
}
}
/*ARGSUSED*/
int
{
b2s_nexus_t *n;
b2s_leaf_t *l;
/*
* Lookup the target and lun.
*/
n = tran->tran_hba_private;
/*
* Hold the leaf node as long as the devinfo node is using it.
*/
if (l == NULL) {
/*
* Target node not found on bus.
*/
return (DDI_FAILURE);
}
tran->tran_tgt_private = l;
return (DDI_SUCCESS);
}
/*ARGSUSED*/
void
{
b2s_leaf_t *l;
l = tran->tran_tgt_private;
b2s_rele_leaf(l);
}
/*ARGSUSED*/
int
{
/*
* NB: The other fields are initialized properly at tran_start(9e).
* We don't care about their values the rest of the time.
*/
return (0);
}
/*ARGSUSED*/
void
{
/*
* Free the cloned buf header.
*/
}
/*ARGSUSED*/
int
{
int capid;
switch (capid) {
case SCSI_CAP_ARQ:
case SCSI_CAP_UNTAGGED_QING:
return (1);
default:
return (-1);
}
}
int
{
int err;
/*
* We can only do the blind abort of all packets. We have
* no way to request an individual packet be aborted.
*/
return (B_FALSE);
}
return (B_FALSE);
}
/* leave all else null */
/*
* Submit request to device driver.
*/
/* this shouldn't happen, since we are just starting out */
b2s_warn(l, "Busy trying to abort");
return (B_FALSE);
}
/*
* Wait for command completion.
*/
mutex_enter(&n->n_lock);
mutex_exit(&n->n_lock);
if (err != 0) {
return (B_FALSE);
}
return (B_TRUE);
}
int
{
int err;
return (B_FALSE);
}
return (B_FALSE);
}
/* leave all else null */
/*
* Submit request to device driver.
*/
/* this shouldn't happen, since we are just starting out */
b2s_warn(l, "Busy trying to reset");
return (B_FALSE);
}
/*
* Wait for command completion.
*/
mutex_enter(&n->n_lock);
mutex_exit(&n->n_lock);
if (err != 0) {
return (B_FALSE);
}
return (B_TRUE);
}
/*ARGSUSED*/
int
{
int capid;
switch (capid) {
case SCSI_CAP_ARQ:
if (val == 0) {
return (0);
} else {
return (1);
}
default:
return (-1);
}
}
int
{
((l->l_flags & B2S_LEAF_DETACHED) != 0)) {
/*
* Leaf is not on the bus!
*
* We should add support for inquiry when lun != 0,
* even if when the lun does not exist, but lun 0 is
* present. But, it turns out this is not strictly
* required by sd(7d).
*/
return (TRAN_ACCEPT);
}
case SCMD_DOORLOCK:
return (b2s_scmd_doorlock(ri));
case SCMD_FORMAT:
return (b2s_scmd_format(ri));
case SCMD_INQUIRY:
return (b2s_scmd_inq(ri));
case SCMD_REQUEST_SENSE:
return (b2s_scmd_rqs(ri));
case SCMD_SDIAG:
return (b2s_scmd_sdiag(ri));
case SCMD_TEST_UNIT_READY:
return (b2s_scmd_tur(ri));
case SCMD_READ_CAPACITY:
return (b2s_scmd_readcap(ri));
case SCMD_RELEASE:
case SCMD_RESERVE:
return (b2s_scmd_reserve_release(ri));
case SCMD_START_STOP:
return (b2s_scmd_start_stop(ri));
case SCMD_MODE_SENSE:
return (b2s_scmd_mode_sense(ri));
case SCMD_READ:
case SCMD_READ_G1:
case SCMD_WRITE:
case SCMD_WRITE_G1:
return (b2s_scmd_rw(ri));
default:
return (TRAN_ACCEPT);
}
}
/*
* Publish standard properties on a newly created devinfo node.
*/
int
{
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
l->l_uuid) != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
if (l->l_flags & B2S_LEAF_HOTPLUGGABLE) {
"hotpluggable") != DDI_PROP_SUCCESS) {
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* Find the devinfo node associated with the leaf, looking up by target and
* lun. (Alternatively in the future we could use a full address)
*
* This must be called with the tree lock held.
*/
{
/* is this the right target */
return (dip);
}
}
return (NULL);
}
/*
* combination.
*/
int
{
char *name;
char **compat;
int ncompat;
int rv;
/*
* If the node was already created, then we're done.
*/
if (dipp)
return (DDI_SUCCESS);
}
/*
* Perform an inquiry to collect key information.
*/
if (b2s_inquiry(l) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
NDI_SUCCESS) {
b2s_warn(l, "Unable to create devinfo node");
return (DDI_FAILURE);
}
(void) ndi_devi_free(dip);
b2s_warn(l, "Unable to create properties");
return (DDI_FAILURE);
}
if (dipp) {
/*
* We were called by bus_config BUS_CONFIG_ONE,
* and therefore must be done synchronously.
*/
if (rv == NDI_SUCCESS)
} else {
/*
* The rest of the time, asynchronous is easier and
* safer (nexus could call us from interrupt context).
*/
}
if (rv != NDI_SUCCESS) {
b2s_warn(l, "Failed to online device");
(void) ndi_devi_free(dip);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
long val;
char *ptr;
int rv;
b2s_leaf_t *l;
b2s_nexus_t *n;
int circ;
n = tran->tran_hba_private;
switch (op) {
case BUS_CONFIG_ONE:
/*
* First parse out the target and lun from the
* address.
*/
rv = NDI_FAILURE;
break;
}
ptr++;
rv = NDI_FAILURE;
break;
}
ptr++;
rv = NDI_FAILURE;
break;
}
/*
* Now lookup the leaf, and if we have it, attempt to create
* the devinfo node for it.
*/
rv = NDI_SUCCESS;
rv = NDI_FAILURE;
}
b2s_rele_leaf(l);
break;
}
break;
case BUS_CONFIG_DRIVER:
case BUS_CONFIG_ALL:
l = b2s_next_leaf(n, NULL);
while (l != NULL) {
(void) b2s_create_node(n, l, NULL);
l = b2s_next_leaf(n, l);
}
rv = NDI_SUCCESS;
break;
default:
rv = NDI_FAILURE;
break;
}
if (rv == NDI_SUCCESS) {
}
return (rv);
}
void
{
/*
* The only post processing we have to do is to massage the
* strings into the inquiry structure.
*/
}
int
{
b2s_nexus_t *n;
struct scsi_inquiry *inqp;
int err;
n = l->l_nexus;
/*
* Set up basic structure, including space padding for ASCII strings.
*/
if (l->l_flags & B2S_LEAF_REMOVABLE)
/*
* To get product strings, we have to issue a query to the driver.
*/
return (DDI_FAILURE);
}
/* leave all else null */
/*
* Submit inquiry request to device driver.
*/
/* this shouldn't happen, since we are just starting out */
b2s_warn(l, "Busy trying to collect inquiry data");
return (DDI_FAILURE);
}
/*
* Wait for inquiry completion.
*/
mutex_enter(&n->n_lock);
mutex_exit(&n->n_lock);
if (err != 0) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
{
/*
* Suppport inquiry pages: 0 is the list itself, and 80 is the
* unit serial number (in ASCII).
*/
hdr[2] = 0;
/*
* We don't support the EVP data bit, and hence neither a page code.
* This corresponds to the entire G0 address field (which includes
* a few reserved bits).
*/
case 0x00000: /* standard SCSI inquiry */
return (TRAN_ACCEPT);
case 0x10000: /* page 0 supported VPD pages */
hdr[0] = DTYPE_DIRECT;
hdr[2] = 0;
break;
case 0x18000: /* page 80 unit serial number */
hdr[0] = DTYPE_DIRECT;
hdr[2] = 0;
break;
default:
return (TRAN_ACCEPT);
}
/* now copy the header */
/* now copy the actual page data */
return (TRAN_ACCEPT);
}
int
{
int rv;
/* Like inquiry, the entire G0 address field must be zero. */
rv = B2S_EINVAL;
len = 0;
resid = 0;
} else {
struct scsi_extended_sense es;
/*
* We always use ARQ, unconditionally, so this command
* can always return success.
*/
}
return (TRAN_ACCEPT);
}
int
{
int rv;
/* we only support the SELFTEST bit */
rv = B2S_EINVAL;
} else {
}
return (TRAN_ACCEPT);
}
int
{
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
int
{
/*
* Bit 0 of the count indicates the "Prevent" mode. All other address
* and count bits are reserved.
*/
return (TRAN_ACCEPT);
}
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
int
{
return (TRAN_ACCEPT);
}
/*
* FmtData set. A defect list is attached.
*
* This is an awful lot of work just to support a command
* option we don't ever care about. SCSI-2 says we have
* to do it.
*
* The alternative would just be to ignore the defect list
* and format options altogether. That would be a lot easier.
*/
if (len < 4) {
return (TRAN_ACCEPT);
}
return (TRAN_ACCEPT);
}
}
/*
* No defect list, so this bit (CmpLst) should have been zero!
*/
return (TRAN_ACCEPT);
}
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
void
{
struct scsi_capacity cap;
/*
* Lower layer resid is meaningless here.
*/
return;
}
switch (GETG1COUNT(cdb)) {
case 0: /* PMI == 0 */
if (lba != 0) {
return;
}
break;
case 1: /* PMI == 1 */
return;
}
break;
default:
return;
}
/*
* Note that the capacity is the LBA of the last block, not the
* number of blocks. A little surprising if you don't pay close
* enough attention to the spec.
*/
if (len != 0) {
}
}
int
{
/*
* No transfer by real target.
*/
return (TRAN_ACCEPT);
}
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
int
{
/* we aren't checking fields we don't care about */
/* extent reservations not supported */
return (TRAN_ACCEPT);
}
/*
* We don't support multi-initiator access, so we always
* return success.
*/
return (TRAN_ACCEPT);
}
int
{
case 0:
break;
case 0x10000: /* immed set */
break;
default:
return (TRAN_ACCEPT);
}
if (count > 3) {
return (TRAN_ACCEPT);
}
if (count & 0x2)
if (count & 0x1) {
} else {
}
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
void
{
devspec = 0;
} else {
/* this marks the media read-only */
devspec = 0x80;
}
pc &= 0xc0;
page &= 0x3f;
/* we do not support savable parameters, at all */
return;
}
/* Peripheral device page */
/* header */
/* page data - 9 bytes long */
}
/* Control mode page */
/* header */
/* page data - 9 bytes long */
}
}
int
{
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
int
{
case CDB_GROUPID_0:
break;
case CDB_GROUPID_1:
/* we don't support relative addresses */
return (TRAN_ACCEPT);
}
break;
default:
return (TRAN_ACCEPT);
}
if (nblks == 0) {
return (TRAN_ACCEPT);
}
return (TRAN_BUSY);
}
return (TRAN_ACCEPT);
}
void
{
b2s_nexus_t *n;
char msg[256];
n = l->l_nexus;
}
{
b2s_nexus_t *n;
struct scsi_hba_tran *tran;
return (NULL);
n = kmem_zalloc(sizeof (*n), KM_SLEEP);
} else {
n->n_dma = &b2s_default_dma_attr;
}
list_destroy(&n->n_leaves);
mutex_destroy(&n->n_lock);
cv_destroy(&n->n_cv);
kmem_free(n, sizeof (*n));
return (NULL);
}
tran->tran_hba_private = n;
return (n);
}
void
{
b2s_leaf_t *l;
/*
* Toss any registered leaves, if we haven't already done so.
* At this point we don't care about upper layers, because the
* DDI should not have allowed us to detach if there were busy
* targets.
*/
list_remove(&n->n_leaves, l);
}
list_destroy(&n->n_leaves);
mutex_destroy(&n->n_lock);
cv_destroy(&n->n_cv);
}
int
{
int rv;
if (rv == 0) {
n->n_attached = B_TRUE;
}
return (rv);
}
int
{
int rv;
if (n->n_attached) {
if (rv == 0) {
n->n_attached = B_FALSE;
}
} else {
rv = 0;
}
}
{
b2s_leaf_t *l;
uuid = "";
}
mutex_enter(&n->n_lock);
/*
* If the leaf already exists, it is a sign that the device
* was kept around because it was still in use. In that case,
* we attempt to detect the situation where the node is the same
* as the previous one, and reconnect it.
*/
/*
* Leaf already exists, but is not the same! This
* would be a good time to issue a warning.
*/
mutex_exit(&n->n_lock);
b2s_warn(l, "Target disconnected while still in use.");
b2s_warn(l, "Reconnect the previous target device.");
return (NULL);
}
l->l_flags &= ~B2S_LEAF_DETACHED;
} else {
mutex_exit(&n->n_lock);
b2s_warn(l, "Unable to allocate target state.");
return (NULL);
}
l->l_nexus = n;
/* strdup would be nice here */
mutex_exit(&n->n_lock);
kmem_free(l, sizeof (*l));
b2s_warn(l, "Unable to allocate target UUID storage.");
return (NULL);
}
list_insert_tail(&n->n_leaves, l);
}
/*
* Make sure we hold it, so that it won't be freed out from
* underneath us.
*/
l->l_refcnt++;
mutex_exit(&n->n_lock);
/*
* If the HBA is currently attached, then we need to attach
* the node right now. This supports "hotplug". Note that
* if the node is a reinsert, then this should degenerate into
* a NOP.
*/
if (n->n_attached) {
int circ;
(void) b2s_create_node(n, l, NULL);
}
return (l);
}
void
{
b2s_nexus_t *n = l->l_nexus;
int circ;
l->l_flags |= B2S_LEAF_DETACHED;
/*
* Search for an appropriate child devinfo.
*/
dip = b2s_find_node(n, l);
}
b2s_rele_leaf(l);
}