New SCSI target emulator code

This code allows a user program to enable target mode on a SIM and
then emulate any number of devices (disks, tape drives, etc.)  All
decisions about device behavior (UA, CA, inquiry response) are left
to the usermode program and the kernel driver is merely a conduit
for CCBs.  This enables multiple concurrent target emulators, each
using its own backing store and IO model.

Also included is a user program that emulates a disk (RBC) using a
file as a backing store.  This provides functionality similar to
md(4) at the CAM layer.

Code has been tested on ahc(4) and should also work on isp(4) (and
other SIMs that gain target mode support).  It is a complete rewrite
of /sys/cam/scsi_target* and /usr/share/examples/scsi_target.

Design, comments from:	gibbs
Supported by:		Cryptography Research
Approved by:		re
This commit is contained in:
Nate Lawson 2002-11-22 22:55:51 +00:00
parent 2db19028d3
commit c38b150ae4
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=107178
9 changed files with 2843 additions and 2427 deletions

View File

@ -1,8 +1,9 @@
# $FreeBSD$
PROG= scsi_target
SRCS= scsi_target.c
SRCS= scsi_target.h scsi_target.c scsi_cmds.c
LDADD= -lcam
NOMAN= noman
MAN= scsi_target.8
.include <bsd.prog.mk>

View File

