diff options
author | njl <njl@FreeBSD.org> | 2002-11-22 22:55:51 +0000 |
---|---|---|
committer | njl <njl@FreeBSD.org> | 2002-11-22 22:55:51 +0000 |
commit | aeab0b1a1869e24828db1a4a45df921e4d2673da (patch) | |
tree | 60367d753c053b106c51fbbc085b3a0433b196a5 | |
parent | a52def4e3fb09991ac89994427cc83679843d652 (diff) | |
download | FreeBSD-src-aeab0b1a1869e24828db1a4a45df921e4d2673da.zip FreeBSD-src-aeab0b1a1869e24828db1a4a45df921e4d2673da.tar.gz |
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
-rw-r--r-- | share/examples/scsi_target/Makefile | 5 | ||||
-rw-r--r-- | share/examples/scsi_target/scsi_cmds.c | 664 | ||||
-rw-r--r-- | share/examples/scsi_target/scsi_target.8 | 142 | ||||
-rw-r--r-- | share/examples/scsi_target/scsi_target.c | 1002 | ||||
-rw-r--r-- | share/examples/scsi_target/scsi_target.h | 117 | ||||
-rw-r--r-- | share/man/man4/targ.4 | 143 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_target.c | 2915 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_targetio.h | 110 | ||||
-rw-r--r-- | sys/modules/cam/Makefile | 2 |
9 files changed, 2758 insertions, 2342 deletions
diff --git a/share/examples/scsi_target/Makefile b/share/examples/scsi_target/Makefile index 67f2a4e..abd5a96 100644 --- a/share/examples/scsi_target/Makefile +++ b/share/examples/scsi_target/Makefile @@ -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> diff --git a/share/examples/scsi_target/scsi_cmds.c b/share/examples/scsi_target/scsi_cmds.c new file mode 100644 index 0000000..d624cfb --- /dev/null +++ b/share/examples/scsi_target/scsi_cmds.c @@ -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); +} diff --git a/share/examples/scsi_target/scsi_target.8 b/share/examples/scsi_target/scsi_target.8 new file mode 100644 index 0000000..d2ab3de --- /dev/null +++ b/share/examples/scsi_target/scsi_target.8 @@ -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 . diff --git a/share/examples/scsi_target/scsi_target.c b/share/examples/scsi_target/scsi_target.c index 7c58db2..e1e0a84 100644 --- a/share/examples/scsi_target/scsi_target.c +++ b/share/examples/scsi_target/scsi_target.c @@ -1,8 +1,7 @@ /* - * Sample program to attach to the "targ" processor target, target mode - * peripheral driver and push or receive data. + * SCSI Disk Emulator * - * Copyright (c) 1998 Justin T. Gibbs. + * Copyright (c) 2002 Nate Lawson. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,80 +30,137 @@ #include <sys/types.h> #include <errno.h> +#include <err.h> #include <fcntl.h> -#include <paths.h> -#include <poll.h> #include <signal.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sysexits.h> #include <unistd.h> - +#include <aio.h> +#include <sys/stat.h> +#include <sys/queue.h> +#include <sys/event.h> +#include <sys/param.h> +#include <cam/cam_queue.h> #include <cam/scsi/scsi_all.h> -#include <cam/scsi/scsi_message.h> #include <cam/scsi/scsi_targetio.h> - -char *appname; -int ifd; -char *ifilename; -int ofd; -char *ofilename; -size_t bufsize = 64 * 1024; -void *buf; -char targdevname[80]; -int targctlfd; -int targfd; -int quit; -int debug = 0; -struct ioc_alloc_unit alloc_unit = { +#include <cam/scsi/scsi_message.h> +#include "scsi_target.h" + +/* Maximum amount to transfer per CTIO */ +#define MAX_XFER MAXPHYS +/* Maximum number of allocated CTIOs */ +#define MAX_CTIOS 32 +/* Maximum sector size for emulated volume */ +#define MAX_SECTOR 32768 + +/* Global variables */ +int debug; +u_int32_t volume_size; +size_t sector_size; +size_t buf_size; + +/* Local variables */ +static int targ_fd; +static int kq_fd; +static int file_fd; +static int num_ctios; +static struct ccb_queue pending_queue; +static struct ccb_queue work_queue; +static struct ioc_enable_lun ioc_enlun = { CAM_BUS_WILDCARD, CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD }; -static void pump_events(); -static void cleanup(); -static void handle_exception(); -static void quit_handler(); -static void usage(); +/* Local functions */ +static void cleanup(void); +static int init_ccbs(void); +static void request_loop(void); +static void handle_read(void); +/* static int work_atio(struct ccb_accept_tio *); */ +static void queue_io(struct ccb_scsiio *); +static void run_queue(struct ccb_accept_tio *); +static int work_inot(struct ccb_immed_notify *); +static struct ccb_scsiio * + get_ctio(void); +/* static void free_ccb(union ccb *); */ +static cam_status get_sim_flags(u_int16_t *); +static void rel_simq(void); +static void abort_all_pending(void); +static void usage(void); int main(int argc, char *argv[]) { - int ch; - - appname = *argv; - while ((ch = getopt(argc, argv, "i:o:p:t:l:d")) != -1) { + int ch, unit; + char *file_name, targname[16]; + u_int16_t req_flags, sim_flags; + off_t user_size; + + /* Initialize */ + debug = 0; + req_flags = sim_flags = 0; + user_size = 0; + targ_fd = file_fd = kq_fd = -1; + num_ctios = 0; + sector_size = SECTOR_SIZE; + buf_size = DFLTPHYS; + + /* Prepare resource pools */ + TAILQ_INIT(&pending_queue); + TAILQ_INIT(&work_queue); + + while ((ch = getopt(argc, argv, "AdSTb:c:s:W:")) != -1) { switch(ch) { - case 'i': - if ((ifd = open(optarg, O_RDONLY)) == -1) { - perror(optarg); - exit(EX_NOINPUT); - } - ifilename = optarg; + case 'A': + req_flags |= SID_Addr16; break; - case 'o': - if ((ofd = open(optarg, - O_WRONLY|O_CREAT), 0600) == -1) { - perror(optarg); - exit(EX_CANTCREAT); - } - ofilename = optarg; + case 'd': + debug = 1; break; - case 'p': - alloc_unit.path_id = atoi(optarg); + case 'S': + req_flags |= SID_Sync; break; - case 't': - alloc_unit.target_id = atoi(optarg); + case 'T': + req_flags |= SID_CmdQue; break; - case 'l': - alloc_unit.lun_id = atoi(optarg); + case 'b': + buf_size = atoi(optarg); + if (buf_size < 256 || buf_size > MAX_XFER) + errx(1, "Unreasonable buf size: %s", optarg); break; - case 'd': - debug++; + case 'c': + sector_size = atoi(optarg); + if (sector_size < 512 || sector_size > MAX_SECTOR) + errx(1, "Unreasonable sector size: %s", optarg); + break; + case 's': + user_size = strtoll(optarg, (char **)NULL, /*base*/10); + if (user_size < 0) + errx(1, "Unreasonable volume size: %s", optarg); + break; + case 'W': + req_flags &= ~(SID_WBus16 | SID_WBus32); + switch (atoi(optarg)) { + case 8: + /* Leave req_flags zeroed */ + break; + case 16: + req_flags |= SID_WBus16; + break; + case 32: + req_flags |= SID_WBus32; + break; + default: + warnx("Width %s not supported", optarg); + usage(); + /* NOTREACHED */ + } break; - case '?': default: usage(); /* NOTREACHED */ @@ -112,263 +168,737 @@ main(int argc, char *argv[]) } argc -= optind; argv += optind; - - if (alloc_unit.path_id == CAM_BUS_WILDCARD - || alloc_unit.target_id == CAM_TARGET_WILDCARD - || alloc_unit.lun_id == CAM_LUN_WILDCARD) { - fprintf(stderr, "%s: Incomplete device path specifiled\n", - appname); + + if (argc != 2) usage(); - /* NOTREACHED */ - } - if (argc != 0) { - fprintf(stderr, "%s: Too many arguments\n", appname); + sscanf(argv[0], "%u:%u:%u", &ioc_enlun.path_id, &ioc_enlun.target_id, + &ioc_enlun.lun_id); + file_name = argv[1]; + + if (ioc_enlun.path_id == CAM_BUS_WILDCARD || + ioc_enlun.target_id == CAM_TARGET_WILDCARD || + ioc_enlun.lun_id == CAM_LUN_WILDCARD) { + warnx("Incomplete target path specified"); usage(); /* NOTREACHED */ } - - /* Allocate a new instance */ - if ((targctlfd = open("/dev/targ.ctl", O_RDWR)) == -1) { - perror("/dev/targ.ctl"); - exit(EX_UNAVAILABLE); - } - - if (ioctl(targctlfd, TARGCTLIOALLOCUNIT, &alloc_unit) == -1) { - perror("TARGCTLIOALLOCUNIT"); - exit(EX_SOFTWARE); - } - - snprintf(targdevname, sizeof(targdevname), "%starg%d", _PATH_DEV, - alloc_unit.unit); - - if ((targfd = open(targdevname, O_RDWR)) == -1) { - perror(targdevname); - ioctl(targctlfd, TARGCTLIOFREEUNIT, &alloc_unit); - exit(EX_NOINPUT); - } - - if (ioctl(targfd, TARGIODEBUG, &debug) == -1) { - perror("TARGIODEBUG"); - (void) ioctl(targctlfd, TARGCTLIOFREEUNIT, &alloc_unit); - exit(EX_SOFTWARE); + /* We don't support any vendor-specific commands */ + ioc_enlun.grp6_len = 0; + ioc_enlun.grp7_len = 0; + + /* Open backing store for IO */ + file_fd = open(file_name, O_RDWR); + if (file_fd < 0) + err(1, "open backing store file"); + + /* Check backing store size or use the size user gave us */ + if (user_size == 0) { + struct stat st; + + if (fstat(file_fd, &st) < 0) + err(1, "fstat file"); + volume_size = st.st_size / sector_size; + } else { + volume_size = user_size / sector_size; } + if (volume_size <= 0) + errx(1, "volume must be larger than %d", sector_size); + + /* Go through all the control devices and find one that isn't busy. */ + unit = 0; + do { + snprintf(targname, sizeof(targname), "/dev/targ%d", unit++); + targ_fd = open(targname, O_RDWR); + } while (targ_fd < 0 && errno == EBUSY); + + if (targ_fd < 0) + err(1, "Tried to open %d devices, none available", unit); + + /* The first three are handled by kevent() later */ + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGPROF, SIG_IGN); + signal(SIGALRM, SIG_IGN); + signal(SIGSTOP, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + + /* Register a cleanup handler to run when exiting */ + atexit(cleanup); - buf = malloc(bufsize); + /* Enable listening on the specified LUN */ + if (ioctl(targ_fd, TARGIOCENABLE, &ioc_enlun) != 0) + err(1, "TARGIOCENABLE"); - if (buf == NULL) { - fprintf(stderr, "%s: Could not malloc I/O buffer", appname); - if (debug) { - debug = 0; - (void) ioctl(targfd, TARGIODEBUG, &debug); - } - (void) ioctl(targctlfd, TARGCTLIOFREEUNIT, &alloc_unit); - exit(EX_OSERR); + /* Enable debugging if requested */ + if (debug) { + if (ioctl(targ_fd, TARGIOCDEBUG, &debug) != 0) + err(1, "TARGIOCDEBUG"); } - signal(SIGHUP, quit_handler); - signal(SIGINT, quit_handler); - signal(SIGTERM, quit_handler); + /* Set up inquiry data according to what SIM supports */ + if (get_sim_flags(&sim_flags) != CAM_REQ_CMP) + errx(1, "get_sim_flags"); + if (tcmd_init(req_flags, sim_flags) != 0) + errx(1, "Initializing tcmd subsystem failed"); - atexit(cleanup); + /* Queue ATIOs and INOTs on descriptor */ + if (init_ccbs() != 0) + errx(1, "init_ccbs failed"); - pump_events(); + if (debug) + warnx("main loop beginning"); + request_loop(); - return (0); + exit(0); } static void cleanup() { + struct ccb_hdr *ccb_h; + if (debug) { + warnx("cleanup called"); debug = 0; - (void) ioctl(targfd, TARGIODEBUG, &debug); + ioctl(targ_fd, TARGIOCDEBUG, &debug); + } + ioctl(targ_fd, TARGIOCDISABLE, NULL); + close(targ_fd); + + while ((ccb_h = TAILQ_FIRST(&pending_queue)) != NULL) { + TAILQ_REMOVE(&pending_queue, ccb_h, periph_links.tqe); + free_ccb((union ccb *)ccb_h); } - close(targfd); - if (ioctl(targctlfd, TARGCTLIOFREEUNIT, &alloc_unit) == -1) { - perror("TARGCTLIOFREEUNIT"); + while ((ccb_h = TAILQ_FIRST(&work_queue)) != NULL) { + TAILQ_REMOVE(&work_queue, ccb_h, periph_links.tqe); + free_ccb((union ccb *)ccb_h); } - close(targctlfd); + + if (kq_fd != -1) + close(kq_fd); } -static void -pump_events() +/* Allocate ATIOs/INOTs and queue on HBA */ +static int +init_ccbs() { - struct pollfd targpoll; - - targpoll.fd = targfd; - targpoll.events = POLLRDNORM|POLLWRNORM; + int i; - while (quit == 0) { - int retval; + for (i = 0; i < MAX_INITIATORS; i++) { + struct ccb_accept_tio *atio; + struct atio_descr *a_descr; + struct ccb_immed_notify *inot; - retval = poll(&targpoll, 1, INFTIM); - - if (retval == -1) { - if (errno == EINTR) - continue; - perror("Poll Failed"); - exit(EX_SOFTWARE); + atio = (struct ccb_accept_tio *)malloc(sizeof(*atio)); + if (atio == NULL) { + warn("malloc ATIO"); + return (-1); } - - if (retval == 0) { - perror("Poll returned 0 although timeout infinite???"); - exit(EX_SOFTWARE); - } - - if (retval > 1) { - perror("Poll returned more fds ready than allocated"); - exit(EX_SOFTWARE); + a_descr = (struct atio_descr *)malloc(sizeof(*a_descr)); + if (a_descr == NULL) { + free(atio); + warn("malloc atio_descr"); + return (-1); } - - /* Process events */ - if ((targpoll.revents & POLLERR) != 0) { - handle_exception(); + atio->ccb_h.func_code = XPT_ACCEPT_TARGET_IO; + atio->ccb_h.targ_descr = a_descr; + send_ccb((union ccb *)atio, /*priority*/1); + + inot = (struct ccb_immed_notify *)malloc(sizeof(*inot)); + if (inot == NULL) { + warn("malloc INOT"); + return (-1); } + inot->ccb_h.func_code = XPT_IMMED_NOTIFY; + send_ccb((union ccb *)inot, /*priority*/1); + } - if ((targpoll.revents & POLLRDNORM) != 0) { - retval = read(targfd, buf, bufsize); + return (0); +} - if (retval == -1) { - perror("Read from targ failed"); - /* Go look for exceptions */ +static void +request_loop() +{ + struct kevent events[MAX_EVENTS]; + struct timespec ts, *tptr; + int quit; + + /* Register kqueue for event notification */ + if ((kq_fd = kqueue()) < 0) + err(1, "init kqueue"); + + /* Set up some default events */ + EV_SET(&events[0], SIGHUP, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0, 0, 0); + EV_SET(&events[1], SIGINT, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0, 0, 0); + EV_SET(&events[2], SIGTERM, EVFILT_SIGNAL, EV_ADD|EV_ENABLE, 0, 0, 0); + EV_SET(&events[3], targ_fd, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, 0); + if (kevent(kq_fd, events, 4, NULL, 0, NULL) < 0) + err(1, "kevent signal registration"); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + tptr = NULL; + quit = 0; + + /* Loop until user signal */ + while (quit == 0) { + int retval, i; + struct ccb_hdr *ccb_h; + + /* Check for the next signal, read ready, or AIO completion */ + retval = kevent(kq_fd, NULL, 0, events, MAX_EVENTS, tptr); + if (retval < 0) { + if (errno == EINTR) { + if (debug) + warnx("EINTR, looping"); continue; - } else { - retval = write(ofd, buf, retval); - if (retval == -1) { - perror("Write to file failed"); - } + } + else { + err(1, "kevent failed"); } + } else if (retval > MAX_EVENTS) { + errx(1, "kevent returned more events than allocated?"); } - if ((targpoll.revents & POLLWRNORM) != 0) { - int amount_read; + /* Process all received events. */ + for (i = 0; i < retval; i++) { + if ((events[i].flags & EV_ERROR) != 0) + errx(1, "kevent registration failed"); + + switch (events[i].filter) { + case EVFILT_READ: + if (debug) + warnx("read ready"); + handle_read(); + break; + case EVFILT_AIO: + { + struct ccb_scsiio *ctio; + struct ctio_descr *c_descr; + if (debug) + warnx("aio ready"); + + ctio = (struct ccb_scsiio *)events[i].udata; + c_descr = (struct ctio_descr *) + ctio->ccb_h.targ_descr; + c_descr->event = AIO_DONE; + /* Queue on the appropriate ATIO */ + queue_io(ctio); + /* Process any queued completions. */ + run_queue(c_descr->atio); + break; + } + case EVFILT_SIGNAL: + if (debug) + warnx("signal ready, setting quit"); + quit = 1; + break; + default: + warnx("unknown event %#x", events[i].filter); + break; + } - retval = read(ifd, buf, bufsize); - if (retval == -1) { - perror("Read from file failed"); - exit(EX_SOFTWARE); + if (debug) + warnx("event done"); + } + + /* Grab the first CCB and perform one work unit. */ + if ((ccb_h = TAILQ_FIRST(&work_queue)) != NULL) { + union ccb *ccb; + + ccb = (union ccb *)ccb_h; + switch (ccb_h->func_code) { + case XPT_ACCEPT_TARGET_IO: + /* Start one more transfer. */ + retval = work_atio(&ccb->atio); + break; + case XPT_IMMED_NOTIFY: + retval = work_inot(&ccb->cin); + break; + default: + warnx("Unhandled ccb type %#x on workq", + ccb_h->func_code); + abort(); + /* NOTREACHED */ } - amount_read = retval; - retval = write(targfd, buf, retval); - if (retval == -1) { - perror("Write to targ failed"); - retval = 0; + /* Assume work function handled the exception */ + if ((ccb_h->status & CAM_DEV_QFRZN) != 0) { + warnx("Queue frozen receiving CCB, releasing"); + rel_simq(); } - /* Backup in our input stream on short writes */ - if (retval != amount_read) - lseek(ifd, retval - amount_read, SEEK_CUR); + /* No more work needed for this command. */ + if (retval == 0) { + TAILQ_REMOVE(&work_queue, ccb_h, + periph_links.tqe); + } } + + /* + * Poll for new events (i.e. completions) while we + * are processing CCBs on the work_queue. Once it's + * empty, use an infinite wait. + */ + if (!TAILQ_EMPTY(&work_queue)) + tptr = &ts; + else + tptr = NULL; } } +/* CCBs are ready from the kernel */ static void -handle_exception() +handle_read() { - targ_exception exceptions; + union ccb *ccb_array[MAX_INITIATORS], *ccb; + int ccb_count, i; - if (ioctl(targfd, TARGIOCFETCHEXCEPTION, &exceptions) == -1) { - perror("TARGIOCFETCHEXCEPTION"); - exit(EX_SOFTWARE); + ccb_count = read(targ_fd, ccb_array, sizeof(ccb_array)); + if (ccb_count <= 0) { + warn("read ccb ptrs"); + return; + } + ccb_count /= sizeof(union ccb *); + if (ccb_count < 1) { + warnx("truncated read ccb ptr?"); + return; } - printf("Saw exceptions %x\n", exceptions); - if ((exceptions & TARG_EXCEPT_DEVICE_INVALID) != 0) { - /* Device went away. Nothing more to do. */ - printf("Device went away\n"); - exit(0); + for (i = 0; i < ccb_count; i++) { + ccb = ccb_array[i]; + TAILQ_REMOVE(&pending_queue, &ccb->ccb_h, periph_links.tqe); + + switch (ccb->ccb_h.func_code) { + case XPT_ACCEPT_TARGET_IO: + { + struct ccb_accept_tio *atio; + struct atio_descr *a_descr; + + /* Initialize ATIO descr for this transaction */ + atio = &ccb->atio; + a_descr = (struct atio_descr *)atio->ccb_h.targ_descr; + bzero(a_descr, sizeof(*a_descr)); + TAILQ_INIT(&a_descr->cmplt_io); + a_descr->flags = atio->ccb_h.flags & + (CAM_DIS_DISCONNECT | CAM_TAG_ACTION_VALID); + /* XXX add a_descr->priority */ + if ((atio->ccb_h.flags & CAM_CDB_POINTER) == 0) + a_descr->cdb = atio->cdb_io.cdb_bytes; + else + a_descr->cdb = atio->cdb_io.cdb_ptr; + + /* ATIOs are processed in FIFO order */ + TAILQ_INSERT_TAIL(&work_queue, &ccb->ccb_h, + periph_links.tqe); + break; + } + case XPT_CONT_TARGET_IO: + { + struct ccb_scsiio *ctio; + struct ctio_descr *c_descr; + + ctio = &ccb->ctio; + c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr; + c_descr->event = CTIO_DONE; + /* Queue on the appropriate ATIO */ + queue_io(ctio); + /* Process any queued completions. */ + run_queue(c_descr->atio); + break; + } + case XPT_IMMED_NOTIFY: + /* INOTs are handled with priority */ + TAILQ_INSERT_HEAD(&work_queue, &ccb->ccb_h, + periph_links.tqe); + break; + default: + warnx("Unhandled ccb type %#x in handle_read", + ccb->ccb_h.func_code); + break; + } } +} - if ((exceptions & TARG_EXCEPT_UNKNOWN_ATIO) != 0) { - struct ccb_accept_tio atio; - struct ioc_initiator_state ioc_istate; +/* Process an ATIO CCB from the kernel */ +int +work_atio(struct ccb_accept_tio *atio) +{ + struct ccb_scsiio *ctio; + struct atio_descr *a_descr; + struct ctio_descr *c_descr; + cam_status status; + int ret; + + if (debug) + warnx("Working on ATIO %p", atio); + + a_descr = (struct atio_descr *)atio->ccb_h.targ_descr; + + /* Get a CTIO and initialize it according to our known parameters */ + ctio = get_ctio(); + if (ctio == NULL) + return (1); + ret = 0; + ctio->ccb_h.flags = a_descr->flags; + ctio->tag_id = atio->tag_id; + ctio->init_id = atio->init_id; + /* XXX priority needs to be added to a_descr */ + c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr; + c_descr->atio = atio; + if ((a_descr->flags & CAM_DIR_IN) != 0) + c_descr->offset = a_descr->base_off + a_descr->targ_req; + else if ((a_descr->flags & CAM_DIR_MASK) == CAM_DIR_OUT) + c_descr->offset = a_descr->base_off + a_descr->init_req; + + /* + * Return a check condition if there was an error while + * receiving this ATIO. + */ + if (atio->sense_len != 0) { struct scsi_sense_data *sense; - union ccb ccb; - if (ioctl(targfd, TARGIOCFETCHATIO, &atio) == -1) { - perror("TARGIOCFETCHATIO"); - exit(EX_SOFTWARE); + if (debug) { + warnx("ATIO with %u bytes sense received", + atio->sense_len); } + sense = &atio->sense_data; + tcmd_sense(ctio->init_id, ctio, sense->flags, + sense->add_sense_code, sense->add_sense_code_qual); + send_ccb((union ccb *)ctio, /*priority*/1); + return (0); + } + + status = atio->ccb_h.status & CAM_STATUS_MASK; + switch (status) { + case CAM_CDB_RECVD: + ret = tcmd_handle(atio, ctio, ATIO_WORK); + break; + case CAM_REQ_ABORTED: + /* Requeue on HBA */ + TAILQ_REMOVE(&work_queue, &atio->ccb_h, periph_links.tqe); + send_ccb((union ccb *)atio, /*priority*/1); + ret = 1; + break; + default: + warnx("ATIO completed with unhandled status %#x", status); + abort(); + /* NOTREACHED */ + break; + } + + return (ret); +} + +static void +queue_io(struct ccb_scsiio *ctio) +{ + struct ccb_hdr *ccb_h; + struct io_queue *ioq; + struct ctio_descr *c_descr, *curr_descr; + + c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr; + /* If the completion is for a specific ATIO, queue in order */ + if (c_descr->atio != NULL) { + struct atio_descr *a_descr; - printf("Ignoring unhandled command 0x%x for Id %d\n", - atio.cdb_io.cdb_bytes[0], atio.init_id); + a_descr = (struct atio_descr *)c_descr->atio->ccb_h.targ_descr; + ioq = &a_descr->cmplt_io; + } else { + errx(1, "CTIO %p has NULL ATIO", ctio); + } - ioc_istate.initiator_id = atio.init_id; - if (ioctl(targfd, TARGIOCGETISTATE, &ioc_istate) == -1) { - perror("TARGIOCGETISTATE"); - exit(EX_SOFTWARE); + /* Insert in order, sorted by offset */ + if (!TAILQ_EMPTY(ioq)) { + TAILQ_FOREACH_REVERSE(ccb_h, ioq, io_queue, periph_links.tqe) { + curr_descr = (struct ctio_descr *)ccb_h->targ_descr; + if (curr_descr->offset <= c_descr->offset) { + TAILQ_INSERT_AFTER(ioq, ccb_h, &ctio->ccb_h, + periph_links.tqe); + break; + } + if (TAILQ_PREV(ccb_h, io_queue, periph_links.tqe) + == NULL) { + TAILQ_INSERT_BEFORE(ccb_h, &ctio->ccb_h, + periph_links.tqe); + break; + } } + } else { + TAILQ_INSERT_HEAD(ioq, &ctio->ccb_h, periph_links.tqe); + } +} - /* Send back Illegal Command code status */ - ioc_istate.istate.pending_ca |= CA_CMD_SENSE; - sense = &ioc_istate.istate.sense_data; - bzero(sense, sizeof(*sense)); - sense->error_code = SSD_CURRENT_ERROR; - sense->flags = SSD_KEY_ILLEGAL_REQUEST; - sense->add_sense_code = 0x20; - sense->add_sense_code_qual = 0x00; - sense->extra_len = offsetof(struct scsi_sense_data, fru) - - offsetof(struct scsi_sense_data, extra_len); - - if (ioctl(targfd, TARGIOCSETISTATE, &ioc_istate) == -1) { - perror("TARGIOCSETISTATE"); - exit(EX_SOFTWARE); +/* + * Go through all completed AIO/CTIOs for a given ATIO and advance data + * counts, start continuation IO, etc. + */ +static void +run_queue(struct ccb_accept_tio *atio) +{ + struct atio_descr *a_descr; + struct ccb_hdr *ccb_h; + int sent_status, event; + + if (atio == NULL) + return; + + a_descr = (struct atio_descr *)atio->ccb_h.targ_descr; + + while ((ccb_h = TAILQ_FIRST(&a_descr->cmplt_io)) != NULL) { + struct ccb_scsiio *ctio; + struct ctio_descr *c_descr; + + ctio = (struct ccb_scsiio *)ccb_h; + c_descr = (struct ctio_descr *)ctio->ccb_h.targ_descr; + + /* If completed item is in range, call handler */ + if ((c_descr->event == AIO_DONE && + c_descr->offset == a_descr->base_off + a_descr->targ_ack) + || (c_descr->event == CTIO_DONE && + c_descr->offset == a_descr->base_off + a_descr->init_ack)) { + sent_status = (ccb_h->flags & CAM_SEND_STATUS) != 0; + event = c_descr->event; + + TAILQ_REMOVE(&a_descr->cmplt_io, ccb_h, + periph_links.tqe); + tcmd_handle(atio, ctio, c_descr->event); + + /* If entire transfer complete, send back ATIO */ + if (sent_status != 0 && event == CTIO_DONE) + send_ccb((union ccb *)atio, /*priority*/1); + } else { + /* Gap in offsets so wait until later callback */ + if (debug) + warnx("IO %p out of order", ccb_h); + break; } + } +} - /* Clear the exception so the kernel will take our response */ - if (ioctl(targfd, TARGIOCCLEAREXCEPTION, &exceptions) == -1) { - perror("TARGIOCCLEAREXCEPTION"); - exit(EX_SOFTWARE); +static int +work_inot(struct ccb_immed_notify *inot) +{ + cam_status status; + int sense; + + if (debug) + warnx("Working on INOT %p", inot); + + status = inot->ccb_h.status; + sense = (status & CAM_AUTOSNS_VALID) != 0; + status &= CAM_STATUS_MASK; + + switch (status) { + case CAM_SCSI_BUS_RESET: + tcmd_ua(CAM_TARGET_WILDCARD, UA_BUS_RESET); + abort_all_pending(); + break; + case CAM_BDR_SENT: + tcmd_ua(CAM_TARGET_WILDCARD, UA_BDR); + abort_all_pending(); + break; + case CAM_MESSAGE_RECV: + switch (inot->message_args[0]) { + case MSG_TASK_COMPLETE: + case MSG_INITIATOR_DET_ERR: + case MSG_ABORT_TASK_SET: + case MSG_MESSAGE_REJECT: + case MSG_NOOP: + case MSG_PARITY_ERROR: + case MSG_TARGET_RESET: + case MSG_ABORT_TASK: + case MSG_CLEAR_TASK_SET: + default: + warnx("INOT message %#x", inot->message_args[0]); + break; } + break; + case CAM_REQ_ABORTED: + warnx("INOT %p aborted", inot); + break; + default: + warnx("Unhandled INOT status %#x", status); + break; + } - bzero(&ccb, sizeof(ccb)); - cam_fill_ctio(&ccb.csio, - /*retries*/2, - /*cbfcnp*/NULL, - CAM_DIR_NONE | CAM_SEND_STATUS, - (atio.ccb_h.flags & CAM_TAG_ACTION_VALID)? - MSG_SIMPLE_Q_TAG : 0, - atio.tag_id, - atio.init_id, - SCSI_STATUS_CHECK_COND, - /*data_ptr*/NULL, - /*dxfer_len*/0, - /*timeout*/5 * 1000); - /* - * Make sure that periph_priv pointers are clean. - */ - bzero(&ccb.ccb_h.periph_priv, sizeof ccb.ccb_h.periph_priv); + /* If there is sense data, use it */ + if (sense != 0) { + struct scsi_sense_data *sense; - if (ioctl(targfd, TARGIOCCOMMAND, &ccb) == -1) { - perror("TARGIOCCOMMAND"); - exit(EX_SOFTWARE); - } - - } else { - if (ioctl(targfd, TARGIOCCLEAREXCEPTION, &exceptions) == -1) { - perror("TARGIOCCLEAREXCEPTION"); - exit(EX_SOFTWARE); - } + sense = &inot->sense_data; + tcmd_sense(inot->initiator_id, NULL, sense->flags, + sense->add_sense_code, sense->add_sense_code_qual); + if (debug) + warnx("INOT has sense: %#x", sense->flags); } + /* Requeue on SIM */ + TAILQ_REMOVE(&work_queue, &inot->ccb_h, periph_links.tqe); + send_ccb((union ccb *)inot, /*priority*/1); + + return (1); } -static void -quit_handler(int signum) +void +send_ccb(union ccb *ccb, int priority) { - quit = 1; + if (debug) + warnx("sending ccb (%#x)", ccb->ccb_h.func_code); + ccb->ccb_h.pinfo.priority = priority; + if (XPT_FC_IS_QUEUED(ccb)) { + TAILQ_INSERT_TAIL(&pending_queue, &ccb->ccb_h, + periph_links.tqe); + } + if (write(targ_fd, &ccb, sizeof(ccb)) != sizeof(ccb)) { + warn("write ccb"); + ccb->ccb_h.status = CAM_PROVIDE_FAIL; + } } -static void -usage() +/* Return a CTIO/descr/buf combo from the freelist or malloc one */ +static struct ccb_scsiio * +get_ctio() { + struct ccb_scsiio *ctio; + struct ctio_descr *c_descr; + struct sigevent *se; + + if (num_ctios == MAX_CTIOS) + return (NULL); - (void)fprintf(stderr, -"usage: %-16s [ -d ] [-o output_file] [-i input_file] -p path -t target -l lun\n", - appname); + ctio = (struct ccb_scsiio *)malloc(sizeof(*ctio)); + if (ctio == NULL) { + warn("malloc CTIO"); + return (NULL); + } + c_descr = (struct ctio_descr *)malloc(sizeof(*c_descr)); + if (c_descr == NULL) { + free(ctio); + warn("malloc ctio_descr"); + return (NULL); + } + c_descr->buf = malloc(buf_size); + if (c_descr->buf == NULL) { + free(c_descr); + free(ctio); + warn("malloc backing store"); + return (NULL); + } + num_ctios++; + + /* Initialize CTIO, CTIO descr, and AIO */ + ctio->ccb_h.func_code = XPT_CONT_TARGET_IO; + ctio->ccb_h.retry_count = 2; + ctio->ccb_h.timeout = 5; + ctio->data_ptr = c_descr->buf; + ctio->ccb_h.targ_descr = c_descr; + c_descr->aiocb.aio_buf = c_descr->buf; + c_descr->aiocb.aio_fildes = file_fd; + se = &c_descr->aiocb.aio_sigevent; + se->sigev_notify = SIGEV_KEVENT; + se->sigev_notify_kqueue = kq_fd; + se->sigev_value.sigval_ptr = ctio; + + return (ctio); +} - exit(EX_USAGE); +void +free_ccb(union ccb *ccb) +{ + switch (ccb->ccb_h.func_code) { + case XPT_CONT_TARGET_IO: + { + struct ctio_descr *c_descr; + + c_descr = (struct ctio_descr *)ccb->ccb_h.targ_descr; + free(c_descr->buf); + num_ctios--; + /* FALLTHROUGH */ + } + case XPT_ACCEPT_TARGET_IO: + free(ccb->ccb_h.targ_descr); + /* FALLTHROUGH */ + case XPT_IMMED_NOTIFY: + default: + free(ccb); + break; + } } +static cam_status +get_sim_flags(u_int16_t *flags) +{ + struct ccb_pathinq cpi; + cam_status status; + + /* Find SIM capabilities */ + bzero(&cpi, sizeof(cpi)); + cpi.ccb_h.func_code = XPT_PATH_INQ; + send_ccb((union ccb *)&cpi, /*priority*/1); + status = cpi.ccb_h.status & CAM_STATUS_MASK; + if (status != CAM_REQ_CMP) { + fprintf(stderr, "CPI failed, status %#x\n", status); + return (status); + } + + /* Can only enable on controllers that support target mode */ + if ((cpi.target_sprt & PIT_PROCESSOR) == 0) { + fprintf(stderr, "HBA does not support target mode\n"); + status = CAM_PATH_INVALID; + return (status); + } + + *flags = cpi.hba_inquiry; + return (status); +} + +static void +rel_simq() +{ + struct ccb_relsim crs; + + bzero(&crs, sizeof(crs)); + crs.ccb_h.func_code = XPT_REL_SIMQ; + crs.release_flags = RELSIM_RELEASE_AFTER_QEMPTY; + crs.openings = 0; + crs.release_timeout = 0; + crs.qfrozen_cnt = 0; + send_ccb((union ccb *)&crs, /*priority*/0); +} + +/* Cancel all pending CCBs. */ +static void +abort_all_pending() +{ + struct ccb_abort cab; + struct ccb_hdr *ccb_h; + + if (debug) + warnx("abort_all_pending"); + + bzero(&cab, sizeof(cab)); + cab.ccb_h.func_code = XPT_ABORT; + TAILQ_FOREACH(ccb_h, &pending_queue, periph_links.tqe) { + if (debug) + warnx("Aborting pending CCB %p\n", ccb_h); + cab.abort_ccb = (union ccb *)ccb_h; + send_ccb((union ccb *)&cab, /*priority*/1); + if (cab.ccb_h.status != CAM_REQ_CMP) { + warnx("Unable to abort CCB, status %#x\n", + cab.ccb_h.status); + } + } +} + +static void +usage() +{ + fprintf(stderr, + "Usage: scsi_target [-AdST] [-b bufsize] [-c sectorsize]\n" + "\t\t[-r numbufs] [-s volsize] [-W 8,16,32]\n" + "\t\tbus:target:lun filename\n"); + exit(1); +} diff --git a/share/examples/scsi_target/scsi_target.h b/share/examples/scsi_target/scsi_target.h new file mode 100644 index 0000000..7e179ff --- /dev/null +++ b/share/examples/scsi_target/scsi_target.h @@ -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 */ diff --git a/share/man/man4/targ.4 b/share/man/man4/targ.4 new file mode 100644 index 0000000..30bfeff --- /dev/null +++ b/share/man/man4/targ.4 @@ -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 . diff --git a/sys/cam/scsi/scsi_target.c b/sys/cam/scsi/scsi_target.c index 2c8f32e..b775c43 100644 --- a/sys/cam/scsi/scsi_target.c +++ b/sys/cam/scsi/scsi_target.c @@ -1,7 +1,8 @@ /* - * Implementation of a simple Target Mode SCSI Proccessor Target driver for CAM. + * Generic SCSI Target Kernel Mode Driver * - * Copyright (c) 1998, 1999, 2001 Justin T. Gibbs. + * Copyright (c) 2002 Nate Lawson. + * Copyright (c) 1998, 1999, 2001, 2002 Justin T. Gibbs. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,17 +30,17 @@ */ #include <sys/param.h> -#include <sys/queue.h> #include <sys/systm.h> #include <sys/kernel.h> -#include <sys/types.h> -#include <sys/bio.h> #include <sys/conf.h> -#include <sys/devicestat.h> +#include <sys/event.h> #include <sys/malloc.h> #include <sys/poll.h> #include <sys/selinfo.h> #include <sys/uio.h> +#include <sys/vnode.h> +#include <sys/queue.h> +#include <sys/devicestat.h> #include <cam/cam.h> #include <cam/cam_ccb.h> @@ -47,131 +48,66 @@ #include <cam/cam_queue.h> #include <cam/cam_xpt_periph.h> #include <cam/cam_debug.h> - -#include <cam/scsi/scsi_all.h> -#include <cam/scsi/scsi_pt.h> #include <cam/scsi/scsi_targetio.h> -#include <cam/scsi/scsi_message.h> - -typedef enum { - TARG_STATE_NORMAL, - TARG_STATE_EXCEPTION, - TARG_STATE_TEARDOWN -} targ_state; - -typedef enum { - TARG_FLAG_NONE = 0x00, - TARG_FLAG_SEND_EOF = 0x01, - TARG_FLAG_RECEIVE_EOF = 0x02, - TARG_FLAG_LUN_ENABLED = 0x04 -} targ_flags; - -typedef enum { - TARG_CCB_NONE = 0x00, - TARG_CCB_WAITING = 0x01, - TARG_CCB_HELDQ = 0x02, - TARG_CCB_ABORT_TO_HELDQ = 0x04 -} targ_ccb_flags; - -#define MAX_ACCEPT 16 -#define MAX_IMMEDIATE 16 -#define MAX_BUF_SIZE 256 /* Max inquiry/sense/mode page transfer */ -#define MAX_INITIATORS 256 /* includes widest fibre channel for now */ - -#define MIN(a, b) ((a > b) ? b : a) -#define TARG_CONTROL_UNIT 0xffff00ff -#define TARG_IS_CONTROL_DEV(d) (minor((d)) == TARG_CONTROL_UNIT) - -#define TARG_TAG_WILDCARD ((u_int)~0) +/* Transaction information attached to each CCB sent by the user */ +struct targ_cmd_descr { + struct cam_periph_map_info mapinfo; + TAILQ_ENTRY(targ_cmd_descr) tqe; + union ccb *user_ccb; + int priority; + int func_code; +}; -/* Offsets into our private CCB area for storing accept information */ -#define ccb_flags ppriv_field0 -#define ccb_descr ppriv_ptr1 +/* Offset into the private CCB area for storing our descriptor */ +#define targ_descr periph_priv.entries[1].ptr -/* We stick a pointer to the originating accept TIO in each continue I/O CCB */ -#define ccb_atio ppriv_ptr1 +TAILQ_HEAD(descr_queue, targ_cmd_descr); -/* - * When we're constructing a unit, we point to passed in user inquiry data here. - */ -#define ccb_inq ppriv_ptr1 +typedef enum { + TARG_STATE_RESV = 0x00, /* Invalid state */ + TARG_STATE_OPENED = 0x01, /* Device opened, softc initialized */ + TARG_STATE_LUN_ENABLED = 0x02 /* Device enabled for a path */ +} targ_state; +/* Per-instance device software context */ struct targ_softc { - /* CTIOs pending on the controller */ - struct ccb_queue pending_queue; - - /* ATIOs awaiting CTIO resources from the XPT */ - struct ccb_queue work_queue; - - /* - * ATIOs for SEND operations waiting for 'write' - * buffer resources from our userland daemon. - */ - struct ccb_queue snd_ccb_queue; + /* CCBs (CTIOs, ATIOs, INOTs) pending on the controller */ + struct ccb_queue pending_ccb_queue; - /* - * ATIOs for RCV operations waiting for 'read' - * buffer resources from our userland daemon. - */ - struct ccb_queue rcv_ccb_queue; + /* Command descriptors awaiting CTIO resources from the XPT */ + struct descr_queue work_queue; - /* - * ATIOs for commands unknown to the kernel driver. - * These are queued for the userland daemon to - * consume. - */ - struct ccb_queue unknown_atio_queue; + /* Command descriptors that have been aborted back to the user. */ + struct descr_queue abort_queue; /* - * Userland buffers for SEND commands waiting for - * SEND ATIOs to be queued by an initiator. + * Queue of CCBs that have been copied out to userland, but our + * userland daemon has not yet seen. */ - struct bio_queue_head snd_bio_queue; - - /* - * Userland buffers for RCV commands waiting for - * RCV ATIOs to be queued by an initiator. - */ - struct bio_queue_head rcv_bio_queue; - struct devstat device_stats; - dev_t targ_dev; - struct selinfo snd_select; - struct selinfo rcv_select; - targ_state state; - targ_flags flags; - targ_exception exceptions; - u_int init_level; - u_int inq_data_len; - struct scsi_inquiry_data *inq_data; - struct ccb_accept_tio *accept_tio_list; - struct ccb_hdr_slist immed_notify_slist; - struct initiator_state istate[MAX_INITIATORS]; -}; - -struct targ_cmd_desc { - struct ccb_accept_tio* atio_link; - u_int data_resid; /* How much left to transfer */ - u_int data_increment;/* Amount to send before next disconnect */ - void* data; /* The data. Can be from backing_store or not */ - void* backing_store;/* Backing store allocated for this descriptor*/ - struct bio *bp; /* Buffer for this transfer */ - u_int max_size; /* Size of backing_store */ - u_int32_t timeout; - u_int32_t - user_atio : 1, /* user ATIO (will define last CTIO) */ - status : 8; /* Status to return to initiator */ + struct ccb_queue user_ccb_queue; + + struct cam_periph *periph; + struct cam_path *path; + targ_state state; + struct selinfo read_select; + struct devstat device_stats; + struct mtx mtx; }; -static d_open_t targopen; -static d_close_t targclose; -static d_read_t targread; -static d_write_t targwrite; -static d_ioctl_t targioctl; -static d_poll_t targpoll; -static d_strategy_t targstrategy; - -#define TARG_CDEV_MAJOR 65 +static d_open_t targopen; +static d_close_t targclose; +static d_read_t targread; +static d_write_t targwrite; +static d_ioctl_t targioctl; +static d_poll_t targpoll; +static d_kqfilter_t targkqfilter; +static void targreadfiltdetach(struct knote *kn); +static int targreadfilt(struct knote *kn, long hint); +static struct filterops targread_filtops = + { 1, NULL, targreadfiltdetach, targreadfilt }; + +#define TARG_CDEV_MAJOR 65 static struct cdevsw targ_cdevsw = { /* open */ targopen, /* close */ targclose, @@ -180,805 +116,644 @@ static struct cdevsw targ_cdevsw = { /* ioctl */ targioctl, /* poll */ targpoll, /* mmap */ nommap, - /* strategy */ targstrategy, + /* strategy */ nostrategy, /* name */ "targ", /* maj */ TARG_CDEV_MAJOR, /* dump */ nodump, /* psize */ nopsize, - /* flags */ 0, + /* flags */ D_KQFILTER, + /* kqfilter */ targkqfilter }; -static int targsendccb(struct cam_periph *periph, union ccb *ccb, - union ccb *inccb); +static cam_status targendislun(struct cam_path *path, int enable, + int grp6_len, int grp7_len); +static cam_status targenable(struct targ_softc *softc, + struct cam_path *path, + int grp6_len, int grp7_len); +static cam_status targdisable(struct targ_softc *softc); +static periph_ctor_t targctor; +static periph_dtor_t targdtor; +static periph_start_t targstart; +static int targusermerge(struct targ_softc *softc, + struct targ_cmd_descr *descr, + union ccb *ccb); +static int targsendccb(struct targ_softc *softc, union ccb *ccb, + struct targ_cmd_descr *descr); +static void targdone(struct cam_periph *periph, + union ccb *done_ccb); +static int targreturnccb(struct targ_softc *softc, + union ccb *ccb); +static union ccb * targgetccb(struct targ_softc *softc, xpt_opcode type, + int priority); +static void targfreeccb(struct targ_softc *softc, union ccb *ccb); +static struct targ_cmd_descr * + targgetdescr(struct targ_softc *softc); static periph_init_t targinit; +static void targclone(void *arg, char *name, int namelen, + dev_t *dev); static void targasync(void *callback_arg, u_int32_t code, - struct cam_path *path, void *arg); -static int targallocinstance(void *, u_long); -static int targfreeinstance(struct ioc_alloc_unit *); -static cam_status targenlun(struct cam_periph *periph); -static cam_status targdislun(struct cam_periph *periph); -static periph_ctor_t targctor; -static periph_dtor_t targdtor; -static void targrunqueue(struct cam_periph *periph, - struct targ_softc *softc); -static periph_start_t targstart; -static void targdone(struct cam_periph *periph, - union ccb *done_ccb); -static void targfireexception(struct cam_periph *periph, - struct targ_softc *softc); -static void targinoterror(struct cam_periph *periph, - struct targ_softc *softc, - struct ccb_immed_notify *inot); -static int targerror(union ccb *ccb, u_int32_t cam_flags, - u_int32_t sense_flags); -static struct targ_cmd_desc* allocdescr(void); -static void freedescr(struct targ_cmd_desc *buf); -static void fill_sense(struct targ_softc *softc, - u_int initiator_id, u_int error_code, - u_int sense_key, u_int asc, u_int ascq); -static void copy_sense(struct targ_softc *softc, - struct initiator_state *istate, - u_int8_t *sense_buffer, size_t sense_len); -static void set_unit_attention_cond(struct cam_periph *periph, - u_int initiator_id, ua_types ua); -static void set_ca_condition(struct cam_periph *periph, - u_int initiator_id, ca_types ca); -static void abort_pending_transactions(struct cam_periph *periph, - u_int initiator_id, u_int tag_id, - int errno, int to_held_queue); - + struct cam_path *path, void *arg); +static void abort_all_pending(struct targ_softc *softc); +static void notify_user(struct targ_softc *softc); +static int targcamstatus(cam_status status); +static size_t targccblen(xpt_opcode func_code); + static struct periph_driver targdriver = { targinit, "targ", TAILQ_HEAD_INITIALIZER(targdriver.units), /* generation */ 0 }; - PERIPHDRIVER_DECLARE(targ, targdriver); -static dev_t targ_ctl_dev; +static struct mtx targ_mtx; +#define TARG_LOCK(softc) mtx_lock(&(softc)->mtx) +#define TARG_UNLOCK(softc) mtx_unlock(&(softc)->mtx) -static void -targinit(void) -{ - targ_ctl_dev = make_dev(&targ_cdevsw, TARG_CONTROL_UNIT, UID_ROOT, - GID_OPERATOR, 0600, "%s.ctl", "targ"); - if (targ_ctl_dev == (dev_t) 0) { - printf("targ: failed to create control dev\n"); - } -} +static MALLOC_DEFINE(M_TARG, "TARG", "TARG data"); -static void -targasync(void *callback_arg, u_int32_t code, - struct cam_path *path, void *arg) +/* Create softc and initialize it. Only one proc can open each targ device. */ +static int +targopen(dev_t dev, int flags, int fmt, struct thread *td) { - struct cam_periph *periph; struct targ_softc *softc; - periph = (struct cam_periph *)callback_arg; - softc = (struct targ_softc *)periph->softc; - switch (code) { - case AC_PATH_DEREGISTERED: - { - /* XXX Implement */ - break; - } - default: - break; + mtx_lock(&targ_mtx); + if (dev->si_drv1 != 0) { + mtx_unlock(&targ_mtx); + return (EBUSY); } + + /* Mark device busy before any potentially blocking operations */ + dev->si_drv1 = (void *)~0; + mtx_unlock(&targ_mtx); + + /* Create the targ device, allocate its softc, initialize it */ + if ((dev->si_flags & SI_NAMED) == 0) { + make_dev(&targ_cdevsw, minor(dev), UID_ROOT, GID_WHEEL, 0600, + "targ%d", dev2unit(dev)); + } + MALLOC(softc, struct targ_softc *, sizeof(*softc), M_TARG, + M_WAITOK | M_ZERO); + dev->si_drv1 = softc; + softc->state = TARG_STATE_OPENED; + softc->periph = NULL; + softc->path = NULL; + mtx_init(&softc->mtx, devtoname(dev), "targ cdev", MTX_DEF); + + TAILQ_INIT(&softc->pending_ccb_queue); + TAILQ_INIT(&softc->work_queue); + TAILQ_INIT(&softc->abort_queue); + TAILQ_INIT(&softc->user_ccb_queue); + + return (0); } -/* Attempt to enable our lun */ -static cam_status -targenlun(struct cam_periph *periph) +/* Disable LUN if enabled and teardown softc */ +static int +targclose(dev_t dev, int flag, int fmt, struct thread *td) { - union ccb immed_ccb; - struct targ_softc *softc; - cam_status status; - int i; - - softc = (struct targ_softc *)periph->softc; - - if ((softc->flags & TARG_FLAG_LUN_ENABLED) != 0) - return (CAM_REQ_CMP); - - xpt_setup_ccb(&immed_ccb.ccb_h, periph->path, /*priority*/1); - immed_ccb.ccb_h.func_code = XPT_EN_LUN; - - /* Don't need support for any vendor specific commands */ - immed_ccb.cel.grp6_len = 0; - immed_ccb.cel.grp7_len = 0; - immed_ccb.cel.enable = 1; - xpt_action(&immed_ccb); - status = immed_ccb.ccb_h.status; - if (status != CAM_REQ_CMP) { - xpt_print_path(periph->path); - printf("targenlun - Enable Lun Rejected with status 0x%x\n", - status); - return (status); - } - - softc->flags |= TARG_FLAG_LUN_ENABLED; - - /* - * Build up a buffer of accept target I/O - * operations for incoming selections. - */ - for (i = 0; i < MAX_ACCEPT; i++) { - struct ccb_accept_tio *atio; + struct targ_softc *softc; + int error; - atio = (struct ccb_accept_tio*)malloc(sizeof(*atio), M_DEVBUF, - M_NOWAIT); - if (atio == NULL) { - status = CAM_RESRC_UNAVAIL; - break; + softc = (struct targ_softc *)dev->si_drv1; + TARG_LOCK(softc); + error = targdisable(softc); + if (error == 0) { + dev->si_drv1 = 0; + mtx_lock(&targ_mtx); + if (softc->periph != NULL) { + cam_periph_invalidate(softc->periph); + softc->periph = NULL; } + mtx_unlock(&targ_mtx); + TARG_UNLOCK(softc); + mtx_destroy(&softc->mtx); + destroy_dev(dev); + FREE(softc, M_TARG); + } else { + TARG_UNLOCK(softc); + } + return (error); +} - atio->ccb_h.ccb_descr = allocdescr(); +/* Enable/disable LUNs, set debugging level */ +static int +targioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td) +{ + struct targ_softc *softc; + cam_status status; - if (atio->ccb_h.ccb_descr == NULL) { - free(atio, M_DEVBUF); - status = CAM_RESRC_UNAVAIL; - break; - } + softc = (struct targ_softc *)dev->si_drv1; - xpt_setup_ccb(&atio->ccb_h, periph->path, /*priority*/1); - atio->ccb_h.func_code = XPT_ACCEPT_TARGET_IO; - atio->ccb_h.cbfcnp = targdone; - atio->ccb_h.ccb_flags = TARG_CCB_NONE; - xpt_action((union ccb *)atio); - status = atio->ccb_h.status; - if (status != CAM_REQ_INPROG) { - xpt_print_path(periph->path); - printf("Queue of atio failed\n"); - freedescr(atio->ccb_h.ccb_descr); - free(atio, M_DEVBUF); + switch (cmd) { + case TARGIOCENABLE: + { + struct ioc_enable_lun *new_lun; + struct cam_path *path; + + new_lun = (struct ioc_enable_lun *)addr; + status = xpt_create_path(&path, /*periph*/NULL, + new_lun->path_id, + new_lun->target_id, + new_lun->lun_id); + if (status != CAM_REQ_CMP) { + printf("Couldn't create path, status %#x\n", status); break; } - ((struct targ_cmd_desc*)atio->ccb_h.ccb_descr)->atio_link = - softc->accept_tio_list; - softc->accept_tio_list = atio; - } - - if (i == 0) { - xpt_print_path(periph->path); - printf("targenlun - Could not allocate accept tio CCBs: " - "status = 0x%x\n", status); - targdislun(periph); - return (CAM_REQ_CMP_ERR); + TARG_LOCK(softc); + status = targenable(softc, path, new_lun->grp6_len, + new_lun->grp7_len); + TARG_UNLOCK(softc); + xpt_free_path(path); + break; } + case TARGIOCDISABLE: + TARG_LOCK(softc); + status = targdisable(softc); + TARG_UNLOCK(softc); + break; + case TARGIOCDEBUG: + { +#ifdef CAMDEBUG + struct ccb_debug cdbg; - /* - * Build up a buffer of immediate notify CCBs - * so the SIM can tell us of asynchronous target mode events. - */ - for (i = 0; i < MAX_ACCEPT; i++) { - struct ccb_immed_notify *inot; - - inot = (struct ccb_immed_notify*)malloc(sizeof(*inot), M_DEVBUF, - M_NOWAIT); - - if (inot == NULL) { - status = CAM_RESRC_UNAVAIL; + bzero(&cdbg, sizeof cdbg); + if (*((int *)addr) != 0) + cdbg.flags = CAM_DEBUG_PERIPH; + else + cdbg.flags = CAM_DEBUG_NONE; + xpt_setup_ccb(&cdbg.ccb_h, softc->path, /*priority*/0); + cdbg.ccb_h.func_code = XPT_DEBUG; + cdbg.ccb_h.cbfcnp = targdone; + + /* If no periph available, disallow debugging changes */ + TARG_LOCK(softc); + if ((softc->state & TARG_STATE_LUN_ENABLED) == 0) { + status = CAM_DEV_NOT_THERE; + TARG_UNLOCK(softc); break; } - - xpt_setup_ccb(&inot->ccb_h, periph->path, /*priority*/1); - inot->ccb_h.func_code = XPT_IMMED_NOTIFY; - inot->ccb_h.cbfcnp = targdone; - SLIST_INSERT_HEAD(&softc->immed_notify_slist, &inot->ccb_h, - periph_links.sle); - xpt_action((union ccb *)inot); + xpt_action((union ccb *)&cdbg); + TARG_UNLOCK(softc); + status = cdbg.ccb_h.status & CAM_STATUS_MASK; +#else + status = CAM_FUNC_NOTAVAIL; +#endif + break; } - - if (i == 0) { - xpt_print_path(periph->path); - printf("targenlun - Could not allocate immediate notify CCBs: " - "status = 0x%x\n", status); - targdislun(periph); - return (CAM_REQ_CMP_ERR); + default: + status = CAM_PROVIDE_FAIL; + break; } - return (CAM_REQ_CMP); + return (targcamstatus(status)); } -static cam_status -targdislun(struct cam_periph *periph) +/* Writes are always ready, reads wait for user_ccb_queue or abort_queue */ +static int +targpoll(dev_t dev, int poll_events, struct thread *td) { - union ccb ccb; struct targ_softc *softc; - struct ccb_accept_tio* atio; - struct ccb_hdr *ccb_h; + int revents; - softc = (struct targ_softc *)periph->softc; - if ((softc->flags & TARG_FLAG_LUN_ENABLED) == 0) - return CAM_REQ_CMP; + softc = (struct targ_softc *)dev->si_drv1; - /* XXX Block for Continue I/O completion */ - - /* Kill off all ACCECPT and IMMEDIATE CCBs */ - while ((atio = softc->accept_tio_list) != NULL) { - - softc->accept_tio_list = - ((struct targ_cmd_desc*)atio->ccb_h.ccb_descr)->atio_link; - xpt_setup_ccb(&ccb.cab.ccb_h, periph->path, /*priority*/1); - ccb.cab.ccb_h.func_code = XPT_ABORT; - ccb.cab.abort_ccb = (union ccb *)atio; - xpt_action(&ccb); - } - - while ((ccb_h = SLIST_FIRST(&softc->immed_notify_slist)) != NULL) { - SLIST_REMOVE_HEAD(&softc->immed_notify_slist, periph_links.sle); - xpt_setup_ccb(&ccb.cab.ccb_h, periph->path, /*priority*/1); - ccb.cab.ccb_h.func_code = XPT_ABORT; - ccb.cab.abort_ccb = (union ccb *)ccb_h; - xpt_action(&ccb); + /* Poll for write() is always ok. */ + revents = poll_events & (POLLOUT | POLLWRNORM); + if ((poll_events & (POLLIN | POLLRDNORM)) != 0) { + /* Poll for read() depends on user and abort queues. */ + TARG_LOCK(softc); + if (!TAILQ_EMPTY(&softc->user_ccb_queue) || + !TAILQ_EMPTY(&softc->abort_queue)) { + revents |= poll_events & (POLLIN | POLLRDNORM); + } + /* Only sleep if the user didn't poll for write. */ + if (revents == 0) + selrecord(td, &softc->read_select); + TARG_UNLOCK(softc); } - /* - * Dissable this lun. - */ - xpt_setup_ccb(&ccb.cel.ccb_h, periph->path, /*priority*/1); - ccb.cel.ccb_h.func_code = XPT_EN_LUN; - ccb.cel.enable = 0; - xpt_action(&ccb); - - if (ccb.cel.ccb_h.status != CAM_REQ_CMP) - printf("targdislun - Disabling lun on controller failed " - "with status 0x%x\n", ccb.cel.ccb_h.status); - else - softc->flags &= ~TARG_FLAG_LUN_ENABLED; - return (ccb.cel.ccb_h.status); + return (revents); } -static cam_status -targctor(struct cam_periph *periph, void *arg) +static int +targkqfilter(dev_t dev, struct knote *kn) { - struct ccb_pathinq *cpi; - struct targ_softc *softc; - int i; - - cpi = (struct ccb_pathinq *)arg; - - /* Allocate our per-instance private storage */ - softc = (struct targ_softc *)malloc(sizeof(*softc), M_DEVBUF, M_NOWAIT); - if (softc == NULL) { - printf("targctor: unable to malloc softc\n"); - return (CAM_REQ_CMP_ERR); - } - - bzero(softc, sizeof(*softc)); - TAILQ_INIT(&softc->pending_queue); - TAILQ_INIT(&softc->work_queue); - TAILQ_INIT(&softc->snd_ccb_queue); - TAILQ_INIT(&softc->rcv_ccb_queue); - TAILQ_INIT(&softc->unknown_atio_queue); - bioq_init(&softc->snd_bio_queue); - bioq_init(&softc->rcv_bio_queue); - softc->accept_tio_list = NULL; - SLIST_INIT(&softc->immed_notify_slist); - softc->state = TARG_STATE_NORMAL; - periph->softc = softc; - softc->init_level++; - - /* - * We start out life with a UA to indicate power-on/reset. - */ - for (i = 0; i < MAX_INITIATORS; i++) - softc->istate[i].pending_ua = UA_POWER_ON; - - /* - * Allocate an inquiry data buffer. - * We let the user to override this if desired. - */ - softc->inq_data_len = sizeof(*softc->inq_data); - softc->inq_data = malloc(softc->inq_data_len, M_DEVBUF, M_NOWAIT); - if (softc->inq_data == NULL) { - printf("targctor - Unable to malloc inquiry data\n"); - targdtor(periph); - return (CAM_RESRC_UNAVAIL); - } - if (cpi->ccb_h.ccb_inq) { - bcopy(cpi->ccb_h.ccb_inq, softc->inq_data, softc->inq_data_len); - } else { - bzero(softc->inq_data, softc->inq_data_len); - softc->inq_data->device = - T_PROCESSOR | (SID_QUAL_LU_CONNECTED << 5); - softc->inq_data->version = 2; - softc->inq_data->response_format = 2; /* SCSI2 Inquiry Format */ - softc->inq_data->additional_length = softc->inq_data_len - 4; - strncpy(softc->inq_data->vendor, "FreeBSD ", SID_VENDOR_SIZE); - strncpy(softc->inq_data->product, - "TM-PT ", SID_PRODUCT_SIZE); - strncpy(softc->inq_data->revision, "0.0 ", SID_REVISION_SIZE); - } - - /* - * Preserve the SIM's capabilities here. Don't let user applications - * do something dumb. - */ - if (softc->inq_data->version >= 2) { - softc->inq_data->flags &= - ~(PI_SDTR_ABLE|PI_WIDE_16|PI_WIDE_32|PI_TAG_ABLE); - softc->inq_data->flags |= (cpi->hba_inquiry & - (PI_SDTR_ABLE|PI_WIDE_16|PI_WIDE_32|PI_TAG_ABLE)); - } - softc->targ_dev = make_dev(&targ_cdevsw, periph->unit_number, UID_ROOT, - GID_OPERATOR, 0600, "%s%d", - periph->periph_name, periph->unit_number); - softc->targ_dev->si_drv1 = periph; - - softc->init_level++; - return (CAM_REQ_CMP); + struct targ_softc *softc; + + softc = (struct targ_softc *)dev->si_drv1; + kn->kn_hook = (caddr_t)softc; + kn->kn_fop = &targread_filtops; + TARG_LOCK(softc); + SLIST_INSERT_HEAD(&softc->read_select.si_note, kn, kn_selnext); + TARG_UNLOCK(softc); + return (0); } static void -targdtor(struct cam_periph *periph) +targreadfiltdetach(struct knote *kn) { - struct targ_softc *softc; - - softc = (struct targ_softc *)periph->softc; + struct targ_softc *softc; - softc->state = TARG_STATE_TEARDOWN; - - targdislun(periph); - - switch (softc->init_level) { - default: - /* FALLTHROUGH */ - case 2: - free(softc->inq_data, M_DEVBUF); - destroy_dev(softc->targ_dev); - /* FALLTHROUGH */ - case 1: - free(softc, M_DEVBUF); - break; - case 0: - panic("targdtor - impossible init level");; - } + softc = (struct targ_softc *)kn->kn_hook; + TARG_LOCK(softc); + SLIST_REMOVE(&softc->read_select.si_note, kn, knote, kn_selnext); + TARG_UNLOCK(softc); } +/* Notify the user's kqueue when the user queue or abort queue gets a CCB */ static int -targopen(dev_t dev, int flags, int fmt, struct thread *td) +targreadfilt(struct knote *kn, long hint) { - struct cam_periph *periph; - struct targ_softc *softc; - cam_status status; - int error; - int s; - - /* An open of the control device always succeeds */ - if (TARG_IS_CONTROL_DEV(dev)) - return 0; - - s = splsoftcam(); - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) { - splx(s); - return (ENXIO); - } - if ((error = cam_periph_lock(periph, PRIBIO | PCATCH)) != 0) { - splx(s); - return (error); - } - - softc = (struct targ_softc *)periph->softc; - if ((softc->flags & TARG_FLAG_LUN_ENABLED) == 0) { - if (cam_periph_acquire(periph) != CAM_REQ_CMP) { - splx(s); - cam_periph_unlock(periph); - return(ENXIO); - } - } - splx(s); - - status = targenlun(periph); - switch (status) { - case CAM_REQ_CMP: - error = 0; - break; - case CAM_RESRC_UNAVAIL: - error = ENOMEM; - break; - case CAM_LUN_ALRDY_ENA: - error = EADDRINUSE; - break; - default: - error = ENXIO; - break; - } - cam_periph_unlock(periph); - if (error) { - cam_periph_release(periph); - } - return (error); + struct targ_softc *softc; + int retval; + + softc = (struct targ_softc *)kn->kn_hook; + TARG_LOCK(softc); + retval = !TAILQ_EMPTY(&softc->user_ccb_queue) || + !TAILQ_EMPTY(&softc->abort_queue); + TARG_UNLOCK(softc); + return (retval); } -static int -targclose(dev_t dev, int flag, int fmt, struct thread *td) +/* Send the HBA the enable/disable message */ +static cam_status +targendislun(struct cam_path *path, int enable, int grp6_len, int grp7_len) { - struct cam_periph *periph; - struct targ_softc *softc; - int s; - int error; - - /* A close of the control device always succeeds */ - if (TARG_IS_CONTROL_DEV(dev)) - return 0; - - s = splsoftcam(); - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) { - splx(s); - return (ENXIO); - } - if ((error = cam_periph_lock(periph, PRIBIO)) != 0) - return (error); - softc = (struct targ_softc *)periph->softc; - splx(s); - - targdislun(periph); - - cam_periph_unlock(periph); - cam_periph_release(periph); + struct ccb_en_lun en_ccb; + cam_status status; - return (0); + /* Tell the lun to begin answering selects */ + xpt_setup_ccb(&en_ccb.ccb_h, path, /*priority*/1); + en_ccb.ccb_h.func_code = XPT_EN_LUN; + /* Don't need support for any vendor specific commands */ + en_ccb.grp6_len = grp6_len; + en_ccb.grp7_len = grp7_len; + en_ccb.enable = enable ? 1 : 0; + xpt_action((union ccb *)&en_ccb); + status = en_ccb.ccb_h.status & CAM_STATUS_MASK; + if (status != CAM_REQ_CMP) { + xpt_print_path(path); + printf("%sable lun CCB rejected, status %#x\n", + enable ? "en" : "dis", status); + } + return (status); } -static int -targallocinstance(void *arg, u_long cmd) +/* Enable target mode on a LUN, given its path */ +static cam_status +targenable(struct targ_softc *softc, struct cam_path *path, int grp6_len, + int grp7_len) { - struct ioc_alloc_unit *alloc_unit = arg; - struct scsi_inquiry_data local; - struct ccb_pathinq cpi; - struct cam_path *path; struct cam_periph *periph; - cam_status status; - int free_path_on_return; - int error; - - free_path_on_return = 0; - status = xpt_create_path(&path, /*periph*/NULL, - alloc_unit->path_id, - alloc_unit->target_id, - alloc_unit->lun_id); - if (status != CAM_REQ_CMP) { - printf("Couldn't Allocate Path %x\n", status); - goto fail; - } + struct ccb_pathinq cpi; + cam_status status; - free_path_on_return++; + if ((softc->state & TARG_STATE_LUN_ENABLED) != 0) + return (CAM_LUN_ALRDY_ENA); + /* Make sure SIM supports target mode */ xpt_setup_ccb(&cpi.ccb_h, path, /*priority*/1); cpi.ccb_h.func_code = XPT_PATH_INQ; xpt_action((union ccb *)&cpi); - status = cpi.ccb_h.status; - + status = cpi.ccb_h.status & CAM_STATUS_MASK; if (status != CAM_REQ_CMP) { - printf("Couldn't CPI %x\n", status); - goto fail; + printf("pathinq failed, status %#x\n", status); + goto enable_fail; } - - /* Can only alloc units on controllers that support target mode */ if ((cpi.target_sprt & PIT_PROCESSOR) == 0) { - printf("Controller does not support target mode - status %x\n", - status); - status = CAM_PATH_INVALID; - goto fail; + printf("controller does not support target mode\n"); + status = CAM_FUNC_NOTAVAIL; + goto enable_fail; } - /* Ensure that we don't already have an instance for this unit. */ - if ((periph = cam_periph_find(path, "targ")) != NULL) { - status = CAM_LUN_ALRDY_ENA; - goto fail; - } + /* Destroy any periph on our path if it is disabled */ + mtx_lock(&targ_mtx); + periph = cam_periph_find(path, "targ"); + if (periph != NULL) { + struct targ_softc *del_softc; - if (cmd == TARGCTLIOALLOCUNIT) { - status = copyin(alloc_unit->inquiry_data, &local, sizeof local); - if (status) - goto fail; - cpi.ccb_h.ccb_inq = &local; - } else { - cpi.ccb_h.ccb_inq = NULL; + del_softc = (struct targ_softc *)periph->softc; + if ((del_softc->state & TARG_STATE_LUN_ENABLED) == 0) { + cam_periph_invalidate(del_softc->periph); + del_softc->periph = NULL; + } else { + printf("Requested path still in use by targ%d\n", + periph->unit_number); + mtx_unlock(&targ_mtx); + status = CAM_LUN_ALRDY_ENA; + goto enable_fail; + } } - - /* - * Allocate a peripheral instance for - * this target instance. - */ + /* Create a periph instance attached to this path */ status = cam_periph_alloc(targctor, NULL, targdtor, targstart, - "targ", CAM_PERIPH_BIO, path, targasync, - 0, &cpi); - -fail: - switch (status) { - case CAM_REQ_CMP: - { - struct cam_periph *periph; + "targ", CAM_PERIPH_BIO, path, targasync, 0, softc); + mtx_unlock(&targ_mtx); + if (status != CAM_REQ_CMP) { + printf("cam_periph_alloc failed, status %#x\n", status); + goto enable_fail; + } - if ((periph = cam_periph_find(path, "targ")) == NULL) - panic("targallocinstance: Succeeded but no periph?"); - error = 0; - alloc_unit->unit = periph->unit_number; - break; + /* Ensure that the periph now exists. */ + if (cam_periph_find(path, "targ") == NULL) { + panic("targenable: succeeded but no periph?"); + /* NOTREACHED */ } - case CAM_RESRC_UNAVAIL: - error = ENOMEM; - break; - case CAM_LUN_ALRDY_ENA: - error = EADDRINUSE; - break; - default: - printf("targallocinstance: Unexpected CAM status %x\n", status); - /* FALLTHROUGH */ - case CAM_PATH_INVALID: - error = ENXIO; - break; - case CAM_PROVIDE_FAIL: - error = ENODEV; - break; + + /* Send the enable lun message */ + status = targendislun(path, /*enable*/1, grp6_len, grp7_len); + if (status != CAM_REQ_CMP) { + printf("enable lun failed, status %#x\n", status); + goto enable_fail; } + softc->state |= TARG_STATE_LUN_ENABLED; - if (free_path_on_return != 0) - xpt_free_path(path); +enable_fail: + return (status); +} - return (error); +/* Disable this softc's target instance if enabled */ +static cam_status +targdisable(struct targ_softc *softc) +{ + cam_status status; + + if ((softc->state & TARG_STATE_LUN_ENABLED) == 0) + return (CAM_REQ_CMP); + + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("targdisable\n")); + + /* Abort any ccbs pending on the controller */ + abort_all_pending(softc); + + /* Disable this lun */ + status = targendislun(softc->path, /*enable*/0, + /*grp6_len*/0, /*grp7_len*/0); + if (status == CAM_REQ_CMP) + softc->state &= ~TARG_STATE_LUN_ENABLED; + else + printf("Disable lun failed, status %#x\n", status); + + return (status); } -static int -targfreeinstance(struct ioc_alloc_unit *alloc_unit) +/* Initialize a periph (called from cam_periph_alloc) */ +static cam_status +targctor(struct cam_periph *periph, void *arg) { - struct cam_path *path; - struct cam_periph *periph; struct targ_softc *softc; - cam_status status; - int free_path_on_return; - int error; - - periph = NULL; - free_path_on_return = 0; - status = xpt_create_path(&path, /*periph*/NULL, - alloc_unit->path_id, - alloc_unit->target_id, - alloc_unit->lun_id); - free_path_on_return++; - - if (status != CAM_REQ_CMP) - goto fail; - - /* Find our instance. */ - if ((periph = cam_periph_find(path, "targ")) == NULL) { - xpt_print_path(path); - printf("Invalid path specified for freeing target instance\n"); - status = CAM_PATH_INVALID; - goto fail; - } - softc = (struct targ_softc *)periph->softc; - - if ((softc->flags & TARG_FLAG_LUN_ENABLED) != 0) { - status = CAM_BUSY; - goto fail; - } + /* Store pointer to softc for periph-driven routines */ + softc = (struct targ_softc *)arg; + periph->softc = softc; + softc->periph = periph; + softc->path = periph->path; + return (CAM_REQ_CMP); +} -fail: - if (free_path_on_return != 0) - xpt_free_path(path); +static void +targdtor(struct cam_periph *periph) +{ + struct targ_softc *softc; + struct ccb_hdr *ccb_h; + struct targ_cmd_descr *descr; - switch (status) { - case CAM_REQ_CMP: - if (periph != NULL) - cam_periph_invalidate(periph); - error = 0; - break; - case CAM_RESRC_UNAVAIL: - error = ENOMEM; - break; - case CAM_LUN_ALRDY_ENA: - error = EADDRINUSE; - break; - default: - printf("targfreeinstance: Unexpected CAM status %x\n", status); - /* FALLTHROUGH */ - case CAM_PATH_INVALID: - error = ENODEV; - break; + softc = (struct targ_softc *)periph->softc; + + /* + * targdisable() aborts CCBs back to the user and leaves them + * on user_ccb_queue and abort_queue in case the user is still + * interested in them. We free them now. + */ + while ((ccb_h = TAILQ_FIRST(&softc->user_ccb_queue)) != NULL) { + TAILQ_REMOVE(&softc->user_ccb_queue, ccb_h, periph_links.tqe); + targfreeccb(softc, (union ccb *)ccb_h); } - return (error); + while ((descr = TAILQ_FIRST(&softc->abort_queue)) != NULL) { + TAILQ_REMOVE(&softc->abort_queue, descr, tqe); + FREE(descr, M_TARG); + } + + softc->periph = NULL; + softc->path = NULL; + periph->softc = NULL; } +/* Receive CCBs from user mode proc and send them to the HBA */ static int -targioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td) +targwrite(dev_t dev, struct uio *uio, int ioflag) { - struct cam_periph *periph; + union ccb *user_ccb; struct targ_softc *softc; - int error; + struct targ_cmd_descr *descr; + int write_len, error; + int func_code, priority; + + softc = (struct targ_softc *)dev->si_drv1; + write_len = error = 0; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("write - uio_resid %d\n", uio->uio_resid)); + while (uio->uio_resid >= sizeof(user_ccb) && error == 0) { + union ccb *ccb; + int error; - error = 0; - if (TARG_IS_CONTROL_DEV(dev)) { - switch (cmd) { - case OTARGCTLIOALLOCUNIT: - case TARGCTLIOALLOCUNIT: - error = targallocinstance(addr, cmd); + error = uiomove((caddr_t)&user_ccb, sizeof(user_ccb), uio); + if (error != 0) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("write - uiomove failed (%d)\n", error)); break; - case OTARGCTLIOFREEUNIT: - case TARGCTLIOFREEUNIT: - /* - * Old_ioc_alloc_unit and ioc_alloc_unit are the - * same with respect to what we need from the structure - * for this function. - */ - error = targfreeinstance((struct ioc_alloc_unit*)addr); + } + priority = fuword(&user_ccb->ccb_h.pinfo.priority); + if (priority == -1) { + error = EINVAL; + break; + } + func_code = fuword(&user_ccb->ccb_h.func_code); + switch (func_code) { + case XPT_ACCEPT_TARGET_IO: + case XPT_IMMED_NOTIFY: + ccb = targgetccb(softc, func_code, priority); + descr = (struct targ_cmd_descr *)ccb->ccb_h.targ_descr; + descr->user_ccb = user_ccb; + descr->func_code = func_code; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Sent ATIO/INOT (%p)\n", user_ccb)); + xpt_action(ccb); + TARG_LOCK(softc); + TAILQ_INSERT_TAIL(&softc->pending_ccb_queue, + &ccb->ccb_h, + periph_links.tqe); + TARG_UNLOCK(softc); break; default: - error = EINVAL; + if ((func_code & XPT_FC_QUEUED) != 0) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Sending queued ccb %#x (%p)\n", + func_code, user_ccb)); + descr = targgetdescr(softc); + descr->user_ccb = user_ccb; + descr->priority = priority; + descr->func_code = func_code; + TARG_LOCK(softc); + TAILQ_INSERT_TAIL(&softc->work_queue, + descr, tqe); + TARG_UNLOCK(softc); + xpt_schedule(softc->periph, priority); + } else { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Sending inline ccb %#x (%p)\n", + func_code, user_ccb)); + ccb = targgetccb(softc, func_code, priority); + descr = (struct targ_cmd_descr *) + ccb->ccb_h.targ_descr; + descr->user_ccb = user_ccb; + descr->priority = priority; + descr->func_code = func_code; + if (targusermerge(softc, descr, ccb) != EFAULT) + targsendccb(softc, ccb, descr); + targreturnccb(softc, ccb); + } break; } - return (error); + write_len += sizeof(user_ccb); } + + /* + * If we've successfully taken in some amount of + * data, return success for that data first. If + * an error is persistent, it will be reported + * on the next write. + */ + if (error != 0 && write_len == 0) + return (error); + if (write_len == 0 && uio->uio_resid != 0) + return (ENOSPC); + return (0); +} + +/* Process requests (descrs) via the periph-supplied CCBs */ +static void +targstart(struct cam_periph *periph, union ccb *start_ccb) +{ + struct targ_softc *softc; + struct targ_cmd_descr *descr, *next_descr; + int error; - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) - return (ENXIO); softc = (struct targ_softc *)periph->softc; - switch (cmd) { - case TARGIOCFETCHEXCEPTION: - *((targ_exception *)addr) = softc->exceptions; - break; - case TARGIOCCLEAREXCEPTION: - { - targ_exception clear_mask; - - clear_mask = *((targ_exception *)addr); - if ((clear_mask & TARG_EXCEPT_UNKNOWN_ATIO) != 0) { - struct ccb_hdr *ccbh; - - ccbh = TAILQ_FIRST(&softc->unknown_atio_queue); - if (ccbh != NULL) { - TAILQ_REMOVE(&softc->unknown_atio_queue, - ccbh, periph_links.tqe); - /* Requeue the ATIO back to the controller */ - ccbh->ccb_flags = TARG_CCB_NONE; - xpt_action((union ccb *)ccbh); - ccbh = TAILQ_FIRST(&softc->unknown_atio_queue); - } - if (ccbh != NULL) - clear_mask &= ~TARG_EXCEPT_UNKNOWN_ATIO; - } - softc->exceptions &= ~clear_mask; - if (softc->exceptions == TARG_EXCEPT_NONE - && softc->state == TARG_STATE_EXCEPTION) { - softc->state = TARG_STATE_NORMAL; - targrunqueue(periph, softc); - } - break; - } - case TARGIOCFETCHATIO: - { - struct ccb_hdr *ccbh; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("targstart %p\n", start_ccb)); - ccbh = TAILQ_FIRST(&softc->unknown_atio_queue); - if (ccbh != NULL) { - bcopy(ccbh, addr, sizeof(struct ccb_accept_tio)); - } else { - error = ENOENT; + TARG_LOCK(softc); + descr = TAILQ_FIRST(&softc->work_queue); + if (descr == NULL) { + TARG_UNLOCK(softc); + xpt_release_ccb(start_ccb); + } else { + TAILQ_REMOVE(&softc->work_queue, descr, tqe); + next_descr = TAILQ_FIRST(&softc->work_queue); + TARG_UNLOCK(softc); + + /* Initiate a transaction using the descr and supplied CCB */ + error = targusermerge(softc, descr, start_ccb); + if (error == 0) + error = targsendccb(softc, start_ccb, descr); + if (error != 0) { + xpt_print_path(periph->path); + printf("targsendccb failed, err %d\n", error); + xpt_release_ccb(start_ccb); + suword(&descr->user_ccb->ccb_h.status, + CAM_REQ_CMP_ERR); + TARG_LOCK(softc); + TAILQ_INSERT_TAIL(&softc->abort_queue, descr, tqe); + TARG_UNLOCK(softc); + notify_user(softc); } - break; - } - case TARGIOCCOMMAND: - { - union ccb *inccb; - union ccb *ccb; - /* - * XXX JGibbs - * This code is lifted directly from the pass-thru driver. - * Perhaps this should be moved to a library???? - */ - inccb = (union ccb *)addr; - ccb = cam_periph_getccb(periph, inccb->ccb_h.pinfo.priority); + /* If we have more work to do, stay scheduled */ + if (next_descr != NULL) + xpt_schedule(periph, next_descr->priority); + } +} - error = targsendccb(periph, ccb, inccb); +static int +targusermerge(struct targ_softc *softc, struct targ_cmd_descr *descr, + union ccb *ccb) +{ + struct ccb_hdr *u_ccbh, *k_ccbh; + size_t ccb_len; + int error; - xpt_release_ccb(ccb); + u_ccbh = &descr->user_ccb->ccb_h; + k_ccbh = &ccb->ccb_h; - break; + /* + * There are some fields in the CCB header that need to be + * preserved, the rest we get from the user ccb. (See xpt_merge_ccb) + */ + xpt_setup_ccb(k_ccbh, softc->path, descr->priority); + k_ccbh->retry_count = fuword(&u_ccbh->retry_count); + k_ccbh->func_code = descr->func_code; + k_ccbh->flags = fuword(&u_ccbh->flags); + k_ccbh->timeout = fuword(&u_ccbh->timeout); + ccb_len = targccblen(k_ccbh->func_code) - sizeof(struct ccb_hdr); + error = copyin(u_ccbh + 1, k_ccbh + 1, ccb_len); + if (error != 0) { + k_ccbh->status = CAM_REQ_CMP_ERR; + return (error); } - case TARGIOCGETISTATE: - case TARGIOCSETISTATE: - { - struct ioc_initiator_state *ioc_istate; - ioc_istate = (struct ioc_initiator_state *)addr; - if (ioc_istate->initiator_id > MAX_INITIATORS) { - error = EINVAL; - break; - } - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("GET/SETISTATE for %d\n", ioc_istate->initiator_id)); - if (cmd == TARGIOCGETISTATE) { - bcopy(&softc->istate[ioc_istate->initiator_id], - &ioc_istate->istate, sizeof(ioc_istate->istate)); - } else { - bcopy(&ioc_istate->istate, - &softc->istate[ioc_istate->initiator_id], - sizeof(ioc_istate->istate)); - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("pending_ca now %x\n", - softc->istate[ioc_istate->initiator_id].pending_ca)); - } - break; - } - case TARGIODEBUG: - { -#ifdef CAMDEBUG - union ccb ccb; - bzero (&ccb, sizeof ccb); - if (xpt_create_path(&ccb.ccb_h.path, periph, - xpt_path_path_id(periph->path), - xpt_path_target_id(periph->path), - xpt_path_lun_id(periph->path)) != CAM_REQ_CMP) { - error = EINVAL; - break; - } - if (*((int *)addr)) { - ccb.cdbg.flags = CAM_DEBUG_PERIPH; - } else { - ccb.cdbg.flags = CAM_DEBUG_NONE; + /* Translate usermode abort_ccb pointer to its kernel counterpart */ + if (k_ccbh->func_code == XPT_ABORT) { + struct ccb_abort *cab; + struct ccb_hdr *ccb_h; + + cab = (struct ccb_abort *)ccb; + TARG_LOCK(softc); + TAILQ_FOREACH(ccb_h, &softc->pending_ccb_queue, + periph_links.tqe) { + struct targ_cmd_descr *ab_descr; + + ab_descr = (struct targ_cmd_descr *)ccb_h->targ_descr; + if (ab_descr->user_ccb == cab->abort_ccb) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Changing abort for %p to %p\n", + cab->abort_ccb, ccb_h)); + cab->abort_ccb = (union ccb *)ccb_h; + break; + } } - xpt_setup_ccb(&ccb.ccb_h, ccb.ccb_h.path, 0); - ccb.ccb_h.func_code = XPT_DEBUG; - ccb.ccb_h.path_id = xpt_path_path_id(ccb.ccb_h.path); - ccb.ccb_h.target_id = xpt_path_target_id(ccb.ccb_h.path); - ccb.ccb_h.target_lun = xpt_path_lun_id(ccb.ccb_h.path); - ccb.ccb_h.cbfcnp = targdone; - xpt_action(&ccb); - if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { - error = EIO; - } else { - error = 0; + TARG_UNLOCK(softc); + /* CCB not found, set appropriate status */ + if (ccb_h == NULL) { + k_ccbh->status = CAM_PATH_INVALID; + error = ESRCH; } - xpt_free_path(ccb.ccb_h.path); -#else - error = 0; -#endif - break; - } - default: - error = ENOTTY; - break; } + return (error); } -/* - * XXX JGibbs lifted from pass-thru driver. - * Generally, "ccb" should be the CCB supplied by the kernel. "inccb" - * should be the CCB that is copied in from the user. - */ +/* Build and send a kernel CCB formed from descr->user_ccb */ static int -targsendccb(struct cam_periph *periph, union ccb *ccb, union ccb *inccb) +targsendccb(struct targ_softc *softc, union ccb *ccb, + struct targ_cmd_descr *descr) { - struct targ_softc *softc; - struct cam_periph_map_info mapinfo; - int error, need_unmap; - int s; - - softc = (struct targ_softc *)periph->softc; - - need_unmap = 0; + struct cam_periph_map_info *mapinfo; + struct ccb_hdr *ccb_h; + int error; - /* - * There are some fields in the CCB header that need to be - * preserved, the rest we get from the user. - */ - xpt_merge_ccb(ccb, inccb); + ccb_h = &ccb->ccb_h; + mapinfo = &descr->mapinfo; + mapinfo->num_bufs_used = 0; /* * There's no way for the user to have a completion * function, so we put our own completion function in here. + * We also stash in a reference to our descriptor so targreturnccb() + * can find our mapping info. */ - ccb->ccb_h.cbfcnp = targdone; + ccb_h->cbfcnp = targdone; + ccb_h->targ_descr = descr; /* * We only attempt to map the user memory into kernel space @@ -991,1327 +766,435 @@ targsendccb(struct cam_periph *periph, union ccb *ccb, union ccb *inccb) * without data are a reasonably common occurance (e.g. test unit * ready), it will save a few cycles if we check for it here. */ - if (((ccb->ccb_h.flags & CAM_DATA_PHYS) == 0) - && (((ccb->ccb_h.func_code == XPT_CONT_TARGET_IO) - && ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE)) - || (ccb->ccb_h.func_code == XPT_DEV_MATCH))) { - - bzero(&mapinfo, sizeof(mapinfo)); + if (((ccb_h->flags & CAM_DATA_PHYS) == 0) + && (((ccb_h->func_code == XPT_CONT_TARGET_IO) + && ((ccb_h->flags & CAM_DIR_MASK) != CAM_DIR_NONE)) + || (ccb_h->func_code == XPT_DEV_MATCH))) { - error = cam_periph_mapmem(ccb, &mapinfo); + error = cam_periph_mapmem(ccb, mapinfo); /* * cam_periph_mapmem returned an error, we can't continue. * Return the error to the user. */ - if (error) - return(error); - - /* - * We successfully mapped the memory in, so we need to - * unmap it when the transaction is done. - */ - need_unmap = 1; + if (error) { + ccb_h->status = CAM_REQ_CMP_ERR; + mapinfo->num_bufs_used = 0; + return (error); + } } /* * Once queued on the pending CCB list, this CCB will be protected - * by the error recovery handling used for 'buffer I/O' ccbs. Since - * we are in a process context here, however, the software interrupt - * for this driver may deliver an event invalidating this CCB just - * before we queue it. Close this race condition by blocking - * software interrupt delivery, checking for any pertinent queued - * events, and only then queuing this CCB. + * by our error recovery handler. */ - s = splsoftcam(); - if (softc->exceptions == 0) { - if (ccb->ccb_h.func_code == XPT_CONT_TARGET_IO) - TAILQ_INSERT_TAIL(&softc->pending_queue, &ccb->ccb_h, - periph_links.tqe); - - /* - * If the user wants us to perform any error recovery, - * then honor that request. Otherwise, it's up to the - * user to perform any error recovery. - */ - error = cam_periph_runccb(ccb, /* error handler */NULL, - CAM_RETRY_SELTO, SF_RETRY_UA, - &softc->device_stats); - - if (ccb->ccb_h.func_code == XPT_CONT_TARGET_IO) - TAILQ_REMOVE(&softc->pending_queue, &ccb->ccb_h, - periph_links.tqe); - } else { - ccb->ccb_h.status = CAM_UNACKED_EVENT; - error = 0; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("sendccb %p\n", ccb)); + if (XPT_FC_IS_QUEUED(ccb)) { + TARG_LOCK(softc); + TAILQ_INSERT_TAIL(&softc->pending_ccb_queue, ccb_h, + periph_links.tqe); + TARG_UNLOCK(softc); } - splx(s); + xpt_action(ccb); - if (need_unmap != 0) - cam_periph_unmapmem(ccb, &mapinfo); - - ccb->ccb_h.cbfcnp = NULL; - ccb->ccb_h.periph_priv = inccb->ccb_h.periph_priv; - bcopy(ccb, inccb, sizeof(union ccb)); - - return(error); + return (0); } - -static int -targpoll(dev_t dev, int poll_events, struct thread *td) +/* Completion routine for CCBs (called at splsoftcam) */ +static void +targdone(struct cam_periph *periph, union ccb *done_ccb) { - struct cam_periph *periph; struct targ_softc *softc; - int revents; - int s; - - /* ioctl is the only supported operation of the control device */ - if (TARG_IS_CONTROL_DEV(dev)) - return EINVAL; + cam_status status; - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) - return (ENXIO); + CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, ("targdone %p\n", done_ccb)); softc = (struct targ_softc *)periph->softc; - - revents = 0; - s = splcam(); - if ((poll_events & (POLLOUT | POLLWRNORM)) != 0) { - if (TAILQ_FIRST(&softc->rcv_ccb_queue) != NULL - && bioq_first(&softc->rcv_bio_queue) == NULL) - revents |= poll_events & (POLLOUT | POLLWRNORM); - } - if ((poll_events & (POLLIN | POLLRDNORM)) != 0) { - if (TAILQ_FIRST(&softc->snd_ccb_queue) != NULL - && bioq_first(&softc->snd_bio_queue) == NULL) - revents |= poll_events & (POLLIN | POLLRDNORM); + TARG_LOCK(softc); + TAILQ_REMOVE(&softc->pending_ccb_queue, &done_ccb->ccb_h, + periph_links.tqe); + status = done_ccb->ccb_h.status & CAM_STATUS_MASK; + + /* If we're no longer enabled, throw away CCB */ + if ((softc->state & TARG_STATE_LUN_ENABLED) == 0) { + targfreeccb(softc, done_ccb); + return; } + /* abort_all_pending() waits for pending queue to be empty */ + if (TAILQ_EMPTY(&softc->pending_ccb_queue)) + wakeup(&softc->pending_ccb_queue); - if (softc->state != TARG_STATE_NORMAL) - revents |= POLLERR; - - if (revents == 0) { - if (poll_events & (POLLOUT | POLLWRNORM)) - selrecord(td, &softc->rcv_select); - if (poll_events & (POLLIN | POLLRDNORM)) - selrecord(td, &softc->snd_select); + switch (done_ccb->ccb_h.func_code) { + /* All FC_*_QUEUED CCBs go back to userland */ + case XPT_IMMED_NOTIFY: + case XPT_ACCEPT_TARGET_IO: + case XPT_CONT_TARGET_IO: + TAILQ_INSERT_TAIL(&softc->user_ccb_queue, &done_ccb->ccb_h, + periph_links.tqe); + notify_user(softc); + break; + default: + panic("targdone: impossible xpt opcode %#x", + done_ccb->ccb_h.func_code); + /* NOTREACHED */ } - splx(s); - return (revents); + TARG_UNLOCK(softc); } +/* Return CCBs to the user from the user queue and abort queue */ static int targread(dev_t dev, struct uio *uio, int ioflag) { - /* ioctl is the only supported operation of the control device */ - if (TARG_IS_CONTROL_DEV(dev)) - return EINVAL; - - if (uio->uio_iovcnt == 0 - || uio->uio_iov->iov_len == 0) { - /* EOF */ - struct cam_periph *periph; - struct targ_softc *softc; - int s; - - s = splcam(); - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) - return (ENXIO); - softc = (struct targ_softc *)periph->softc; - softc->flags |= TARG_FLAG_SEND_EOF; - splx(s); - targrunqueue(periph, softc); - return (0); - } - return(physread(dev, uio, ioflag)); -} + struct descr_queue *abort_queue; + struct targ_cmd_descr *user_descr; + struct targ_softc *softc; + struct ccb_queue *user_queue; + struct ccb_hdr *ccb_h; + union ccb *user_ccb; + int read_len, error; -static int -targwrite(dev_t dev, struct uio *uio, int ioflag) -{ - /* ioctl is the only supported operation of the control device */ - if (TARG_IS_CONTROL_DEV(dev)) - return EINVAL; - - if (uio->uio_iovcnt == 0 - || uio->uio_iov->iov_len == 0) { - /* EOF */ - struct cam_periph *periph; - struct targ_softc *softc; - int s; - - s = splcam(); - periph = (struct cam_periph *)dev->si_drv1; - if (periph == NULL) - return (ENXIO); - softc = (struct targ_softc *)periph->softc; - softc->flags |= TARG_FLAG_RECEIVE_EOF; - splx(s); - targrunqueue(periph, softc); - return (0); - } - return(physwrite(dev, uio, ioflag)); -} - -/* - * Actually translate the requested transfer into one the physical driver - * can understand. The transfer is described by a buf and will include - * only one physical transfer. - */ -static void -targstrategy(struct bio *bp) -{ - struct cam_periph *periph; - struct targ_softc *softc; - int s; - - bp->bio_resid = bp->bio_bcount; - - /* ioctl is the only supported operation of the control device */ - if (TARG_IS_CONTROL_DEV(bp->bio_dev)) { - biofinish(bp, NULL, EINVAL); - return; + error = 0; + read_len = 0; + softc = (struct targ_softc *)dev->si_drv1; + user_queue = &softc->user_ccb_queue; + abort_queue = &softc->abort_queue; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("targread\n")); + + /* If no data is available, wait or return immediately */ + TARG_LOCK(softc); + ccb_h = TAILQ_FIRST(user_queue); + user_descr = TAILQ_FIRST(abort_queue); + while (ccb_h == NULL && user_descr == NULL) { + if ((ioflag & IO_NDELAY) == 0) { + error = msleep(user_queue, &softc->mtx, + PRIBIO | PCATCH, "targrd", 0); + ccb_h = TAILQ_FIRST(user_queue); + user_descr = TAILQ_FIRST(abort_queue); + if (error != 0) { + if (error == ERESTART) { + continue; + } else { + TARG_UNLOCK(softc); + goto read_fail; + } + } + } else { + TARG_UNLOCK(softc); + return (EAGAIN); + } } - periph = (struct cam_periph *)bp->bio_dev->si_drv1; - if (periph == NULL) { - biofinish(bp, NULL, ENXIO); - return; - } - softc = (struct targ_softc *)periph->softc; + /* Data is available so fill the user's buffer */ + while (ccb_h != NULL) { + struct targ_cmd_descr *descr; - /* - * Mask interrupts so that the device cannot be invalidated until - * after we are in the queue. Otherwise, we might not properly - * clean up one of the buffers. - */ - s = splbio(); - - /* - * If there is an exception pending, error out - */ - if (softc->state != TARG_STATE_NORMAL) { - splx(s); - if (softc->state == TARG_STATE_EXCEPTION - && (softc->exceptions & TARG_EXCEPT_DEVICE_INVALID) == 0) - s = EBUSY; - else - s = ENXIO; - biofinish(bp, NULL, s); - return; - } - - /* - * Place it in the queue of buffers available for either - * SEND or RECEIVE commands. - * - */ - bp->bio_resid = bp->bio_bcount; - if (bp->bio_cmd == BIO_READ) { - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Queued a SEND buffer\n")); - bioq_insert_tail(&softc->snd_bio_queue, bp); - } else { - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Queued a RECEIVE buffer\n")); - bioq_insert_tail(&softc->rcv_bio_queue, bp); - } + if (uio->uio_resid < sizeof(user_ccb)) + break; + TAILQ_REMOVE(user_queue, ccb_h, periph_links.tqe); + TARG_UNLOCK(softc); + descr = (struct targ_cmd_descr *)ccb_h->targ_descr; + user_ccb = descr->user_ccb; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("targread ccb %p (%p)\n", ccb_h, user_ccb)); + error = targreturnccb(softc, (union ccb *)ccb_h); + if (error != 0) + goto read_fail; + error = uiomove((caddr_t)&user_ccb, sizeof(user_ccb), uio); + if (error != 0) + goto read_fail; + read_len += sizeof(user_ccb); + + TARG_LOCK(softc); + ccb_h = TAILQ_FIRST(user_queue); + } + + /* Flush out any aborted descriptors */ + while (user_descr != NULL) { + if (uio->uio_resid < sizeof(user_ccb)) + break; + TAILQ_REMOVE(abort_queue, user_descr, tqe); + TARG_UNLOCK(softc); + user_ccb = user_descr->user_ccb; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("targread aborted descr %p (%p)\n", + user_descr, user_ccb)); + suword(&user_ccb->ccb_h.status, CAM_REQ_ABORTED); + error = uiomove((caddr_t)&user_ccb, sizeof(user_ccb), uio); + if (error != 0) + goto read_fail; + read_len += sizeof(user_ccb); + + TARG_LOCK(softc); + user_descr = TAILQ_FIRST(abort_queue); + } + TARG_UNLOCK(softc); - splx(s); - /* - * Attempt to use the new buffer to service any pending - * target commands. + * If we've successfully read some amount of data, don't report an + * error. If the error is persistent, it will be reported on the + * next read(). */ - targrunqueue(periph, softc); + if (read_len == 0 && uio->uio_resid != 0) + error = ENOSPC; - return; +read_fail: + return (error); } -static void -targrunqueue(struct cam_periph *periph, struct targ_softc *softc) +/* Copy completed ccb back to the user */ +static int +targreturnccb(struct targ_softc *softc, union ccb *ccb) { - struct ccb_queue *pending_queue; - struct ccb_accept_tio *atio; - struct bio_queue_head *bioq; - struct bio *bp; - struct targ_cmd_desc *desc; - struct ccb_hdr *ccbh; - int s; - - s = splbio(); - pending_queue = NULL; - bioq = NULL; - ccbh = NULL; - /* Only run one request at a time to maintain data ordering. */ - if (softc->state != TARG_STATE_NORMAL - || TAILQ_FIRST(&softc->work_queue) != NULL - || TAILQ_FIRST(&softc->pending_queue) != NULL) { - splx(s); - return; - } + struct targ_cmd_descr *descr; + struct ccb_hdr *u_ccbh; + size_t ccb_len; + int error; - if (((bp = bioq_first(&softc->snd_bio_queue)) != NULL - || (softc->flags & TARG_FLAG_SEND_EOF) != 0) - && (ccbh = TAILQ_FIRST(&softc->snd_ccb_queue)) != NULL) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("targreturnccb %p\n", ccb)); + descr = (struct targ_cmd_descr *)ccb->ccb_h.targ_descr; + u_ccbh = &descr->user_ccb->ccb_h; - if (bp == NULL) - softc->flags &= ~TARG_FLAG_SEND_EOF; - else { - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("De-Queued a SEND buffer %ld\n", - bp->bio_bcount)); - } - bioq = &softc->snd_bio_queue; - pending_queue = &softc->snd_ccb_queue; - } else if (((bp = bioq_first(&softc->rcv_bio_queue)) != NULL - || (softc->flags & TARG_FLAG_RECEIVE_EOF) != 0) - && (ccbh = TAILQ_FIRST(&softc->rcv_ccb_queue)) != NULL) { - - if (bp == NULL) - softc->flags &= ~TARG_FLAG_RECEIVE_EOF; - else { - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("De-Queued a RECEIVE buffer %ld\n", - bp->bio_bcount)); - } - bioq = &softc->rcv_bio_queue; - pending_queue = &softc->rcv_ccb_queue; - } + /* Copy out the central portion of the ccb_hdr */ + copyout(&ccb->ccb_h.retry_count, &u_ccbh->retry_count, + offsetof(struct ccb_hdr, periph_priv) - + offsetof(struct ccb_hdr, retry_count)); - if (pending_queue != NULL) { - /* Process a request */ - atio = (struct ccb_accept_tio *)ccbh; - TAILQ_REMOVE(pending_queue, ccbh, periph_links.tqe); - desc = (struct targ_cmd_desc *)atio->ccb_h.ccb_descr; - desc->bp = bp; - if (bp == NULL) { - /* EOF */ - desc->data = NULL; - desc->data_increment = 0; - desc->data_resid = 0; - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_NONE; - } else { - bioq_remove(bioq, bp); - desc->data = &bp->bio_data[bp->bio_bcount - bp->bio_resid]; - desc->data_increment = - MIN(desc->data_resid, bp->bio_resid); - } - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Buffer command: data %p: datacnt %d\n", - desc->data, desc->data_increment)); - TAILQ_INSERT_TAIL(&softc->work_queue, &atio->ccb_h, - periph_links.tqe); + /* Copy out the rest of the ccb (after the ccb_hdr) */ + ccb_len = targccblen(ccb->ccb_h.func_code) - sizeof(struct ccb_hdr); + if (descr->mapinfo.num_bufs_used != 0) + cam_periph_unmapmem(ccb, &descr->mapinfo); + error = copyout(&ccb->ccb_h + 1, u_ccbh + 1, ccb_len); + if (error != 0) { + xpt_print_path(softc->path); + printf("targreturnccb - CCB copyout failed (%d)\n", + error); } - atio = (struct ccb_accept_tio *)TAILQ_FIRST(&softc->work_queue); - if (atio != NULL) { - int priority; - - priority = (atio->ccb_h.flags & CAM_DIS_DISCONNECT) ? 0 : 1; - splx(s); - xpt_schedule(periph, priority); - } else - splx(s); + /* Free CCB or send back to devq. */ + targfreeccb(softc, ccb); + + return (error); } -static void -targstart(struct cam_periph *periph, union ccb *start_ccb) +static union ccb * +targgetccb(struct targ_softc *softc, xpt_opcode type, int priority) { - struct targ_softc *softc; - struct ccb_hdr *ccbh; - struct ccb_accept_tio *atio; - struct targ_cmd_desc *desc; - struct ccb_scsiio *csio; - targ_ccb_flags flags; - int s; + union ccb *ccb; + int ccb_len; - softc = (struct targ_softc *)periph->softc; - - s = splbio(); - ccbh = TAILQ_FIRST(&softc->work_queue); - if (periph->immediate_priority <= periph->pinfo.priority) { - start_ccb->ccb_h.ccb_flags = TARG_CCB_WAITING; - SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h, - periph_links.sle); - periph->immediate_priority = CAM_PRIORITY_NONE; - splx(s); - wakeup(&periph->ccb_list); - } else if (ccbh == NULL) { - splx(s); - xpt_release_ccb(start_ccb); - } else { - TAILQ_REMOVE(&softc->work_queue, ccbh, periph_links.tqe); - splx(s); - atio = (struct ccb_accept_tio*)ccbh; - desc = (struct targ_cmd_desc *)atio->ccb_h.ccb_descr; - - /* Is this a tagged request? */ - flags = atio->ccb_h.flags & (CAM_DIS_DISCONNECT | - CAM_TAG_ACTION_VALID | CAM_DIR_MASK | CAM_SEND_STATUS); - - /* - * If we are done with the transaction, tell the - * controller to send status and perform a CMD_CMPLT. - */ - if (desc->user_atio == 0 && - desc->data_resid == desc->data_increment) { - flags |= CAM_SEND_STATUS; - } - - csio = &start_ccb->csio; - cam_fill_ctio(csio, - /*retries*/2, - targdone, - flags, - (flags & CAM_TAG_ACTION_VALID) ? - MSG_SIMPLE_Q_TAG : 0, - atio->tag_id, - atio->init_id, - desc->status, - /*data_ptr*/desc->data_increment == 0 - ? NULL : desc->data, - /*dxfer_len*/desc->data_increment, - /*timeout*/desc->timeout); - - if ((flags & CAM_SEND_STATUS) != 0 - && (desc->status == SCSI_STATUS_CHECK_COND - || desc->status == SCSI_STATUS_CMD_TERMINATED)) { - struct initiator_state *istate; - - istate = &softc->istate[atio->init_id]; - csio->sense_len = istate->sense_data.extra_len - + offsetof(struct scsi_sense_data, - extra_len); - bcopy(&istate->sense_data, &csio->sense_data, - csio->sense_len); - csio->ccb_h.flags |= CAM_SEND_SENSE; - } else { - csio->sense_len = 0; - } + ccb_len = targccblen(type); + MALLOC(ccb, union ccb *, ccb_len, M_TARG, M_WAITOK); + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("getccb %p\n", ccb)); - start_ccb->ccb_h.ccb_flags = TARG_CCB_NONE; - start_ccb->ccb_h.ccb_atio = atio; - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Sending a CTIO (flags 0x%x)\n", csio->ccb_h.flags)); - TAILQ_INSERT_TAIL(&softc->pending_queue, &csio->ccb_h, - periph_links.tqe); - xpt_action(start_ccb); - /* - * If the queue was frozen waiting for the response - * to this ATIO (for instance disconnection was disallowed), - * then release it now that our response has been queued. - */ - if ((atio->ccb_h.status & CAM_DEV_QFRZN) != 0) { - cam_release_devq(periph->path, - /*relsim_flags*/0, - /*reduction*/0, - /*timeout*/0, - /*getcount_only*/0); - atio->ccb_h.status &= ~CAM_DEV_QFRZN; - } - s = splbio(); - ccbh = TAILQ_FIRST(&softc->work_queue); - splx(s); - } - if (ccbh != NULL) - targrunqueue(periph, softc); + xpt_setup_ccb(&ccb->ccb_h, softc->path, priority); + ccb->ccb_h.func_code = type; + ccb->ccb_h.cbfcnp = targdone; + ccb->ccb_h.targ_descr = targgetdescr(softc); + return (ccb); } static void -targdone(struct cam_periph *periph, union ccb *done_ccb) +targfreeccb(struct targ_softc *softc, union ccb *ccb) { - struct targ_softc *softc; - - softc = (struct targ_softc *)periph->softc; - - if (done_ccb->ccb_h.ccb_flags == TARG_CCB_WAITING) { - /* Caller will release the CCB */ - wakeup(&done_ccb->ccb_h.cbfcnp); - return; - } - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("targdone %x\n", done_ccb->ccb_h.func_code)); + CAM_DEBUG_PRINT(CAM_DEBUG_PERIPH, ("targfreeccb descr %p and\n", + ccb->ccb_h.targ_descr)); + FREE(ccb->ccb_h.targ_descr, M_TARG); - switch (done_ccb->ccb_h.func_code) { + switch (ccb->ccb_h.func_code) { case XPT_ACCEPT_TARGET_IO: - { - struct ccb_accept_tio *atio; - struct targ_cmd_desc *descr; - struct initiator_state *istate; - u_int8_t *cdb; - int priority; - - atio = &done_ccb->atio; - descr = (struct targ_cmd_desc*)atio->ccb_h.ccb_descr; - istate = &softc->istate[atio->init_id]; - cdb = atio->cdb_io.cdb_bytes; - if (softc->state == TARG_STATE_TEARDOWN - || atio->ccb_h.status == CAM_REQ_ABORTED) { - freedescr(descr); - free(done_ccb, M_DEVBUF); - return; - } - descr->data_resid = 0; - descr->data_increment = 0; - descr->user_atio = 0; - -#ifdef CAMDEBUG - { - int i; - char dcb[128]; - for (dcb[0] = 0, i = 0; i < atio->cdb_len; i++) { - snprintf(dcb, sizeof dcb, - "%s %02x", dcb, cdb[i] & 0xff); - } - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("flags %x cdb:%s\n", atio->ccb_h.flags, dcb)); - } -#endif - if (atio->sense_len != 0) { - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("ATIO with sense_len\n")); - - /* - * We had an error in the reception of - * this command. Immediately issue a CA. - */ - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_NONE; - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_CHECK_COND; - copy_sense(softc, istate, (u_int8_t *)&atio->sense_data, - atio->sense_len); - set_ca_condition(periph, atio->init_id, CA_CMD_SENSE); - } else if (istate->pending_ca == 0 - && istate->pending_ua != 0 - && cdb[0] != INQUIRY) { - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("pending_ca %d pending_ua %d\n", - istate->pending_ca, istate->pending_ua)); - - /* Pending UA, tell initiator */ - /* Direction is always relative to the initator */ - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_NONE; - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_CHECK_COND; - fill_sense(softc, atio->init_id, - SSD_CURRENT_ERROR, SSD_KEY_UNIT_ATTENTION, - 0x29, - istate->pending_ua == UA_POWER_ON ? 1 : 2); - set_ca_condition(periph, atio->init_id, CA_UNIT_ATTN); - } else { - /* - * Save the current CA and UA status so - * they can be used by this command. - */ - ua_types pending_ua; - ca_types pending_ca; - - pending_ua = istate->pending_ua; - pending_ca = istate->pending_ca; - - /* - * As per the SCSI2 spec, 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 - * persistant condition. - */ - istate->pending_ca = CA_NONE; - if (pending_ca == CA_UNIT_ATTN) - istate->pending_ua = UA_NONE; - - /* - * Determine the type of incoming command and - * setup our buffer for a response. - */ - switch (cdb[0]) { - case INQUIRY: - { - struct scsi_inquiry *inq; - struct scsi_sense_data *sense; - - inq = (struct scsi_inquiry *)cdb; - sense = &istate->sense_data; - descr->status = SCSI_STATUS_OK; - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Saw an inquiry!\n")); - /* - * Validate the command. We don't - * support any VPD pages, so complain - * if EVPD is set. - */ - if ((inq->byte2 & SI_EVPD) != 0 - || inq->page_code != 0) { - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_NONE; - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_CHECK_COND; - fill_sense(softc, atio->init_id, - SSD_CURRENT_ERROR, - SSD_KEY_ILLEGAL_REQUEST, - /*asc*/0x24, /*ascq*/0x00); - sense->extra_len = - offsetof(struct scsi_sense_data, - extra_bytes) - - offsetof(struct scsi_sense_data, - extra_len); - set_ca_condition(periph, atio->init_id, - CA_CMD_SENSE); - } - - if ((inq->byte2 & SI_EVPD) != 0) { - 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) { - 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); - } - if (descr->status == SCSI_STATUS_CHECK_COND) - break; - - /* - * Direction is always relative - * to the initator. - */ - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_IN; - descr->data = softc->inq_data; - descr->data_resid = - MIN(softc->inq_data_len, - SCSI_CDB6_LEN(inq->length)); - descr->data_increment = descr->data_resid; - descr->timeout = 5 * 1000; - break; - } - case TEST_UNIT_READY: - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_NONE; - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_OK; - break; - case REQUEST_SENSE: - { - struct scsi_request_sense *rsense; - struct scsi_sense_data *sense; - - rsense = (struct scsi_request_sense *)cdb; - sense = &istate->sense_data; - if (pending_ca == 0) { - fill_sense(softc, atio->init_id, - SSD_CURRENT_ERROR, - SSD_KEY_NO_SENSE, 0x00, - 0x00); - CAM_DEBUG(periph->path, - CAM_DEBUG_PERIPH, - ("No pending CA!\n")); - } - /* - * Direction is always relative - * to the initator. - */ - atio->ccb_h.flags &= ~CAM_DIR_MASK; - atio->ccb_h.flags |= CAM_DIR_IN; - descr->data = sense; - descr->data_resid = - offsetof(struct scsi_sense_data, - extra_len) - + sense->extra_len; - descr->data_resid = - MIN(descr->data_resid, - SCSI_CDB6_LEN(rsense->length)); - descr->data_increment = descr->data_resid; - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_OK; - break; - } - case RECEIVE: - case SEND: - if (SID_TYPE(softc->inq_data) == T_PROCESSOR) { - struct scsi_send_receive *sr; - - sr = (struct scsi_send_receive *)cdb; - - /* - * Direction is always relative - * to the initator. - */ - atio->ccb_h.flags &= ~CAM_DIR_MASK; - descr->data_resid = scsi_3btoul(sr->xfer_len); - descr->timeout = 5 * 1000; - descr->status = SCSI_STATUS_OK; - if (cdb[0] == SEND) { - atio->ccb_h.flags |= CAM_DIR_OUT; - CAM_DEBUG(periph->path, - CAM_DEBUG_PERIPH, - ("Saw a SEND!\n")); - atio->ccb_h.flags |= CAM_DIR_OUT; - TAILQ_INSERT_TAIL(&softc->snd_ccb_queue, - &atio->ccb_h, - periph_links.tqe); - selwakeup(&softc->snd_select); - } else { - atio->ccb_h.flags |= CAM_DIR_IN; - CAM_DEBUG(periph->path, - CAM_DEBUG_PERIPH, - ("Saw a RECEIVE!\n")); - TAILQ_INSERT_TAIL(&softc->rcv_ccb_queue, - &atio->ccb_h, - periph_links.tqe); - selwakeup(&softc->rcv_select); - } - /* - * Attempt to satisfy this request with - * a user buffer. - */ - targrunqueue(periph, softc); - return; - } - default: - /* - * Queue for consumption by our userland - * counterpart and transition to the exception - * state. - */ - descr->data_resid = 0; - descr->data_increment = 0; - descr->user_atio = 1; - TAILQ_INSERT_TAIL(&softc->unknown_atio_queue, - &atio->ccb_h, - periph_links.tqe); - softc->exceptions |= TARG_EXCEPT_UNKNOWN_ATIO; - targfireexception(periph, softc); - return; - } - } - - /* Queue us up to receive a Continue Target I/O ccb. */ - if ((atio->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) { - TAILQ_INSERT_HEAD(&softc->work_queue, &atio->ccb_h, - periph_links.tqe); - priority = 0; - } else { - TAILQ_INSERT_TAIL(&softc->work_queue, &atio->ccb_h, - periph_links.tqe); - priority = 1; - } - xpt_schedule(periph, priority); - break; - } - case XPT_CONT_TARGET_IO: - { - struct ccb_scsiio *csio; - struct ccb_accept_tio *atio; - struct targ_cmd_desc *desc; - struct bio *bp; - int error, lastctio; - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Received completed CTIO\n")); - csio = &done_ccb->csio; - atio = (struct ccb_accept_tio*)done_ccb->ccb_h.ccb_atio; - desc = (struct targ_cmd_desc *)atio->ccb_h.ccb_descr; - - TAILQ_REMOVE(&softc->pending_queue, &done_ccb->ccb_h, - periph_links.tqe); - - if ((done_ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { - printf("CCB with error %x\n", done_ccb->ccb_h.status); - error = targerror(done_ccb, 0, 0); - if (error == ERESTART) - break; - /* - * Right now we don't need to do anything - * prior to unfreezing the queue. This may - * change if certain errors are reported while - * we are in a connected state. - */ - if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) { - printf("Releasing Queue\n"); - cam_release_devq(done_ccb->ccb_h.path, - /*relsim_flags*/0, - /*reduction*/0, - /*timeout*/0, - /*getcount_only*/0); - done_ccb->ccb_h.status &= ~CAM_DEV_QFRZN; - } - } else - error = 0; - - /* - * If we shipped back sense data when completing - * this command, clear the pending CA for it. - */ - if (done_ccb->ccb_h.status & CAM_SENT_SENSE) { - struct initiator_state *istate; - - istate = &softc->istate[csio->init_id]; - if (istate->pending_ca == CA_UNIT_ATTN) - istate->pending_ua = UA_NONE; - istate->pending_ca = CA_NONE; - softc->istate[csio->init_id].pending_ca = CA_NONE; - done_ccb->ccb_h.status &= ~CAM_SENT_SENSE; - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Sent Sense\n")); - done_ccb->ccb_h.flags &= ~CAM_SEND_SENSE; - } - - if (done_ccb->ccb_h.status & CAM_AUTOSNS_VALID) { - struct initiator_state *istate; - - istate = &softc->istate[csio->init_id]; - copy_sense(softc, istate, (u_int8_t *)&csio->sense_data, - csio->sense_len); - set_ca_condition(periph, csio->init_id, CA_CMD_SENSE); - done_ccb->ccb_h.status &= ~CAM_AUTOSNS_VALID; - } - /* - * Was this the last CTIO? - */ - lastctio = done_ccb->ccb_h.status & CAM_SEND_STATUS; - - desc->data_increment -= csio->resid; - desc->data_resid -= desc->data_increment; - if ((bp = desc->bp) != NULL) { - - bp->bio_resid -= desc->data_increment; - bp->bio_error = error; - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Buffer I/O Completed - Resid %ld:%d\n", - bp->bio_resid, desc->data_resid)); - /* - * Send the buffer back to the client if - * either the command has completed or all - * buffer space has been consumed. - */ - if (desc->data_resid == 0 - || bp->bio_resid == 0 - || error != 0) { - if (bp->bio_resid != 0) - /* Short transfer */ - bp->bio_flags |= BIO_ERROR; - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Completing a buffer\n")); - biodone(bp); - desc->bp = NULL; - } - } - - if ((done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0) - atio->ccb_h.status |= CAM_DEV_QFRZN; - xpt_release_ccb(done_ccb); - if (softc->state != TARG_STATE_TEARDOWN) { - if (lastctio) { - /* - * Send the original accept TIO back to the - * controller to handle more work. - */ - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Returning ATIO to target SIM\n")); - atio->ccb_h.ccb_flags = TARG_CCB_NONE; - xpt_action((union ccb *)atio); - break; - } - - if (SID_TYPE(softc->inq_data) == T_PROCESSOR) { - /* Queue us up for another buffer */ - if (atio->cdb_io.cdb_bytes[0] == SEND) { - if (desc->bp != NULL) - TAILQ_INSERT_HEAD(&softc->snd_bio_queue.queue, - bp, bio_queue); - TAILQ_INSERT_HEAD(&softc->snd_ccb_queue, - &atio->ccb_h, - periph_links.tqe); - } else { - if (desc->bp != NULL) - TAILQ_INSERT_HEAD(&softc->rcv_bio_queue.queue, - bp, bio_queue); - TAILQ_INSERT_HEAD(&softc->rcv_ccb_queue, - &atio->ccb_h, - periph_links.tqe); - } - desc->bp = NULL; - } - targrunqueue(periph, softc); - } else { - if (desc->bp != NULL) { - bp->bio_flags |= BIO_ERROR; - bp->bio_error = ENXIO; - biodone(bp); - } - freedescr(desc); - free(atio, M_DEVBUF); - } - break; - } case XPT_IMMED_NOTIFY: - { - int frozen; - - frozen = (done_ccb->ccb_h.status & CAM_DEV_QFRZN) != 0; - if (softc->state == TARG_STATE_TEARDOWN) { - SLIST_REMOVE(&softc->immed_notify_slist, - &done_ccb->ccb_h, ccb_hdr, - periph_links.sle); - free(done_ccb, M_DEVBUF); - } else if (done_ccb->ccb_h.status == CAM_REQ_ABORTED) { - free(done_ccb, M_DEVBUF); - } else { - printf("Saw event %x:%x\n", done_ccb->ccb_h.status, - done_ccb->cin.message_args[0]); - /* Process error condition. */ - targinoterror(periph, softc, &done_ccb->cin); - - /* Requeue for another immediate event */ - xpt_action(done_ccb); - } - if (frozen != 0) - cam_release_devq(periph->path, - /*relsim_flags*/0, - /*opening reduction*/0, - /*timeout*/0, - /*getcount_only*/0); - break; - } - case XPT_DEBUG: - wakeup(&done_ccb->ccb_h.cbfcnp); - break; - default: - panic("targdone: Impossible xpt opcode %x encountered.", - done_ccb->ccb_h.func_code); - /* NOTREACHED */ - break; - } -} - -/* - * Transition to the exception state and notify our symbiotic - * userland process of the change. - */ -static void -targfireexception(struct cam_periph *periph, struct targ_softc *softc) -{ - /* - * return all pending buffers with short read/write status so our - * process unblocks, and do a selwakeup on any process queued - * waiting for reads or writes. When the selwakeup is performed, - * the waking process will wakeup, call our poll routine again, - * and pick up the exception. - */ - struct bio *bp; - - if (softc->state != TARG_STATE_NORMAL) - /* Already either tearing down or in exception state */ - return; - - softc->state = TARG_STATE_EXCEPTION; - - while ((bp = bioq_first(&softc->snd_bio_queue)) != NULL) { - bioq_remove(&softc->snd_bio_queue, bp); - bp->bio_flags |= BIO_ERROR; - biodone(bp); - } - - while ((bp = bioq_first(&softc->rcv_bio_queue)) != NULL) { - bioq_remove(&softc->snd_bio_queue, bp); - bp->bio_flags |= BIO_ERROR; - biodone(bp); - } - - selwakeup(&softc->snd_select); - selwakeup(&softc->rcv_select); -} - -static void -targinoterror(struct cam_periph *periph, struct targ_softc *softc, - struct ccb_immed_notify *inot) -{ - cam_status status; - int sense; - - status = inot->ccb_h.status; - sense = (status & CAM_AUTOSNS_VALID) != 0; - status &= CAM_STATUS_MASK; - switch (status) { - case CAM_SCSI_BUS_RESET: - set_unit_attention_cond(periph, /*init_id*/CAM_TARGET_WILDCARD, - UA_BUS_RESET); - abort_pending_transactions(periph, - /*init_id*/CAM_TARGET_WILDCARD, - TARG_TAG_WILDCARD, EINTR, - /*to_held_queue*/FALSE); - softc->exceptions |= TARG_EXCEPT_BUS_RESET_SEEN; - targfireexception(periph, softc); - break; - case CAM_BDR_SENT: - set_unit_attention_cond(periph, /*init_id*/CAM_TARGET_WILDCARD, - UA_BDR); - abort_pending_transactions(periph, CAM_TARGET_WILDCARD, - TARG_TAG_WILDCARD, EINTR, - /*to_held_queue*/FALSE); - softc->exceptions |= TARG_EXCEPT_BDR_RECEIVED; - targfireexception(periph, softc); - break; - case CAM_MESSAGE_RECV: - switch (inot->message_args[0]) { - case MSG_INITIATOR_DET_ERR: - break; - case MSG_ABORT: - break; - case MSG_BUS_DEV_RESET: - break; - case MSG_ABORT_TAG: - break; - case MSG_CLEAR_QUEUE: - break; - case MSG_TERM_IO_PROC: - break; - default: - break; - } + CAM_DEBUG_PRINT(CAM_DEBUG_PERIPH, ("freeing ccb %p\n", ccb)); + FREE(ccb, M_TARG); break; default: - break; - } -} - -static int -targerror(union ccb *ccb, u_int32_t cam_flags, u_int32_t sense_flags) -{ - struct cam_periph *periph; - struct targ_softc *softc; - struct ccb_scsiio *csio; - struct initiator_state *istate; - cam_status status; - int frozen; - int sense; - int error; - int on_held_queue; - - periph = xpt_path_periph(ccb->ccb_h.path); - softc = (struct targ_softc *)periph->softc; - status = ccb->ccb_h.status; - sense = (status & CAM_AUTOSNS_VALID) != 0; - frozen = (status & CAM_DEV_QFRZN) != 0; - status &= CAM_STATUS_MASK; - on_held_queue = FALSE; - csio = &ccb->csio; - istate = &softc->istate[csio->init_id]; - switch (status) { - case CAM_REQ_ABORTED: - if ((ccb->ccb_h.ccb_flags & TARG_CCB_ABORT_TO_HELDQ) != 0) { - - /* - * Place this CCB into the initiators - * 'held' queue until the pending CA is cleared. - * If there is no CA pending, reissue immediately. - */ - if (istate->pending_ca == 0) { - ccb->ccb_h.ccb_flags = TARG_CCB_NONE; - xpt_action(ccb); - } else { - ccb->ccb_h.ccb_flags = TARG_CCB_HELDQ; - TAILQ_INSERT_TAIL(&softc->pending_queue, - &ccb->ccb_h, - periph_links.tqe); - } - /* The command will be retried at a later time. */ - on_held_queue = TRUE; - error = ERESTART; - break; - } - /* FALLTHROUGH */ - case CAM_SCSI_BUS_RESET: - case CAM_BDR_SENT: - case CAM_REQ_TERMIO: - case CAM_CMD_TIMEOUT: - /* Assume we did not send any data */ - csio->resid = csio->dxfer_len; - error = EIO; - break; - case CAM_SEL_TIMEOUT: - if (ccb->ccb_h.retry_count > 0) { - ccb->ccb_h.retry_count--; - error = ERESTART; + /* Send back CCB if we got it from the periph */ + if (XPT_FC_IS_QUEUED(ccb)) { + CAM_DEBUG_PRINT(CAM_DEBUG_PERIPH, + ("returning queued ccb %p\n", ccb)); + xpt_release_ccb(ccb); } else { - /* "Select or reselect failure" */ - csio->resid = csio->dxfer_len; - fill_sense(softc, csio->init_id, SSD_CURRENT_ERROR, - SSD_KEY_HARDWARE_ERROR, 0x45, 0x00); - set_ca_condition(periph, csio->init_id, CA_CMD_SENSE); - error = EIO; + CAM_DEBUG_PRINT(CAM_DEBUG_PERIPH, + ("freeing ccb %p\n", ccb)); + FREE(ccb, M_TARG); } break; - case CAM_UNCOR_PARITY: - /* "SCSI parity error" */ - fill_sense(softc, csio->init_id, SSD_CURRENT_ERROR, - SSD_KEY_HARDWARE_ERROR, 0x47, 0x00); - set_ca_condition(periph, csio->init_id, CA_CMD_SENSE); - csio->resid = csio->dxfer_len; - error = EIO; - break; - case CAM_NO_HBA: - csio->resid = csio->dxfer_len; - error = ENXIO; - break; - case CAM_SEQUENCE_FAIL: - if (sense != 0) { - copy_sense(softc, istate, (u_int8_t *)&csio->sense_data, - csio->sense_len); - set_ca_condition(periph, csio->init_id, CA_CMD_SENSE); - } - csio->resid = csio->dxfer_len; - error = EIO; - break; - case CAM_IDE: - /* "Initiator detected error message received" */ - fill_sense(softc, csio->init_id, SSD_CURRENT_ERROR, - SSD_KEY_HARDWARE_ERROR, 0x48, 0x00); - set_ca_condition(periph, csio->init_id, CA_CMD_SENSE); - csio->resid = csio->dxfer_len; - error = EIO; - break; - case CAM_REQUEUE_REQ: - printf("Requeue Request!\n"); - error = ERESTART; - break; - default: - csio->resid = csio->dxfer_len; - error = EIO; - panic("targerror: Unexpected status %x encounterd", status); - /* NOTREACHED */ - } - - if (error == ERESTART || error == 0) { - /* Clear the QFRZN flag as we will release the queue */ - if (frozen != 0) - ccb->ccb_h.status &= ~CAM_DEV_QFRZN; - - if (error == ERESTART && !on_held_queue) - xpt_action(ccb); - - if (frozen != 0) - cam_release_devq(ccb->ccb_h.path, - /*relsim_flags*/0, - /*opening reduction*/0, - /*timeout*/0, - /*getcount_only*/0); } - return (error); } -static struct targ_cmd_desc* -allocdescr() +static struct targ_cmd_descr * +targgetdescr(struct targ_softc *softc) { - struct targ_cmd_desc* descr; - - /* Allocate the targ_descr structure */ - descr = (struct targ_cmd_desc *) - malloc(sizeof(*descr), M_DEVBUF, M_NOWAIT); - if (descr == NULL) - return (NULL); - - bzero(descr, sizeof(*descr)); + struct targ_cmd_descr *descr; - /* Allocate buffer backing store */ - descr->backing_store = malloc(MAX_BUF_SIZE, M_DEVBUF, M_NOWAIT); - if (descr->backing_store == NULL) { - free(descr, M_DEVBUF); - return (NULL); - } - descr->max_size = MAX_BUF_SIZE; + MALLOC(descr, struct targ_cmd_descr *, sizeof(*descr), M_TARG, + M_WAITOK); + descr->mapinfo.num_bufs_used = 0; return (descr); } static void -freedescr(struct targ_cmd_desc *descr) -{ - free(descr->backing_store, M_DEVBUF); - free(descr, M_DEVBUF); -} - -static void -fill_sense(struct targ_softc *softc, u_int initiator_id, u_int error_code, - u_int sense_key, u_int asc, u_int ascq) -{ - struct initiator_state *istate; - struct scsi_sense_data *sense; - - istate = &softc->istate[initiator_id]; - sense = &istate->sense_data; - bzero(sense, sizeof(*sense)); - sense->error_code = error_code; - sense->flags = sense_key; - sense->add_sense_code = asc; - sense->add_sense_code_qual = ascq; - - sense->extra_len = offsetof(struct scsi_sense_data, fru) - - offsetof(struct scsi_sense_data, extra_len); -} - -static void -copy_sense(struct targ_softc *softc, struct initiator_state *istate, - u_int8_t *sense_buffer, size_t sense_len) +targinit(void) { - struct scsi_sense_data *sense; - size_t copylen; - - sense = &istate->sense_data; - copylen = sizeof(*sense); - if (copylen > sense_len) - copylen = sense_len; - bcopy(sense_buffer, sense, copylen); + mtx_init(&targ_mtx, "targ global", NULL, MTX_DEF); + EVENTHANDLER_REGISTER(dev_clone, targclone, 0, 1000); + cdevsw_add(&targ_cdevsw); } static void -set_unit_attention_cond(struct cam_periph *periph, - u_int initiator_id, ua_types ua) +targclone(void *arg, char *name, int namelen, dev_t *dev) { - int start; - int end; - struct targ_softc *softc; + int u; - softc = (struct targ_softc *)periph->softc; - if (initiator_id == CAM_TARGET_WILDCARD) { - start = 0; - end = MAX_INITIATORS - 1; - } else - start = end = initiator_id; - - while (start <= end) { - softc->istate[start].pending_ua = ua; - start++; - } + if (*dev != NODEV) + return; + if (dev_stdclone(name, NULL, "targ", &u) != 1) + return; + *dev = make_dev(&targ_cdevsw, unit2minor(u), UID_ROOT, GID_WHEEL, + 0600, "targ%d", u); + (*dev)->si_flags |= SI_CHEAPCLONE; } static void -set_ca_condition(struct cam_periph *periph, u_int initiator_id, ca_types ca) +targasync(void *callback_arg, u_int32_t code, struct cam_path *path, void *arg) { - struct targ_softc *softc; - - softc = (struct targ_softc *)periph->softc; - softc->istate[initiator_id].pending_ca = ca; - abort_pending_transactions(periph, initiator_id, TARG_TAG_WILDCARD, - /*errno*/0, /*to_held_queue*/TRUE); + /* All events are handled in usermode by INOTs */ + panic("targasync() called, should be an INOT instead"); } +/* Cancel all pending requests and CCBs awaiting work. */ static void -abort_pending_transactions(struct cam_periph *periph, u_int initiator_id, - u_int tag_id, int errno, int to_held_queue) +abort_all_pending(struct targ_softc *softc) { - struct ccb_abort cab; - struct ccb_queue *atio_queues[3]; - struct targ_softc *softc; - struct ccb_hdr *ccbh; - u_int i; - - softc = (struct targ_softc *)periph->softc; + struct targ_cmd_descr *descr; + struct ccb_abort cab; + struct ccb_hdr *ccb_h; - atio_queues[0] = &softc->work_queue; - atio_queues[1] = &softc->snd_ccb_queue; - atio_queues[2] = &softc->rcv_ccb_queue; + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, ("abort_all_pending\n")); - /* First address the ATIOs awaiting resources */ - for (i = 0; i < (sizeof(atio_queues) / sizeof(*atio_queues)); i++) { - struct ccb_queue *atio_queue; + /* First abort the descriptors awaiting resources */ + while ((descr = TAILQ_FIRST(&softc->work_queue)) != NULL) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Aborting descr from workq %p\n", descr)); + TAILQ_REMOVE(&softc->work_queue, descr, tqe); + TAILQ_INSERT_TAIL(&softc->abort_queue, descr, tqe); + } - if (to_held_queue) { - /* - * The device queue is frozen anyway, so there - * is nothing for us to do. - */ - continue; - } - atio_queue = atio_queues[i]; - ccbh = TAILQ_FIRST(atio_queue); - while (ccbh != NULL) { - struct ccb_accept_tio *atio; - struct targ_cmd_desc *desc; - - atio = (struct ccb_accept_tio *)ccbh; - desc = (struct targ_cmd_desc *)atio->ccb_h.ccb_descr; - ccbh = TAILQ_NEXT(ccbh, periph_links.tqe); - - /* Only abort the CCBs that match */ - if ((atio->init_id != initiator_id - && initiator_id != CAM_TARGET_WILDCARD) - || (tag_id != TARG_TAG_WILDCARD - && ((atio->ccb_h.flags & CAM_TAG_ACTION_VALID) == 0 - || atio->tag_id != tag_id))) - continue; - - TAILQ_REMOVE(atio_queue, &atio->ccb_h, - periph_links.tqe); - - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Aborting ATIO\n")); - if (desc->bp != NULL) { - desc->bp->bio_flags |= BIO_ERROR; - if (softc->state != TARG_STATE_TEARDOWN) - desc->bp->bio_error = errno; - else - desc->bp->bio_error = ENXIO; - biodone(desc->bp); - desc->bp = NULL; - } - if (softc->state == TARG_STATE_TEARDOWN) { - freedescr(desc); - free(atio, M_DEVBUF); - } else { - /* Return the ATIO back to the controller */ - atio->ccb_h.ccb_flags = TARG_CCB_NONE; - xpt_action((union ccb *)atio); - } + /* + * Then abort all pending CCBs. + * targdone() will return the aborted CCB via user_ccb_queue + */ + xpt_setup_ccb(&cab.ccb_h, softc->path, /*priority*/0); + cab.ccb_h.func_code = XPT_ABORT; + cab.ccb_h.status = CAM_REQ_CMP_ERR; + TAILQ_FOREACH(ccb_h, &softc->pending_ccb_queue, periph_links.tqe) { + CAM_DEBUG(softc->path, CAM_DEBUG_PERIPH, + ("Aborting pending CCB %p\n", ccb_h)); + cab.abort_ccb = (union ccb *)ccb_h; + xpt_action((union ccb *)&cab); + if (cab.ccb_h.status != CAM_REQ_CMP) { + xpt_print_path(cab.ccb_h.path); + printf("Unable to abort CCB, status %#x\n", + cab.ccb_h.status); } } - ccbh = TAILQ_FIRST(&softc->pending_queue); - while (ccbh != NULL) { - struct ccb_scsiio *csio; + /* If we aborted at least one pending CCB ok, wait for it. */ + if (cab.ccb_h.status == CAM_REQ_CMP) { + msleep(&softc->pending_ccb_queue, &softc->mtx, + PRIBIO | PCATCH, "tgabrt", 0); + } - csio = (struct ccb_scsiio *)ccbh; - ccbh = TAILQ_NEXT(ccbh, periph_links.tqe); + /* If we aborted anything from the work queue, wakeup user. */ + if (!TAILQ_EMPTY(&softc->user_ccb_queue) + || !TAILQ_EMPTY(&softc->abort_queue)) + notify_user(softc); +} - /* Only abort the CCBs that match */ - if ((csio->init_id != initiator_id - && initiator_id != CAM_TARGET_WILDCARD) - || (tag_id != TARG_TAG_WILDCARD - && ((csio->ccb_h.flags & CAM_TAG_ACTION_VALID) == 0 - || csio->tag_id != tag_id))) - continue; +/* Notify the user that data is ready */ +static void +notify_user(struct targ_softc *softc) +{ + /* + * Notify users sleeping via poll(), kqueue(), and + * blocking read(). + */ + selwakeup(&softc->read_select); + KNOTE(&softc->read_select.si_note, 0); + wakeup(&softc->user_ccb_queue); +} - CAM_DEBUG(periph->path, CAM_DEBUG_PERIPH, - ("Aborting CTIO\n")); +/* Convert CAM status to errno values */ +static int +targcamstatus(cam_status status) +{ + switch (status & CAM_STATUS_MASK) { + case CAM_REQ_CMP: /* CCB request completed without error */ + return (0); + case CAM_REQ_INPROG: /* CCB request is in progress */ + return (EINPROGRESS); + case CAM_REQ_CMP_ERR: /* CCB request completed with an error */ + return (EIO); + case CAM_PROVIDE_FAIL: /* Unable to provide requested capability */ + return (ENOTTY); + case CAM_FUNC_NOTAVAIL: /* The requested function is not available */ + return (ENOTSUP); + case CAM_LUN_ALRDY_ENA: /* LUN is already enabled for target mode */ + return (EADDRINUSE); + case CAM_PATH_INVALID: /* Supplied Path ID is invalid */ + case CAM_DEV_NOT_THERE: /* SCSI Device Not Installed/there */ + return (ENOENT); + case CAM_REQ_ABORTED: /* CCB request aborted by the host */ + return (ECANCELED); + case CAM_CMD_TIMEOUT: /* Command timeout */ + return (ETIMEDOUT); + case CAM_REQUEUE_REQ: /* Requeue to preserve transaction ordering */ + return (EAGAIN); + case CAM_REQ_INVALID: /* CCB request was invalid */ + return (EINVAL); + case CAM_RESRC_UNAVAIL: /* Resource Unavailable */ + return (ENOMEM); + case CAM_BUSY: /* CAM subsytem is busy */ + case CAM_UA_ABORT: /* Unable to abort CCB request */ + return (EBUSY); + default: + return (ENXIO); + } +} - TAILQ_REMOVE(&softc->pending_queue, &csio->ccb_h, - periph_links.tqe); +static size_t +targccblen(xpt_opcode func_code) +{ + int len; - if (to_held_queue != 0) - csio->ccb_h.ccb_flags |= TARG_CCB_ABORT_TO_HELDQ; - xpt_setup_ccb(&cab.ccb_h, csio->ccb_h.path, /*priority*/1); - cab.abort_ccb = (union ccb *)csio; - xpt_action((union ccb *)&cab); - if (cab.ccb_h.status != CAM_REQ_CMP) { - xpt_print_path(cab.ccb_h.path); - printf("Unable to abort CCB. Status %x\n", - cab.ccb_h.status); - } + /* Codes we expect to see as a target */ + switch (func_code) { + case XPT_CONT_TARGET_IO: + case XPT_SCSI_IO: + len = sizeof(struct ccb_scsiio); + break; + case XPT_ACCEPT_TARGET_IO: + len = sizeof(struct ccb_accept_tio); + break; + case XPT_IMMED_NOTIFY: + len = sizeof(struct ccb_immed_notify); + break; + case XPT_REL_SIMQ: + len = sizeof(struct ccb_relsim); + break; + case XPT_PATH_INQ: + len = sizeof(struct ccb_pathinq); + break; + case XPT_DEBUG: + len = sizeof(struct ccb_debug); + break; + case XPT_ABORT: + len = sizeof(struct ccb_abort); + break; + case XPT_EN_LUN: + len = sizeof(struct ccb_en_lun); + break; + default: + len = sizeof(union ccb); + break; } + + return (len); } diff --git a/sys/cam/scsi/scsi_targetio.h b/sys/cam/scsi/scsi_targetio.h index a67f78e..b6f57db 100644 --- a/sys/cam/scsi/scsi_targetio.h +++ b/sys/cam/scsi/scsi_targetio.h @@ -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) - -/* - * Retreive an Accept Target I/O CCB for a command that is not handled - * directly by the kernel target driver. - */ -#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. + * 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. */ -#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. + * 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 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_ */ diff --git a/sys/modules/cam/Makefile b/sys/modules/cam/Makefile index 633668d..0f0d238 100644 --- a/sys/modules/cam/Makefile +++ b/sys/modules/cam/Makefile @@ -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 |