diff options
| -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 67f2a4ec24f0..abd5a969facf 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 000000000000..d624cfb6b1cb --- /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 000000000000..d2ab3ded40ec --- /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 7c58db207c4b..e1e0a846503a 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 000000000000..7e179ff256bc --- /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 000000000000..30bfeffb90ac --- /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 2c8f32e27652..b775c4388c2b 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 a67f78e00f4a..b6f57dbed809 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 633668d5c195..0f0d2385626c 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 |