@ -0,0 +1,664 @@
/*
* SCSI Disk Emulator
*
* Copyright (c) 2002 Nate Lawson.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification, immediately at the beginning of the file.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <aio.h>
#include <assert.h>
#include <sys/types.h>
#include <cam/cam.h>
#include <cam/cam_ccb.h>
#include <cam/scsi/scsi_all.h>
#include <cam/scsi/scsi_targetio.h>
#include "scsi_target.h"
typedef int targ_start_func(struct ccb_accept_tio *, struct ccb_scsiio *);
typedef void targ_done_func(struct ccb_accept_tio *, struct ccb_scsiio *,
io_ops);
struct targ_cdb_handlers {
u_int8_t cmd;
targ_start_func *start;
targ_done_func *done;
#define ILLEGAL_CDB 0xFF
};
static targ_start_func tcmd_inquiry;
static targ_start_func tcmd_req_sense;
static targ_start_func tcmd_rd_cap;
static targ_start_func tcmd_rdwr;
static targ_start_func tcmd_rdwr_decode;
static targ_done_func tcmd_rdwr_done;
static targ_start_func tcmd_null_ok;
static targ_start_func tcmd_illegal_req;
static int start_io(struct ccb_accept_tio *atio,
struct ccb_scsiio *ctio, int dir);
static int init_inquiry(u_int16_t req_flags, u_int16_t sim_flags);
static struct initiator_state *
tcmd_get_istate(u_int init_id);
static void cdb_debug(u_int8_t *cdb, const char *msg, ...);
static struct targ_cdb_handlers cdb_handlers[] = {
{ READ_10, tcmd_rdwr, tcmd_rdwr_done },
{ WRITE_10, tcmd_rdwr, tcmd_rdwr_done },
{ READ_6, tcmd_rdwr, tcmd_rdwr_done },
{ WRITE_6, tcmd_rdwr, tcmd_rdwr_done },
{ INQUIRY, tcmd_inquiry, NULL },
{ REQUEST_SENSE, tcmd_req_sense, NULL },
{ READ_CAPACITY, tcmd_rd_cap, NULL },
{ TEST_UNIT_READY, tcmd_null_ok, NULL },
{ START_STOP_UNIT, tcmd_null_ok, NULL },
{ SYNCHRONIZE_CACHE, tcmd_null_ok, NULL },
{ MODE_SENSE_6, tcmd_illegal_req, NULL },
{ MODE_SELECT_6, tcmd_illegal_req, NULL },
{ ILLEGAL_CDB, NULL, NULL }
};
static struct scsi_inquiry_data inq_data;
static struct initiator_state istates[MAX_INITIATORS];
extern int debug;
extern u_int32_t volume_size;
extern size_t sector_size;
extern size_t buf_size;
cam_status
tcmd_init(u_int16_t req_inq_flags, u_int16_t sim_inq_flags)
{
struct initiator_state *istate;
int i, ret;
/* Initialize our inquiry data */
ret = init_inquiry(req_inq_flags, sim_inq_flags);
if (ret != 0)
return (ret);
/* We start out life with a UA to indicate power-on/reset. */
for (i = 0; i < MAX_INITIATORS; i++) {
istate = tcmd_get_istate(i);
bzero(istate, sizeof(*istate));
istate->pending_ua = UA_POWER_ON;
}
return (0);
}
/* Caller allocates CTIO, sets its init_id
return 0 if done, 1 if more processing needed
on 0, caller sets SEND_STATUS */
int
tcmd_handle(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio, io_ops event)
{
static struct targ_cdb_handlers *last_cmd;
struct initiator_state *istate;
struct atio_descr *a_descr;
int ret;
warnx("tcmd_handle atio %p ctio %p atioflags %#x", atio, ctio,
atio->ccb_h.flags);
ret = 0;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
/* Do a full lookup if one-behind cache failed */
if (last_cmd == NULL || last_cmd->cmd != a_descr->cdb[0]) {
struct targ_cdb_handlers *h;
for (h = cdb_handlers; h->cmd != ILLEGAL_CDB; h++) {
if (a_descr->cdb[0] == h->cmd)
break;
}
last_cmd = h;
}
if (last_cmd->cmd == ILLEGAL_CDB) {
if (event != ATIO_WORK) {
warnx("no done func for %#x???", a_descr->cdb[0]);
abort();
}
/* Not found, return illegal request */
warnx("cdb %#x not handled", a_descr->cdb[0]);
tcmd_illegal_req(atio, ctio);
send_ccb((union ccb *)ctio, /*priority*/1);
return (0);
}
/* call completion and exit */
if (event != ATIO_WORK) {
if (last_cmd->done != NULL)
last_cmd->done(atio, ctio, event);
else
free_ccb((union ccb *)ctio);
return (1);
}
istate = tcmd_get_istate(ctio->init_id);
if (istate == NULL) {
tcmd_illegal_req(atio, ctio);
send_ccb((union ccb *)ctio, /*priority*/1);
return (0);
}
if (istate->pending_ca == 0 && istate->pending_ua != 0 &&
a_descr->cdb[0] != INQUIRY) {
tcmd_sense(ctio->init_id, ctio, SSD_KEY_UNIT_ATTENTION,
0x29, istate->pending_ua == UA_POWER_ON ? 1 : 2);
istate->pending_ca = CA_UNIT_ATTN;
if (debug) {
cdb_debug(a_descr->cdb, "UA active for %u: ",
atio->init_id);
}
send_ccb((union ccb *)ctio, /*priority*/1);
return (0);
}
/* Store current CA and UA for later */
istate->orig_ua = istate->pending_ua;
istate->orig_ca = istate->pending_ca;
/*
* As per SAM2, any command that occurs
* after a CA is reported, clears the CA. We must
* also clear the UA condition, if any, that caused
* the CA to occur assuming the UA is not for a
* persistent condition.
*/
istate->pending_ca = CA_NONE;
if (istate->orig_ca == CA_UNIT_ATTN)
istate->pending_ua = UA_NONE;
/* If we have a valid handler, call start or completion function */
if (last_cmd->cmd != ILLEGAL_CDB) {
ret = last_cmd->start(atio, ctio);
/* XXX hack */
if (last_cmd->start != tcmd_rdwr) {
a_descr->init_req += ctio->dxfer_len;
send_ccb((union ccb *)ctio, /*priority*/1);
}
}
return (ret);
}
static struct initiator_state *
tcmd_get_istate(u_int init_id)
{
if (init_id >= MAX_INITIATORS) {
warnx("illegal init_id %d, max %d", init_id, MAX_INITIATORS - 1);
return (NULL);
} else {
return (&istates[init_id]);
}
}
void
tcmd_sense(u_int init_id, struct ccb_scsiio *ctio, u_int8_t flags,
u_int8_t asc, u_int8_t ascq)
{
struct initiator_state *istate;
struct scsi_sense_data *sense;
/* Set our initiator's istate */
istate = tcmd_get_istate(init_id);
if (istate == NULL)
return;
istate->pending_ca |= CA_CMD_SENSE; /* XXX set instead of or? */
sense = &istate->sense_data;
bzero(sense, sizeof(*sense));
sense->error_code = SSD_CURRENT_ERROR;
sense->flags = flags;
sense->add_sense_code = asc;
sense->add_sense_code_qual = ascq;
sense->extra_len =
offsetof(struct scsi_sense_data, sense_key_spec[2]) -
offsetof(struct scsi_sense_data, extra_len);
/* Fill out the supplied CTIO */
if (ctio != NULL) {
/* No autosense yet
bcopy(sense, &ctio->sense_data, sizeof(*sense));
ctio->sense_len = sizeof(*sense); XXX
*/
ctio->ccb_h.flags &= ~CAM_DIR_MASK;
ctio->ccb_h.flags |= CAM_DIR_NONE | /* CAM_SEND_SENSE | */
CAM_SEND_STATUS;
ctio->dxfer_len = 0;
ctio->scsi_status = SCSI_STATUS_CHECK_COND;
}
}
void
tcmd_ua(u_int init_id, ua_types new_ua)
{
struct initiator_state *istate;
u_int start, end;
if (init_id == CAM_TARGET_WILDCARD) {
start = 0;
end = MAX_INITIATORS - 1;
} else {
start = end = init_id;
}
for (; start <= end; start++) {
istate = tcmd_get_istate(start);
if (istate == NULL)
break;
istate->pending_ua = new_ua;
}
}
static int
tcmd_inquiry(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
struct scsi_inquiry *inq;
struct atio_descr *a_descr;
struct initiator_state *istate;
struct scsi_sense_data *sense;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
inq = (struct scsi_inquiry *)a_descr->cdb;
if (debug)
cdb_debug(a_descr->cdb, "INQUIRY from %u: ", atio->init_id);
/*
* Validate the command. We don't support any VPD pages, so
* complain if EVPD or CMDDT is set.
*/
istate = tcmd_get_istate(ctio->init_id);
sense = &istate->sense_data;
if ((inq->byte2 & SI_EVPD) != 0) {
tcmd_illegal_req(atio, ctio);
sense->sense_key_spec[0] = SSD_SCS_VALID | SSD_FIELDPTR_CMD |
SSD_BITPTR_VALID | /*bit value*/1;
sense->sense_key_spec[1] = 0;
sense->sense_key_spec[2] =
offsetof(struct scsi_inquiry, byte2);
} else if (inq->page_code != 0) {
tcmd_illegal_req(atio, ctio);
sense->sense_key_spec[0] = SSD_SCS_VALID | SSD_FIELDPTR_CMD;
sense->sense_key_spec[1] = 0;
sense->sense_key_spec[2] =
offsetof(struct scsi_inquiry, page_code);
} else {
bcopy(&inq_data, ctio->data_ptr, sizeof(inq_data));
ctio->dxfer_len = inq_data.additional_length + 4;
ctio->dxfer_len = min(ctio->dxfer_len,
SCSI_CDB6_LEN(inq->length));
ctio->ccb_h.flags |= CAM_DIR_IN | CAM_SEND_STATUS;
ctio->scsi_status = SCSI_STATUS_OK;
}
return (0);
}
/* Initialize the inquiry response structure with the requested flags */
static int
init_inquiry(u_int16_t req_flags, u_int16_t sim_flags)
{
struct scsi_inquiry_data *inq;
inq = &inq_data;
bzero(inq, sizeof(*inq));
inq->device = T_DIRECT | (SID_QUAL_LU_CONNECTED << 5);
inq->version = SCSI_REV_SPC; /* was 2 */
/*
* XXX cpi.hba_inquiry doesn't support Addr16 so we give the
* user what they want if they ask for it.
*/
if ((req_flags & SID_Addr16) != 0) {
sim_flags |= SID_Addr16;
warnx("Not sure SIM supports Addr16 but enabling it anyway");
}
/* Advertise only what the SIM can actually support */
req_flags &= sim_flags;
scsi_ulto2b(req_flags, &inq->reserved[1]);
inq->response_format = 2; /* SCSI2 Inquiry Format */
inq->additional_length = SHORT_INQUIRY_LENGTH -
offsetof(struct scsi_inquiry_data, additional_length);
bcopy("FreeBSD ", inq->vendor, SID_VENDOR_SIZE);
bcopy("Emulated Disk ", inq->product, SID_PRODUCT_SIZE);
bcopy("0.1 ", inq->revision, SID_REVISION_SIZE);
return (0);
}
static int
tcmd_req_sense(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
struct scsi_request_sense *rsense;
struct scsi_sense_data *sense;
struct initiator_state *istate;
size_t dlen;
struct atio_descr *a_descr;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
rsense = (struct scsi_request_sense *)a_descr->cdb;
istate = tcmd_get_istate(ctio->init_id);
sense = &istate->sense_data;
if (debug) {
cdb_debug(a_descr->cdb, "REQ SENSE from %u: ", atio->init_id);
warnx("Sending sense: %#x %#x %#x", sense->flags,
sense->add_sense_code, sense->add_sense_code_qual);
}
if (istate->orig_ca == 0) {
tcmd_sense(ctio->init_id, NULL, SSD_KEY_NO_SENSE, 0, 0);
warnx("REQUEST SENSE from %u but no pending CA!",
ctio->init_id);
}
bcopy(sense, ctio->data_ptr, sizeof(struct scsi_sense_data));
dlen = offsetof(struct scsi_sense_data, extra_len) +
sense->extra_len + 1;
ctio->dxfer_len = min(dlen, SCSI_CDB6_LEN(rsense->length));
ctio->ccb_h.flags |= CAM_DIR_IN | CAM_SEND_STATUS;
ctio->scsi_status = SCSI_STATUS_OK;
return (0);
}
static int
tcmd_rd_cap(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
struct scsi_read_capacity_data *srp;
struct atio_descr *a_descr;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
srp = (struct scsi_read_capacity_data *)ctio->data_ptr;
if (debug) {
cdb_debug(a_descr->cdb, "READ CAP from %u (%u, %u): ",
atio->init_id, volume_size - 1, sector_size);
}
bzero(srp, sizeof(*srp));
scsi_ulto4b(volume_size - 1, srp->addr);
scsi_ulto4b(sector_size, srp->length);
ctio->dxfer_len = sizeof(*srp);
ctio->ccb_h.flags |= CAM_DIR_IN | CAM_SEND_STATUS;
ctio->scsi_status = SCSI_STATUS_OK;
return (0);
}
static int
tcmd_rdwr(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
struct atio_descr *a_descr;
struct ctio_descr *c_descr;
int ret;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr;
/* Command needs to be decoded */
if ((a_descr->flags & CAM_DIR_MASK) == CAM_DIR_RESV) {
if (debug)
warnx("Calling rdwr_decode");
ret = tcmd_rdwr_decode(atio, ctio);
if (ret == 0) {
send_ccb((union ccb *)ctio, /*priority*/1);
return (0);
}
}
ctio->ccb_h.flags |= a_descr->flags;
/* Call appropriate work function */
if ((a_descr->flags & CAM_DIR_IN) != 0) {
ret = start_io(atio, ctio, CAM_DIR_IN);
if (debug)
warnx("Starting DIR_IN @%lld:%u", c_descr->offset,
a_descr->targ_req);
} else {
ret = start_io(atio, ctio, CAM_DIR_OUT);
if (debug)
warnx("Starting DIR_OUT @%lld:%u", c_descr->offset,
a_descr->init_req);
}
return (ret);
}
static int
tcmd_rdwr_decode(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
u_int32_t blkno, count;
struct atio_descr *a_descr;
u_int8_t *cdb;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
cdb = a_descr->cdb;
if (debug)
cdb_debug(cdb, "R/W from %u: ", atio->init_id);
if (cdb[0] == READ_6 || cdb[0] == WRITE_6) {
struct scsi_rw_6 *rw_6 = (struct scsi_rw_6 *)cdb;
blkno = scsi_3btoul(rw_6->addr);
count = rw_6->length;
} else {
struct scsi_rw_10 *rw_10 = (struct scsi_rw_10 *)cdb;
blkno = scsi_4btoul(rw_10->addr);
count = scsi_2btoul(rw_10->length);
}
if (blkno + count > volume_size) {
warnx("Attempt to access past end of volume");
tcmd_sense(ctio->init_id, ctio,
SSD_KEY_ILLEGAL_REQUEST, 0x21, 0);
return (0);
}
/* Get an (overall) data length and set direction */
a_descr->base_off = ((off_t)blkno) * sector_size;
a_descr->total_len = count * sector_size;
if (a_descr->total_len == 0) {
if (debug)
warnx("r/w 0 blocks @ blkno %u", blkno);
tcmd_null_ok(atio, ctio);
return (0);
} else if (cdb[0] == WRITE_6 || cdb[0] == WRITE_10) {
a_descr->flags |= CAM_DIR_OUT;
if (debug)
warnx("write %u blocks @ blkno %u", count, blkno);
} else {
a_descr->flags |= CAM_DIR_IN;
if (debug)
warnx("read %u blocks @ blkno %u", count, blkno);
}
return (1);
}
static int
start_io(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio, int dir)
{
struct atio_descr *a_descr;
struct ctio_descr *c_descr;
int ret;
/* Set up common structures */
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr;
if (dir == CAM_DIR_IN) {
c_descr->offset = a_descr->base_off + a_descr->targ_req;
ctio->dxfer_len = a_descr->total_len - a_descr->targ_req;
} else {
c_descr->offset = a_descr->base_off + a_descr->init_req;
ctio->dxfer_len = a_descr->total_len - a_descr->init_req;
}
ctio->dxfer_len = min(ctio->dxfer_len, buf_size);
assert(ctio->dxfer_len >= 0);
c_descr->aiocb.aio_offset = c_descr->offset;
c_descr->aiocb.aio_nbytes = ctio->dxfer_len;
/* If DIR_IN, start read from target, otherwise begin CTIO xfer. */
ret = 1;
if (dir == CAM_DIR_IN) {
if (aio_read(&c_descr->aiocb) < 0)
err(1, "aio_read"); /* XXX */
a_descr->targ_req += ctio->dxfer_len;
if (a_descr->targ_req == a_descr->total_len) {
ctio->ccb_h.flags |= CAM_SEND_STATUS;
ctio->scsi_status = SCSI_STATUS_OK;
ret = 0;
}
} else {
if (a_descr->targ_ack == a_descr->total_len)
tcmd_null_ok(atio, ctio);
a_descr->init_req += ctio->dxfer_len;
if (a_descr->init_req == a_descr->total_len &&
ctio->dxfer_len > 0) {
/*
* If data phase done, remove atio from workq.
* The completion handler will call work_atio to
* send the final status.
*/
ret = 0;
}
send_ccb((union ccb *)ctio, /*priority*/1);
}
return (ret);
}
static void
tcmd_rdwr_done(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio,
io_ops event)
{
struct atio_descr *a_descr;
struct ctio_descr *c_descr;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr;
switch (event) {
case AIO_DONE:
if (aio_return(&c_descr->aiocb) < 0) {
warn("aio_return error");
/* XXX */
tcmd_sense(ctio->init_id, ctio,
SSD_KEY_MEDIUM_ERROR, 0, 0);
send_ccb((union ccb *)ctio, /*priority*/1);
break;
}
a_descr->targ_ack += ctio->dxfer_len;
if ((a_descr->flags & CAM_DIR_IN) != 0) {
if (debug)
warnx("sending CTIO for AIO read");
a_descr->init_req += ctio->dxfer_len;
send_ccb((union ccb *)ctio, /*priority*/1);
} else {
/* Use work function to send final status */
if (a_descr->init_req == a_descr->total_len)
work_atio(atio);
if (debug)
warnx("AIO done freeing CTIO");
free_ccb((union ccb *)ctio);
}
break;
case CTIO_DONE:
if (ctio->ccb_h.status != CAM_REQ_CMP) {
/* XXX */
errx(1, "CTIO failed, status %#x", ctio->ccb_h.status);
}
a_descr->init_ack += ctio->dxfer_len;
if ((a_descr->flags & CAM_DIR_MASK) == CAM_DIR_OUT &&
ctio->dxfer_len > 0) {
if (debug)
warnx("sending AIO for CTIO write");
a_descr->targ_req += ctio->dxfer_len;
if (aio_write(&c_descr->aiocb) < 0)
err(1, "aio_write"); /* XXX */
} else {
if (debug)
warnx("CTIO done freeing CTIO");
free_ccb((union ccb *)ctio);
}
break;
default:
warnx("Unknown completion code %d", event);
abort();
/* NOTREACHED */
}
}
/* Simple ok message used by TUR, SYNC_CACHE, etc. */
static int
tcmd_null_ok(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
if (debug) {
struct atio_descr *a_descr;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
cdb_debug(a_descr->cdb, "Sending null ok to %u : ", atio->init_id);
}
ctio->dxfer_len = 0;
ctio->ccb_h.flags &= ~CAM_DIR_MASK;
ctio->ccb_h.flags |= CAM_DIR_NONE | CAM_SEND_STATUS;
ctio->scsi_status = SCSI_STATUS_OK;
return (0);
}
/* Simple illegal request message used by MODE SENSE, etc. */
static int
tcmd_illegal_req(struct ccb_accept_tio *atio, struct ccb_scsiio *ctio)
{
if (debug) {
struct atio_descr *a_descr;
a_descr = (struct atio_descr *)atio->ccb_h.targ_descr;
cdb_debug(a_descr->cdb, "Sending ill req to %u: ", atio->init_id);
}
tcmd_sense(atio->init_id, ctio, SSD_KEY_ILLEGAL_REQUEST,
/*asc*/0x24, /*ascq*/0);
return (0);
}
static void
cdb_debug(u_int8_t *cdb, const char *msg, ...)
{
char msg_buf[512];
int len;
va_list ap;
va_start(ap, msg);
vsnprintf(msg_buf, sizeof(msg_buf), msg, ap);
va_end(ap);
len = strlen(msg_buf);
scsi_cdb_string(cdb, msg_buf + len, sizeof(msg_buf) - len);
warnx("%s", msg_buf);
}

View File

@ -0,0 +1,142 @@
.\" Copyright (c) 2002
.\" Nate Lawson. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. Neither the name of the author nor the names of any co-contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY Nate Lawson AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd November 15, 2002
.Dt SCSI_TARGET 8
.Os
.Sh NAME
.Nm scsi_target
.Nd usermode SCSI disk emulator
.Sh SYNOPSIS
.Nm
.Op Fl AdST
.Op Fl b Ar size
.Op Fl c Ar size
.Op Fl s Ar size
.Op Fl W Ar num
.Ar bus:target:lun
.Ar filename
.Sh DESCRIPTION
The
.Nm
program emulates a SCSI target device using the
.Xr targ 4
device driver. It supports the basic commands of a direct access device, like
.Xr da 4 .
In typical operation, it opens a control device and
enables target mode for the specified LUN. It then communicates with
the SIM using CCBs exchanged via
.Xr read 2
and
.Xr write 2 .
READ and WRITE CDBs are satisfied with the specified backing store file.
.Pp
For performance, all backing store accesses use
.Xr aio 4 .
Thus,
.Nm
requires a kernel compiled with "options VFS_AIO".
.Pp
Options:
.Pp
.Bl -tag -width XXXXXXXXXXXXXX
.It Fl A
Enable 16 addresses if supported by the SIM. Default is 8.
.It Fl S
Enable synchronous transfers if supported by the SIM. Default is disabled.
.It Fl T
Enable tagged queuing if supported by the SIM. Default is no tagged
queuing.
.It Fl W Ar "8,16,32"
Enable 16 or 32 bit wide transfers if supported by the SIM. Default is 8.
.It Fl b Ar bufsize
Set buffer size for transfers. Transfers larger than this will be split
into multiple transfers.
.It Fl c Ar sectorsize
Set sector size for emulated volume. Default is 512.
.It Fl d
Enable debugging output in
.Nm
and its associated control device.
.It Fl s Ar volsize
Use a different size for the emulated volume. Must be less than or equal
to the size of
.Ar filename .
.El
.Pp
Required arguments:
.Bl -tag -width XXXXXXXXXXXXXX
.It Ar bus:target:lun
Attach to specified bus id, target id, and lun.
.It Ar filename
file to use as a backing store
.El
.Pp
All options default to the minimal functionality of SCSI-1.
To be safe,
.Nm
checks the SIM for the requested capability before enabling target mode.
.Sh EXAMPLE
Create a 5 megabyte backing store file.
.Bd -literal
# dd if=/dev/zero of=vol size=1m count=5
.Ed
.Pp
Enable target mode on bus 0, target id 1, lun 0, using
.Ar vol
as the backing store for READ6/10 and WRITE6/10 commands.
Only the first 1000 bytes of
.Ar vol
will be used. Debugging information will be output.
16-bit wide transfers will be used if the SIM supports them.
.Pp
.Bd -literal
# scsi_target -d -v 1000 -W 16 0:1:0 vol
.Ed
.Sh FILES
.Bl -tag -width /usr/share/examples/scsi_target -compact
.It Pa /dev/targ*
are the control devices.
.It Pa /usr/share/examples/scsi_target
is the source directory.
.El
.Sh SEE ALSO
.Xr targ 4 ,
.Xr scsi 4
.Sh AUTHORS
The
.Nm
example first appeared in
.Fx 3.0
and was written by
.An Justin T. Gibbs .
It was rewritten for
.Fx 5.0
by
.An Nate Lawson Aq nate@root.org .

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,117 @@
/*
* SCSI Target Emulator
*
* Copyright (c) 2002 Nate Lawson.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification, immediately at the beginning of the file.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _SCSI_TARGET_H
#define _SCSI_TARGET_H
/*
* Maximum number of parallel commands to accept
* Set to 256 for Fibre Channel (SPI is 16)
*/
#define MAX_INITIATORS 16
#define SECTOR_SIZE 512
#define MAX_EVENTS (MAX_INITIATORS + 5)
/* kqueue for AIO, signals */
/* Additional SCSI 3 defines for inquiry response */
#define SID_Addr16 0x0100
TAILQ_HEAD(io_queue, ccb_hdr);
/* Offset into the private CCB area for storing our descriptor */
#define targ_descr periph_priv.entries[1].ptr
/* Descriptor attached to each ATIO */
struct atio_descr {
off_t base_off; /* Base offset for ATIO */
size_t total_len; /* Total xfer len for this ATIO */
size_t init_req; /* Transfer count requested to/from init */
size_t init_ack; /* Data transferred ok to/from init */
size_t targ_req; /* Transfer count requested to/from target */
size_t targ_ack; /* Data transferred ok to/from target */
int flags; /* Flags for CTIOs */
u_int8_t *cdb; /* Pointer to received CDB */
/* List of completed AIO/CTIOs */
struct io_queue cmplt_io;
};
typedef enum {
ATIO_WORK,
AIO_DONE,
CTIO_DONE
} io_ops;
/* Descriptor attached to each CTIO */
struct ctio_descr {
void *buf; /* Backing store */
off_t offset; /* Position in transfer (for file, */
/* doesn't start at 0) */
struct aiocb aiocb; /* AIO descriptor for this CTIO */
struct ccb_accept_tio *atio;
/* ATIO we are satisfying */
io_ops event; /* Event that queued this CTIO */
};
typedef enum {
UA_NONE = 0x00,
UA_POWER_ON = 0x01,
UA_BUS_RESET = 0x02,
UA_BDR = 0x04
} ua_types;
typedef enum {
CA_NONE = 0x00,
CA_UNIT_ATTN = 0x01,
CA_CMD_SENSE = 0x02
} ca_types;
struct initiator_state {
ua_types orig_ua;
ca_types orig_ca;
ua_types pending_ua;
ca_types pending_ca;
struct scsi_sense_data sense_data;
};
/* Global functions */
extern cam_status tcmd_init(u_int16_t req_inq_flags,
u_int16_t sim_inq_flags);
extern int tcmd_handle(struct ccb_accept_tio *atio,
struct ccb_scsiio *ctio, io_ops event);
extern void tcmd_sense(u_int init_id, struct ccb_scsiio *ctio,
u_int8_t flags,
u_int8_t asc, u_int8_t ascq);
extern void tcmd_ua(u_int init_id, ua_types new_ua);
extern int work_atio(struct ccb_accept_tio *atio);
extern void send_ccb(union ccb *ccb, int priority);
extern void free_ccb(union ccb *ccb);
static __inline u_int min(u_int a, u_int b) { return (a < b ? a : b); }
#endif /* _SCSI_TARGET_H */

143
share/man/man4/targ.4 Normal file
View File

@ -0,0 +1,143 @@
.\" Copyright (c) 2002
.\" Nate Lawson. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Neither the name of the author nor the names of any co-contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY Nate Lawson AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd November 15, 2002
.Dt targ 4
.Os
.Sh NAME
.Nm targ
.Nd SCSI target emulator driver
.Sh SYNOPSIS
.Cd device targ
.Sh DESCRIPTION
The
.Nm
driver provides an interface for usermode programs to emulate SCSI target
devices. A sample program that emulates a disk drive (similar to
.Xr da 4 )
can be found in /usr/share/examples/scsi_target.
.Pp
The
.Nm
driver supplies control devices,
.Pa /dev/targ0 ,
.Pa /dev/targ1 ,
etc.
If a device is already in use, the open will fail and
.Va errno
will be set to
.Er EBUSY .
After opening the device, the file descriptor must be bound to a
specific bus/target/lun and enabled to process CCBs using the
.Pa TARGIOCENABLE
ioctl.
The process then uses
.Xr write 2
to send CCBs to the SIM and
.Xr poll 2
or
.Xr kqueue 2
to see if responses are ready. Pointers to completed CCBs are returned via
.Xr read 2 .
Any data transfers requested by the user CCBs are done via zero-copy IO.
.Pp
.Sh IOCTLS
The following
.Xr ioctl 2
calls are defined in the header file
.Aq Pa cam/scsi/scsi_targetio.h .
.Bl -tag -width TARGIOCDISABLE
.It Dv TARGIOCENABLE
.Pq Li "struct ioc_enable_lun"
Enable target mode on the LUN specified by the following structure:
.Bd -literal -offset indent
struct ioc_enable_lun {
path_id_t path_id;
target_id_t target_id;
lun_id_t lun_id;
int grp6_len;
int grp7_len;
};
.Ed
.Pp
The selected path (bus), target, and lun must not already be in use or
.Er EADDRINUSE
is returned.
If grp6_len or grp7_len are non-zero, reception of vendor-specific commands
is enabled.
.It Dv TARGIOCDISABLE
Disable target mode and abort all pending CCBs.
The CCBs may optionally be read as they complete.
.Pa TARGIOCENABLE
can then be called to activate a different LUN.
Multiple disable calls have no effect.
The
.Xr close 2
system call automatically disables target mode if enabled.
.It Dv TARGIOCDEBUG
.Pq Li "int"
Enables CAM_PERIPH debugging if the argument is non-zero, otherwise disables
it.
.El
.Sh FILES
.Bl -tag -width /sys/cam/scsi/scsi_target.c -compact
.It Aq Pa cam/scsi/scsi_targetio.h
describes the usermode interface.
.It Pa /sys/cam/scsi/scsi_target.c
is the driver source file.
.It Pa /dev/targ*
are the control devices.
.El
.Sh SEE ALSO
.Xr /usr/share/examples/scsi_target ,
.Xr scsi 4
.Rs
.%T "FreeBSD Target Information"
.%O http://www.root.org/~nate/freebsd/
.Re
.Sh BUGS
Currently, only the
.Xr ahc 4
driver fully supports target mode. The
.Xr isp 4
and
.Xr sym 4
drivers have some target mode support but are untested.
.Pp
The
.Xr ahc 4
driver does not support tagged queuing in target mode.
.Sh AUTHORS
The
.Nm
driver first appeared in
.Fx 3.0 and was written by
.An Justin T. Gibbs .
It was rewritten
for
.Fx 5.0
by
.An Nate Lawson Aq nate@root.org .

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
/*
* Ioctl definitions for the Target Mode SCSI Proccessor Target driver for CAM.
* Ioctl definitions for the SCSI Target Driver
*
* Copyright (c) 2002 Nate Lawson.
* Copyright (c) 1998 Justin T. Gibbs.
* All rights reserved.
*
@ -38,104 +39,39 @@
#include <cam/cam.h>
#include <cam/cam_ccb.h>
TAILQ_HEAD(ccb_queue, ccb_hdr);
/* Determine and clear exception state in the driver */
typedef enum {
TARG_EXCEPT_NONE = 0x00,
TARG_EXCEPT_DEVICE_INVALID = 0x01,
TARG_EXCEPT_BDR_RECEIVED = 0x02,
TARG_EXCEPT_BUS_RESET_SEEN = 0x04,
TARG_EXCEPT_ABORT_SEEN = 0x08,
TARG_EXCEPT_ABORT_TAG_SEEN = 0x10,
TARG_EXCEPT_UNKNOWN_ATIO = 0x80
} targ_exception;
#define TARGIOCFETCHEXCEPTION _IOR('C', 1, targ_exception)
#define TARGIOCCLEAREXCEPTION _IOW('C', 2, targ_exception)
/*
* CCBs (ATIO, CTIO, INOT, REL_SIMQ) are sent to the kernel driver
* by writing one or more pointers. The user receives notification
* of CCB completion through poll/select/kqueue and then calls
* read(2) which outputs pointers to the completed CCBs.
*/
/*
* Retreive an Accept Target I/O CCB for a command that is not handled
* directly by the kernel target driver.
* Enable and disable a target mode instance. For enabling, the path_id,
* target_id, and lun_id fields must be set. The grp6/7_len fields
* specify the length of vendor-specific CDBs the target expects and
* should normally be set to 0. On successful completion
* of enable, the specified target instance will answer selection.
* Disable causes the target instance to abort any outstanding commands
* and stop accepting new ones. The aborted CCBs will be returned to
* the user via read(2) or discarded if the user closes the device.
* The user can then re-enable the device for a new path.
*/
#define TARGIOCFETCHATIO _IOR('C', 3, struct ccb_accept_tio)
/*
* Used for responding to incoming ATIO requests. XPT_CONTINUE_TARG_IO
* operations are the norm, but ccb types for manipulating the device
* queue, etc. can also be used if error handling is to be performed by the
* user land process.
*/
#define TARGIOCCOMMAND _IOWR('C', 4, union ccb)
typedef enum {
UA_NONE = 0x00,
UA_POWER_ON = 0x01,
UA_BUS_RESET = 0x02,
UA_BDR = 0x04
} ua_types;
typedef enum {
CA_NONE = 0x00,
CA_UNIT_ATTN = 0x01,
CA_CMD_SENSE = 0x02
} ca_types;
struct initiator_state {
ua_types pending_ua;
ca_types pending_ca;
struct scsi_sense_data sense_data;
struct ccb_queue held_queue;
};
struct ioc_initiator_state {
u_int initiator_id;
struct initiator_state istate;
};
/*
* Get and Set Contingent Allegiance and Unit Attention state
* presented by the target driver. This is usually used to
* properly report and error condition in response to an incoming
* ATIO request handled by the userland process.
*
* The initiator_id must be properly initialized in the ioc_initiator_state
* structure before calling TARGIOCGETISTATE.
*/
#define TARGIOCGETISTATE _IOWR('C', 6, struct ioc_initiator_state)
#define TARGIOCSETISTATE _IOW('C', 5, struct ioc_initiator_state)
struct old_ioc_alloc_unit {
struct ioc_enable_lun {
path_id_t path_id;
target_id_t target_id;
lun_id_t lun_id;
u_int unit;
int grp6_len;
int grp7_len;
};
struct ioc_alloc_unit {
path_id_t path_id;
target_id_t target_id;
lun_id_t lun_id;
u_int unit;
struct scsi_inquiry_data *inquiry_data;
};
/*
* Allocate and Free a target mode instance. For allocation, the path_id,
* target_id, and lun_id fields must be set. On successful completion
* of the ioctl, the unit field will indicate the unit number of the
* newly created instance. For de-allocation, all fields must match
* an instance in the inactive (i.e. closed) state.
*/
#define OTARGCTLIOALLOCUNIT _IOWR('C', 7, struct old_ioc_alloc_unit)
#define OTARGCTLIOFREEUNIT _IOW('C', 8, struct old_ioc_alloc_unit)
#define TARGCTLIOALLOCUNIT _IOWR('C', 7, struct ioc_alloc_unit)
#define TARGCTLIOFREEUNIT _IOW('C', 8, struct ioc_alloc_unit)
#define TARGIOCENABLE _IOW('C', 5, struct ioc_enable_lun)
#define TARGIOCDISABLE _IO('C', 6)
/*
* Set/clear debugging for this target mode instance
*/
#define TARGIODEBUG _IOW('C', 9, int)
#define TARGIOCDEBUG _IOW('C', 7, int)
TAILQ_HEAD(ccb_queue, ccb_hdr);
#endif /* _CAM_SCSI_SCSI_TARGETIO_H_ */

View File

@ -15,7 +15,7 @@ SRCS+= opt_hw_wdog.h
SRCS+= opt_pt.h
SRCS+= opt_sa.h
SRCS+= opt_ses.h
SRCS+= device_if.h bus_if.h
SRCS+= device_if.h bus_if.h vnode_if.h
SRCS+= cam.c cam_periph.c cam_queue.c
SRCS+= cam_sim.c cam_xpt.c
SRCS+= scsi_all.c scsi_cd.c scsi_ch.c