From 388705e429b0faac18baf802ff2289cda577c2f3 Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Tue, 25 Aug 2009 18:40:34 -0700 Subject: [PATCH] Kernel - Add scsi/scsi_sg linux-compatible SCSI pass through device Taken-from: FreeBSD --- sys/bus/cam/scsi/scsi_sg.c | 1006 +++++++++++++++++++++++++++++++++++++ sys/bus/cam/scsi/scsi_sg.h | 145 ++++++ sys/conf/files | 1 + sys/config/GENERIC | 1 + sys/emulation/linux/linux_ioctl.h | 138 ++++- 5 files changed, 1264 insertions(+), 27 deletions(-) create mode 100644 sys/bus/cam/scsi/scsi_sg.c create mode 100644 sys/bus/cam/scsi/scsi_sg.h diff --git a/sys/bus/cam/scsi/scsi_sg.c b/sys/bus/cam/scsi/scsi_sg.c new file mode 100644 index 0000000..054e747 --- /dev/null +++ b/sys/bus/cam/scsi/scsi_sg.c @@ -0,0 +1,1006 @@ +/*- + * Copyright (c) 2007 Scott Long + * 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. + */ + +/* + * scsi_sg peripheral driver. This driver is meant to implement the Linux + * SG passthrough interface for SCSI. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../cam.h" +#include "../cam_ccb.h" +#include "../cam_periph.h" +#include "../cam_queue.h" +#include "../cam_xpt_periph.h" +#include "../cam_debug.h" +#include "../cam_sim.h" + +#include + +#include "scsi_all.h" +#include "scsi_message.h" +#include "scsi_sg.h" + +typedef enum { + SG_FLAG_OPEN = 0x01, + SG_FLAG_LOCKED = 0x02, + SG_FLAG_INVALID = 0x04 +} sg_flags; + +typedef enum { + SG_STATE_NORMAL +} sg_state; + +typedef enum { + SG_RDWR_FREE, + SG_RDWR_INPROG, + SG_RDWR_DONE +} sg_rdwr_state; + +typedef enum { + SG_CCB_RDWR_IO, + SG_CCB_WAITING +} sg_ccb_types; + +#define ccb_type ppriv_field0 +#define ccb_rdwr ppriv_ptr1 + +struct sg_rdwr { + TAILQ_ENTRY(sg_rdwr) rdwr_link; + int tag; + int state; + int buf_len; + char *buf; + union ccb *ccb; + union { + struct sg_header hdr; + struct sg_io_hdr io_hdr; + } hdr; +}; + +struct sg_softc { + sg_state state; + sg_flags flags; + struct devstat device_stats; + TAILQ_HEAD(, sg_rdwr) rdwr_done; + cdev_t dev; + int sg_timeout; + int sg_user_timeout; + uint8_t pd_type; + union ccb saved_ccb; +}; + +static d_open_t sgopen; +static d_close_t sgclose; +static d_ioctl_t sgioctl; +static d_write_t sgwrite; +static d_read_t sgread; + +static periph_init_t sginit; +static periph_ctor_t sgregister; +static periph_oninv_t sgoninvalidate; +static periph_dtor_t sgcleanup; +static periph_start_t sgstart; +static void sgasync(void *callback_arg, uint32_t code, + struct cam_path *path, void *arg); +static void sgdone(struct cam_periph *periph, union ccb *done_ccb); +static int sgsendccb(struct cam_periph *periph, union ccb *ccb); +static int sgsendrdwr(struct cam_periph *periph, union ccb *ccb); +static int sgerror(union ccb *ccb, uint32_t cam_flags, + uint32_t sense_flags); +static void sg_scsiio_status(struct ccb_scsiio *csio, + u_short *hoststat, u_short *drvstat); + +static int scsi_group_len(u_char cmd); + +static struct periph_driver sgdriver = +{ + sginit, "sg", + TAILQ_HEAD_INITIALIZER(sgdriver.units), /* gen */ 0 +}; +PERIPHDRIVER_DECLARE(sg, sgdriver); + +static struct dev_ops sg_ops = { + { "sg", 0, D_DISK }, + .d_open = sgopen, + .d_close = sgclose, + .d_read = sgread, + .d_write = sgwrite, + .d_ioctl = sgioctl +}; + +static int sg_version = 30125; + +static void +sginit(void) +{ + cam_status status; + + /* + * Install a global async callback. This callback will receive aync + * callbacks like "new device found". + */ + status = xpt_register_async(AC_FOUND_DEVICE, sgasync, NULL, NULL); + + if (status != CAM_REQ_CMP) { + kprintf("sg: Failed to attach master async callbac " + "due to status 0x%x!\n", status); + } +} + +static void +sgoninvalidate(struct cam_periph *periph) +{ + struct sg_softc *softc; + + softc = (struct sg_softc *)periph->softc; + + /* + * Deregister any async callbacks. + */ + xpt_register_async(0, sgasync, periph, periph->path); + + softc->flags |= SG_FLAG_INVALID; + + /* + * XXX Return all queued I/O with ENXIO. + * XXX Handle any transactions queued to the card + * with XPT_ABORT_CCB. + */ + + if (bootverbose) { + xpt_print(periph->path, "lost device\n"); + } +} + +static void +sgcleanup(struct cam_periph *periph) +{ + struct sg_softc *softc; + + softc = (struct sg_softc *)periph->softc; + if (bootverbose) + xpt_print(periph->path, "removing device entry\n"); + devstat_remove_entry(&softc->device_stats); + cam_periph_unlock(periph); + destroy_dev(softc->dev); + cam_periph_lock(periph); + kfree(softc, M_DEVBUF); +} + +static void +sgasync(void *callback_arg, uint32_t code, struct cam_path *path, void *arg) +{ + struct cam_periph *periph; + + periph = (struct cam_periph *)callback_arg; + + switch (code) { + case AC_FOUND_DEVICE: + { + struct ccb_getdev *cgd; + cam_status status; + + cgd = (struct ccb_getdev *)arg; + if (cgd == NULL) + break; + +#if 0 + if (cgd->protocol != PROTO_SCSI) + break; +#endif + + /* + * Allocate a peripheral instance for this device and + * start the probe process. + */ + status = cam_periph_alloc(sgregister, sgoninvalidate, + sgcleanup, sgstart, + "sg", CAM_PERIPH_BIO, cgd->ccb_h.path, + sgasync, AC_FOUND_DEVICE, cgd); + if ((status != CAM_REQ_CMP) && (status != CAM_REQ_INPROG)) { + const struct cam_status_entry *entry; + + entry = cam_fetch_status_entry(status); + kprintf("sgasync: Unable to attach new device " + "due to status %#x: %s\n", status, entry ? + entry->status_text : "Unknown"); + } + break; + } + default: + cam_periph_async(periph, code, path, arg); + break; + } +} + +static cam_status +sgregister(struct cam_periph *periph, void *arg) +{ + struct sg_softc *softc; + struct ccb_getdev *cgd; + int no_tags; + + cgd = (struct ccb_getdev *)arg; + if (periph == NULL) { + kprintf("sgregister: periph was NULL!!\n"); + return (CAM_REQ_CMP_ERR); + } + + if (cgd == NULL) { + kprintf("sgregister: no getdev CCB, can't register device\n"); + return (CAM_REQ_CMP_ERR); + } + + softc = kmalloc(sizeof(*softc), M_DEVBUF, M_WAITOK | M_ZERO); + if (softc == NULL) { + kprintf("sgregister: Unable to allocate softc\n"); + return (CAM_REQ_CMP_ERR); + } + + softc->state = SG_STATE_NORMAL; + softc->pd_type = SID_TYPE(&cgd->inq_data); + softc->sg_timeout = SG_DEFAULT_TIMEOUT / SG_DEFAULT_HZ * hz; + softc->sg_user_timeout = SG_DEFAULT_TIMEOUT; + TAILQ_INIT(&softc->rdwr_done); + periph->softc = softc; + + /* + * We pass in 0 for all blocksize, since we don't know what the + * blocksize of the device is, if it even has a blocksize. + */ + cam_periph_unlock(periph); + no_tags = (cgd->inq_data.flags & SID_CmdQue) == 0; + devstat_add_entry(&softc->device_stats, "sg", + periph->unit_number, 0, + DEVSTAT_NO_BLOCKSIZE | + (no_tags ? DEVSTAT_NO_ORDERED_TAGS : 0), + softc->pd_type | + DEVSTAT_TYPE_IF_SCSI | DEVSTAT_PRIORITY_PASS, + DEVSTAT_PRIORITY_PASS); + + /* Register the device */ + softc->dev = make_dev(&sg_ops, periph->unit_number, + UID_ROOT, GID_OPERATOR, 0600, "%s%d", + periph->periph_name, periph->unit_number); + make_dev_alias(softc->dev, "sg%c", 'a' + periph->unit_number); + cam_periph_lock(periph); + softc->dev->si_drv1 = periph; + + /* + * Add as async callback so that we get + * notified if this device goes away. + */ + xpt_register_async(AC_LOST_DEVICE, sgasync, periph, periph->path); + + if (bootverbose) + xpt_announce_periph(periph, NULL); + + return (CAM_REQ_CMP); +} + +static void +sgstart(struct cam_periph *periph, union ccb *start_ccb) +{ + struct sg_softc *softc; + + softc = (struct sg_softc *)periph->softc; + + switch (softc->state) { + case SG_STATE_NORMAL: + start_ccb->ccb_h.ccb_type = SG_CCB_WAITING; + SLIST_INSERT_HEAD(&periph->ccb_list, &start_ccb->ccb_h, + periph_links.sle); + periph->immediate_priority = CAM_PRIORITY_NONE; + wakeup(&periph->ccb_list); + break; + } +} + +static void +sgdone(struct cam_periph *periph, union ccb *done_ccb) +{ + struct sg_softc *softc; + struct ccb_scsiio *csio; + + softc = (struct sg_softc *)periph->softc; + csio = &done_ccb->csio; + switch (csio->ccb_h.ccb_type) { + case SG_CCB_WAITING: + /* Caller will release the CCB */ + wakeup(&done_ccb->ccb_h.cbfcnp); + return; + case SG_CCB_RDWR_IO: + { + struct sg_rdwr *rdwr; + int state; + + devstat_end_transaction( + &softc->device_stats, + csio->dxfer_len, + csio->tag_action & 0xf, + ((csio->ccb_h.flags & CAM_DIR_MASK) == + CAM_DIR_NONE) ? DEVSTAT_NO_DATA : + ((csio->ccb_h.flags & CAM_DIR_OUT) ? + DEVSTAT_WRITE : DEVSTAT_READ)); + + rdwr = done_ccb->ccb_h.ccb_rdwr; + state = rdwr->state; + rdwr->state = SG_RDWR_DONE; + wakeup(rdwr); + break; + } + default: + panic("unknown sg CCB type"); + } +} + +static int +sgopen(struct dev_open_args *ap) +/*cdev_t dev, int flags, int fmt, struct thread *td)*/ +{ + struct cam_periph *periph; + struct sg_softc *softc; + int error = 0; + + periph = (struct cam_periph *)ap->a_head.a_dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + /* + * Don't allow access when we're running at a high securelevel. + */ + if (securelevel > 1) { + cam_periph_unlock(periph); + cam_periph_release(periph); + return(EPERM); + } + cam_periph_lock(periph); + + softc = (struct sg_softc *)periph->softc; + if (softc->flags & SG_FLAG_INVALID) { + cam_periph_unlock(periph); + return (ENXIO); + } + + if ((softc->flags & SG_FLAG_OPEN) == 0) { + softc->flags |= SG_FLAG_OPEN; + cam_periph_unlock(periph); + } else { + /* Device closes aren't symmetrical, fix up the refcount. */ + cam_periph_unlock(periph); + cam_periph_release(periph); + } + + return (error); +} + +static int +sgclose(struct dev_close_args *ap) +/* cdev_t dev, int flag, int fmt, struct thread *td) */ +{ + struct cam_periph *periph; + struct sg_softc *softc; + + periph = (struct cam_periph *)ap->a_head.a_dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + cam_periph_lock(periph); + + softc = (struct sg_softc *)periph->softc; + softc->flags &= ~SG_FLAG_OPEN; + + cam_periph_unlock(periph); + cam_periph_release(periph); + + return (0); +} + +static int +sgioctl(struct dev_ioctl_args *ap) +/* cdev_t dev, u_long cmd, caddr_t arg, int flag, struct thread *td) */ +{ + union ccb *ccb; + struct ccb_scsiio *csio; + struct cam_periph *periph; + struct sg_softc *softc; + struct sg_io_hdr req; + int dir, error; + + periph = (struct cam_periph *)ap->a_head.a_dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + cam_periph_lock(periph); + + softc = (struct sg_softc *)periph->softc; + error = 0; + + switch (ap->a_cmd) { + case LINUX_SCSI_GET_BUS_NUMBER: { + int busno; + + busno = xpt_path_path_id(periph->path); + error = copyout(&busno, ap->a_data, sizeof(busno)); + break; + } + case LINUX_SCSI_GET_IDLUN: { + struct scsi_idlun idlun; + struct cam_sim *sim; + + idlun.dev_id = xpt_path_target_id(periph->path); + sim = xpt_path_sim(periph->path); + idlun.host_unique_id = sim->unit_number; + error = copyout(&idlun, ap->a_data, sizeof(idlun)); + break; + } + case SG_GET_VERSION_NUM: + case LINUX_SG_GET_VERSION_NUM: + error = copyout(&sg_version, ap->a_data, sizeof(sg_version)); + break; + case SG_SET_TIMEOUT: + case LINUX_SG_SET_TIMEOUT: { + u_int user_timeout; + + error = copyin(ap->a_data, &user_timeout, sizeof(u_int)); + if (error == 0) { + softc->sg_user_timeout = user_timeout; + softc->sg_timeout = user_timeout / SG_DEFAULT_HZ * hz; + } + break; + } + case SG_GET_TIMEOUT: + case LINUX_SG_GET_TIMEOUT: + /* + * The value is returned directly to the syscall. + */ + ap->a_sysmsg->sm_result.iresult = softc->sg_user_timeout; + error = 0; + break; + case SG_IO: + case LINUX_SG_IO: + error = copyin(ap->a_data, &req, sizeof(req)); + if (error) + break; + + if (req.cmd_len > IOCDBLEN) { + error = EINVAL; + break; + } + + if (req.iovec_count != 0) { + error = EOPNOTSUPP; + break; + } + + ccb = cam_periph_getccb(periph, /*priority*/5); + csio = &ccb->csio; + + error = copyin(req.cmdp, &csio->cdb_io.cdb_bytes, + req.cmd_len); + if (error) { + xpt_release_ccb(ccb); + break; + } + + switch(req.dxfer_direction) { + case SG_DXFER_TO_DEV: + dir = CAM_DIR_OUT; + break; + case SG_DXFER_FROM_DEV: + dir = CAM_DIR_IN; + break; + case SG_DXFER_TO_FROM_DEV: + dir = CAM_DIR_IN | CAM_DIR_OUT; + break; + case SG_DXFER_NONE: + default: + dir = CAM_DIR_NONE; + break; + } + + cam_fill_csio(csio, + /*retries*/1, + sgdone, + dir|CAM_DEV_QFRZDIS, + MSG_SIMPLE_Q_TAG, + req.dxferp, + req.dxfer_len, + req.mx_sb_len, + req.cmd_len, + req.timeout); + + error = sgsendccb(periph, ccb); + if (error) { + req.host_status = DID_ERROR; + req.driver_status = DRIVER_INVALID; + xpt_release_ccb(ccb); + break; + } + + req.status = csio->scsi_status; + req.masked_status = (csio->scsi_status >> 1) & 0x7f; + sg_scsiio_status(csio, &req.host_status, &req.driver_status); + req.resid = csio->resid; + req.duration = csio->ccb_h.timeout; + req.info = 0; + + error = copyout(&req, ap->a_data, sizeof(req)); + if ((error == 0) && (csio->ccb_h.status & CAM_AUTOSNS_VALID) + && (req.sbp != NULL)) { + req.sb_len_wr = req.mx_sb_len - csio->sense_resid; + error = copyout(&csio->sense_data, req.sbp, + req.sb_len_wr); + } + + xpt_release_ccb(ccb); + break; + + case SG_GET_RESERVED_SIZE: + case LINUX_SG_GET_RESERVED_SIZE: { + int size = 32768; + + error = copyout(&size, ap->a_data, sizeof(size)); + break; + } + + case SG_GET_SCSI_ID: + case LINUX_SG_GET_SCSI_ID: + { + struct sg_scsi_id id; + + id.host_no = 0; /* XXX */ + id.channel = xpt_path_path_id(periph->path); + id.scsi_id = xpt_path_target_id(periph->path); + id.lun = xpt_path_lun_id(periph->path); + id.scsi_type = softc->pd_type; + id.h_cmd_per_lun = 1; + id.d_queue_depth = 1; + id.unused[0] = 0; + id.unused[1] = 0; + + error = copyout(&id, ap->a_data, sizeof(id)); + break; + } + + case SG_EMULATED_HOST: + case SG_SET_TRANSFORM: + case SG_GET_TRANSFORM: + case SG_GET_NUM_WAITING: + case SG_SCSI_RESET: + case SG_GET_REQUEST_TABLE: + case SG_SET_KEEP_ORPHAN: + case SG_GET_KEEP_ORPHAN: + case SG_GET_ACCESS_COUNT: + case SG_SET_FORCE_LOW_DMA: + case SG_GET_LOW_DMA: + case SG_GET_SG_TABLESIZE: + case SG_SET_FORCE_PACK_ID: + case SG_GET_PACK_ID: + case SG_SET_RESERVED_SIZE: + case SG_GET_COMMAND_Q: + case SG_SET_COMMAND_Q: + case SG_SET_DEBUG: + case SG_NEXT_CMD_LEN: + case LINUX_SG_EMULATED_HOST: + case LINUX_SG_SET_TRANSFORM: + case LINUX_SG_GET_TRANSFORM: + case LINUX_SG_GET_NUM_WAITING: + case LINUX_SG_SCSI_RESET: + case LINUX_SG_GET_REQUEST_TABLE: + case LINUX_SG_SET_KEEP_ORPHAN: + case LINUX_SG_GET_KEEP_ORPHAN: + case LINUX_SG_GET_ACCESS_COUNT: + case LINUX_SG_SET_FORCE_LOW_DMA: + case LINUX_SG_GET_LOW_DMA: + case LINUX_SG_GET_SG_TABLESIZE: + case LINUX_SG_SET_FORCE_PACK_ID: + case LINUX_SG_GET_PACK_ID: + case LINUX_SG_SET_RESERVED_SIZE: + case LINUX_SG_GET_COMMAND_Q: + case LINUX_SG_SET_COMMAND_Q: + case LINUX_SG_SET_DEBUG: + case LINUX_SG_NEXT_CMD_LEN: + default: +#ifdef CAMDEBUG + kprintf("sgioctl: rejecting cmd 0x%lx\n", cmd); +#endif + error = ENODEV; + break; + } + + cam_periph_unlock(periph); + return (error); +} + +static int +sgwrite(struct dev_write_args *ap) +/*cdev_t dev, struct uio *uio, int ioflag)*/ +{ + union ccb *ccb; + struct cam_periph *periph; + struct ccb_scsiio *csio; + struct sg_softc *sc; + struct sg_header *hdr; + struct sg_rdwr *rdwr; + u_char cdb_cmd; + char *buf; + int error = 0, cdb_len, buf_len, dir; + struct uio *uio = ap->a_uio; + + periph = ap->a_head.a_dev->si_drv1; + rdwr = kmalloc(sizeof(*rdwr), M_DEVBUF, M_WAITOK | M_ZERO); + hdr = &rdwr->hdr.hdr; + + /* Copy in the header block and sanity check it */ + if (uio->uio_resid < sizeof(*hdr)) { + error = EINVAL; + goto out_hdr; + } + error = uiomove((char *)hdr, sizeof(*hdr), uio); + if (error) + goto out_hdr; + + ccb = xpt_alloc_ccb(); + if (ccb == NULL) { + error = ENOMEM; + goto out_hdr; + } + csio = &ccb->csio; + + /* + * Copy in the CDB block. The designers of the interface didn't + * bother to provide a size for this in the header, so we have to + * figure it out ourselves. + */ + if (uio->uio_resid < 1) + goto out_ccb; + error = uiomove(&cdb_cmd, 1, uio); + if (error) + goto out_ccb; + if (hdr->twelve_byte) + cdb_len = 12; + else + cdb_len = scsi_group_len(cdb_cmd); + /* + * We've already read the first byte of the CDB and advanced the uio + * pointer. Just read the rest. + */ + csio->cdb_io.cdb_bytes[0] = cdb_cmd; + error = uiomove(&csio->cdb_io.cdb_bytes[1], cdb_len - 1, uio); + if (error) + goto out_ccb; + + /* + * Now set up the data block. Again, the designers didn't bother + * to make this reliable. + */ + buf_len = uio->uio_resid; + if (buf_len != 0) { + buf = kmalloc(buf_len, M_DEVBUF, M_WAITOK | M_ZERO); + error = uiomove(buf, buf_len, uio); + if (error) + goto out_buf; + dir = CAM_DIR_OUT; + } else if (hdr->reply_len != 0) { + buf = kmalloc(hdr->reply_len, M_DEVBUF, M_WAITOK | M_ZERO); + buf_len = hdr->reply_len; + dir = CAM_DIR_IN; + } else { + buf = NULL; + buf_len = 0; + dir = CAM_DIR_NONE; + } + + cam_periph_lock(periph); + sc = periph->softc; + xpt_setup_ccb(&ccb->ccb_h, periph->path, /*priority*/5); + cam_fill_csio(csio, + /*retries*/1, + sgdone, + dir|CAM_DEV_QFRZDIS, + MSG_SIMPLE_Q_TAG, + buf, + buf_len, + SG_MAX_SENSE, + cdb_len, + sc->sg_timeout); + + /* + * Send off the command and hope that it works. This path does not + * go through sgstart because the I/O is supposed to be asynchronous. + */ + rdwr->buf = buf; + rdwr->buf_len = buf_len; + rdwr->tag = hdr->pack_id; + rdwr->ccb = ccb; + rdwr->state = SG_RDWR_INPROG; + ccb->ccb_h.ccb_rdwr = rdwr; + ccb->ccb_h.ccb_type = SG_CCB_RDWR_IO; + TAILQ_INSERT_TAIL(&sc->rdwr_done, rdwr, rdwr_link); + error = sgsendrdwr(periph, ccb); + cam_periph_unlock(periph); + return (error); + +out_buf: + kfree(buf, M_DEVBUF); +out_ccb: + xpt_free_ccb(ccb); +out_hdr: + kfree(rdwr, M_DEVBUF); + return (error); +} + +static int +sgread(struct dev_read_args *ap) +/*cdev_t dev, struct uio *uio, int ioflag)*/ +{ + struct ccb_scsiio *csio; + struct cam_periph *periph; + struct sg_softc *sc; + struct sg_header *hdr; + struct sg_rdwr *rdwr; + u_short hstat, dstat; + int error, pack_len, reply_len, pack_id; + struct uio *uio = ap->a_uio; + + periph = ap->a_head.a_dev->si_drv1; + + /* XXX The pack len field needs to be updated and written out instead + * of discarded. Not sure how to do that. + */ + uio->uio_rw = UIO_WRITE; + if ((error = uiomove((char *)&pack_len, 4, uio)) != 0) + return (error); + if ((error = uiomove((char *)&reply_len, 4, uio)) != 0) + return (error); + if ((error = uiomove((char *)&pack_id, 4, uio)) != 0) + return (error); + uio->uio_rw = UIO_READ; + + cam_periph_lock(periph); + sc = periph->softc; +search: + TAILQ_FOREACH(rdwr, &sc->rdwr_done, rdwr_link) { + if (rdwr->tag == pack_id) + break; + } + if (rdwr == NULL) { + cam_periph_unlock(periph); + if (tsleep(&hstat, PCATCH, "sgnull", 0) == ERESTART) + return(EAGAIN); + cam_periph_lock(periph); + goto search; + } + if (rdwr->state != SG_RDWR_DONE) { + tsleep_interlock(rdwr, PCATCH); + cam_periph_unlock(periph); + if (rdwr->state != SG_RDWR_DONE) { + if (tsleep(rdwr, PCATCH | PINTERLOCKED, "sgread", 0) == + ERESTART) { + return (EAGAIN); + } + } + cam_periph_lock(periph); + goto search; + } + TAILQ_REMOVE(&sc->rdwr_done, rdwr, rdwr_link); + cam_periph_unlock(periph); + + hdr = &rdwr->hdr.hdr; + csio = &rdwr->ccb->csio; + sg_scsiio_status(csio, &hstat, &dstat); + hdr->host_status = hstat; + hdr->driver_status = dstat; + hdr->target_status = csio->scsi_status >> 1; + + switch (hstat) { + case DID_OK: + case DID_PASSTHROUGH: + case DID_SOFT_ERROR: + hdr->result = 0; + break; + case DID_NO_CONNECT: + case DID_BUS_BUSY: + case DID_TIME_OUT: + hdr->result = EBUSY; + break; + case DID_BAD_TARGET: + case DID_ABORT: + case DID_PARITY: + case DID_RESET: + case DID_BAD_INTR: + case DID_ERROR: + default: + hdr->result = EIO; + break; + } + + if (dstat == DRIVER_SENSE) { + bcopy(&csio->sense_data, hdr->sense_buffer, + min(csio->sense_len, SG_MAX_SENSE)); +#ifdef CAMDEBUG + scsi_sense_print(csio); +#endif + } + + error = uiomove((char *)&hdr->result, sizeof(*hdr) - + offsetof(struct sg_header, result), uio); + if ((error == 0) && (hdr->result == 0)) + error = uiomove(rdwr->buf, rdwr->buf_len, uio); + + cam_periph_lock(periph); + xpt_free_ccb(rdwr->ccb); + cam_periph_unlock(periph); + kfree(rdwr->buf, M_DEVBUF); + kfree(rdwr, M_DEVBUF); + return (error); +} + +static int +sgsendccb(struct cam_periph *periph, union ccb *ccb) +{ + struct sg_softc *softc; + struct cam_periph_map_info mapinfo; + int error, need_unmap = 0; + + softc = periph->softc; + if (((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) + && (ccb->csio.data_ptr != NULL)) { + bzero(&mapinfo, sizeof(mapinfo)); + + /* + * cam_periph_mapmem calls into proc and vm functions that can + * sleep as well as trigger I/O, so we can't hold the lock. + * Dropping it here is reasonably safe. + */ + cam_periph_unlock(periph); + error = cam_periph_mapmem(ccb, &mapinfo); + cam_periph_lock(periph); + if (error) + return (error); + need_unmap = 1; + } + + error = cam_periph_runccb(ccb, sgerror, CAM_RETRY_SELTO, + SF_RETRY_UA, &softc->device_stats); + + if (need_unmap) + cam_periph_unmapmem(ccb, &mapinfo); + + return (error); +} + +static int +sgsendrdwr(struct cam_periph *periph, union ccb *ccb) +{ + struct sg_softc *softc; + + softc = periph->softc; + devstat_start_transaction(&softc->device_stats); + xpt_action(ccb); + return (0); +} + +static int +sgerror(union ccb *ccb, uint32_t cam_flags, uint32_t sense_flags) +{ + struct cam_periph *periph; + struct sg_softc *softc; + + periph = xpt_path_periph(ccb->ccb_h.path); + softc = (struct sg_softc *)periph->softc; + + return (cam_periph_error(ccb, cam_flags, sense_flags, + &softc->saved_ccb)); +} + +static void +sg_scsiio_status(struct ccb_scsiio *csio, u_short *hoststat, u_short *drvstat) +{ + int status; + + status = csio->ccb_h.status; + + switch (status & CAM_STATUS_MASK) { + case CAM_REQ_CMP: + *hoststat = DID_OK; + *drvstat = 0; + break; + case CAM_REQ_CMP_ERR: + *hoststat = DID_ERROR; + *drvstat = 0; + break; + case CAM_REQ_ABORTED: + *hoststat = DID_ABORT; + *drvstat = 0; + break; + case CAM_REQ_INVALID: + *hoststat = DID_ERROR; + *drvstat = DRIVER_INVALID; + break; + case CAM_DEV_NOT_THERE: + *hoststat = DID_BAD_TARGET; + *drvstat = 0; + break; + case CAM_SEL_TIMEOUT: + *hoststat = DID_NO_CONNECT; + *drvstat = 0; + break; + case CAM_CMD_TIMEOUT: + *hoststat = DID_TIME_OUT; + *drvstat = 0; + break; + case CAM_SCSI_STATUS_ERROR: + *hoststat = DID_ERROR; + *drvstat = 0; + break; + case CAM_SCSI_BUS_RESET: + *hoststat = DID_RESET; + *drvstat = 0; + break; + case CAM_UNCOR_PARITY: + *hoststat = DID_PARITY; + *drvstat = 0; + break; + case CAM_SCSI_BUSY: + *hoststat = DID_BUS_BUSY; + *drvstat = 0; + break; + default: + *hoststat = DID_ERROR; + *drvstat = DRIVER_ERROR; + } + + if (status & CAM_AUTOSNS_VALID) + *drvstat = DRIVER_SENSE; +} + +static int +scsi_group_len(u_char cmd) +{ + int len[] = {6, 10, 10, 12, 12, 12, 10, 10}; + int group; + + group = (cmd >> 5) & 0x7; + return (len[group]); +} diff --git a/sys/bus/cam/scsi/scsi_sg.h b/sys/bus/cam/scsi/scsi_sg.h new file mode 100644 index 0000000..f56fbdf --- /dev/null +++ b/sys/bus/cam/scsi/scsi_sg.h @@ -0,0 +1,145 @@ +/* + * Structures and definitions for SCSI commands to the SG passthrough device. + * + * $FreeBSD: src/sys/cam/scsi/scsi_sg.h,v 1.2 2007/04/10 20:03:42 scottl Exp $ + */ + +#ifndef _SCSI_SG_H +#define _SCSI_SG_H + +#define SGIOC '"' +#define SG_SET_TIMEOUT _IO(SGIOC, 0x01) +#define SG_GET_TIMEOUT _IO(SGIOC, 0x02) +#define SG_EMULATED_HOST _IO(SGIOC, 0x03) +#define SG_SET_TRANSFORM _IO(SGIOC, 0x04) +#define SG_GET_TRANSFORM _IO(SGIOC, 0x05) +#define SG_GET_COMMAND_Q _IO(SGIOC, 0x70) +#define SG_SET_COMMAND_Q _IO(SGIOC, 0x71) +#define SG_GET_RESERVED_SIZE _IO(SGIOC, 0x72) +#define SG_SET_RESERVED_SIZE _IO(SGIOC, 0x75) +#define SG_GET_SCSI_ID _IO(SGIOC, 0x76) +#define SG_SET_FORCE_LOW_DMA _IO(SGIOC, 0x79) +#define SG_GET_LOW_DMA _IO(SGIOC, 0x7a) +#define SG_SET_FORCE_PACK_ID _IO(SGIOC, 0x7b) +#define SG_GET_PACK_ID _IO(SGIOC, 0x7c) +#define SG_GET_NUM_WAITING _IO(SGIOC, 0x7d) +#define SG_SET_DEBUG _IO(SGIOC, 0x7e) +#define SG_GET_SG_TABLESIZE _IO(SGIOC, 0x7f) +#define SG_GET_VERSION_NUM _IO(SGIOC, 0x82) +#define SG_NEXT_CMD_LEN _IO(SGIOC, 0x83) +#define SG_SCSI_RESET _IO(SGIOC, 0x84) +#define SG_IO _IO(SGIOC, 0x85) +#define SG_GET_REQUEST_TABLE _IO(SGIOC, 0x86) +#define SG_SET_KEEP_ORPHAN _IO(SGIOC, 0x87) +#define SG_GET_KEEP_ORPHAN _IO(SGIOC, 0x88) +#define SG_GET_ACCESS_COUNT _IO(SGIOC, 0x89) + +struct sg_io_hdr { + int interface_id; + int dxfer_direction; + u_char cmd_len; + u_char mx_sb_len; + u_short iovec_count; + u_int dxfer_len; + void *dxferp; + u_char *cmdp; + u_char *sbp; + u_int timeout; + u_int flags; + int pack_id; + void *usr_ptr; + u_char status; + u_char masked_status; + u_char msg_status; + u_char sb_len_wr; + u_short host_status; + u_short driver_status; + int resid; + u_int duration; + u_int info; +}; + +#define SG_DXFER_NONE -1 +#define SG_DXFER_TO_DEV -2 +#define SG_DXFER_FROM_DEV -3 +#define SG_DXFER_TO_FROM_DEV -4 +#define SG_DXFER_UNKNOWN -5 + +#define SG_MAX_SENSE 16 + +struct sg_header { + int pack_len; + int reply_len; + int pack_id; + int result; + u_int twelve_byte:1; + u_int target_status:5; + u_int host_status:8; + u_int driver_status:8; + u_int other_flags:10; + u_char sense_buffer[SG_MAX_SENSE]; +}; + +struct sg_scsi_id { + int host_no; + int channel; + int scsi_id; + int lun; + int scsi_type; + short h_cmd_per_lun; + short d_queue_depth; + int unused[2]; +}; + +struct scsi_idlun { + uint32_t dev_id; + uint32_t host_unique_id; +}; + +/* + * Host codes + */ +#define DID_OK 0x00 /* OK */ +#define DID_NO_CONNECT 0x01 /* timeout during connect */ +#define DID_BUS_BUSY 0x02 /* timeout during command */ +#define DID_TIME_OUT 0x03 /* other timeout */ +#define DID_BAD_TARGET 0x04 /* bad target */ +#define DID_ABORT 0x05 /* abort */ +#define DID_PARITY 0x06 /* parity error */ +#define DID_ERROR 0x07 /* internal error */ +#define DID_RESET 0x08 /* reset by somebody */ +#define DID_BAD_INTR 0x09 /* unexpected interrupt */ +#define DID_PASSTHROUGH 0x0a /* passthrough */ +#define DID_SOFT_ERROR 0x0b /* low driver wants retry */ +#define DID_IMM_RETRY 0x0c /* retry without decreasing retrycnt */ + +/* + * Driver codes + */ +#define DRIVER_OK 0x00 +#define DRIVER_BUSY 0x01 +#define DRIVER_SOFT 0x02 +#define DRIVER_MEDIA 0x03 +#define DRIVER_ERROR 0x04 + +#define DRIVER_INVALID 0x05 +#define DRIVER_TIMEOUT 0x06 +#define DRIVER_HARD 0x07 +#define DRIVER_SENSE 0x08 + +#define SUGGEST_RETRY 0x10 +#define SUGGEST_ABORT 0x20 +#define SUGGEST_REMAP 0x30 +#define SUGGEST_DIE 0x40 +#define SUGGEST_SENSE 0x80 +#define SUGGEST_IS_OK 0xff + +#define DRIVER_MASK 0x0f +#define SUGGEST_MASK 0xf0 + +/* Other definitions */ +/* HZ isn't always available, so simulate it */ +#define SG_DEFAULT_HZ 1000 +#define SG_DEFAULT_TIMEOUT (60*SG_DEFAULT_HZ) + +#endif /* !_SCSI_SG_H */ diff --git a/sys/conf/files b/sys/conf/files index 386fe45..8205413 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -58,6 +58,7 @@ bus/cam/scsi/scsi_low_pisa.c optional nsp bus/cam/scsi/scsi_low_pisa.c optional stg bus/cam/scsi/scsi_pass.c optional pass bus/cam/scsi/scsi_ses.c optional ses +bus/cam/scsi/scsi_sg.c optional sg bus/cam/scsi/scsi_target.c optional targ bus/cam/scsi/scsi_targ_bh.c optional targbh diff --git a/sys/config/GENERIC b/sys/config/GENERIC index 07aaa25..ed5a573 100644 --- a/sys/config/GENERIC +++ b/sys/config/GENERIC @@ -144,6 +144,7 @@ device da # Direct Access (disks) device sa # Sequential Access (tape etc) device cd # CD device pass # Passthrough device (direct SCSI access) +device sg # Passthrough device (linux scsi generic) # RAID controllers interfaced to the SCSI subsystem device asr # DPT SmartRAID V, VI and Adaptec SCSI RAID diff --git a/sys/emulation/linux/linux_ioctl.h b/sys/emulation/linux/linux_ioctl.h index f010c6b..5e2cf4c 100644 --- a/sys/emulation/linux/linux_ioctl.h +++ b/sys/emulation/linux/linux_ioctl.h @@ -61,41 +61,125 @@ #define LINUX_IOCTL_DISK_MAX LINUX_BLKSSZGET /* + * hdio + */ +#define LINUX_HDIO_GET_GEO 0x0301 +#define LINUX_HDIO_GET_IDENTITY 0x030D /* not yet implemented */ +#define LINUX_HDIO_GET_GEO_BIG 0x0330 + +#define LINUX_IOCTL_HDIO_MIN LINUX_HDIO_GET_GEO +#define LINUX_IOCTL_HDIO_MAX LINUX_HDIO_GET_GEO_BIG + +/* * cdrom */ -#define LINUX_CDROMPAUSE 0x5301 -#define LINUX_CDROMRESUME 0x5302 -#define LINUX_CDROMPLAYMSF 0x5303 -#define LINUX_CDROMPLAYTRKIND 0x5304 -#define LINUX_CDROMREADTOCHDR 0x5305 -#define LINUX_CDROMREADTOCENTRY 0x5306 -#define LINUX_CDROMSTOP 0x5307 -#define LINUX_CDROMSTART 0x5308 -#define LINUX_CDROMEJECT 0x5309 -#define LINUX_CDROMVOLCTRL 0x530a -#define LINUX_CDROMSUBCHNL 0x530b -#define LINUX_CDROMREADMODE2 0x530c -#define LINUX_CDROMREADMODE1 0x530d -#define LINUX_CDROMREADAUDIO 0x530e -#define LINUX_CDROMEJECT_SW 0x530f -#define LINUX_CDROMMULTISESSION 0x5310 -#define LINUX_CDROM_GET_UPC 0x5311 -#define LINUX_CDROMRESET 0x5312 -#define LINUX_CDROMVOLREAD 0x5313 -#define LINUX_CDROMREADRAW 0x5314 -#define LINUX_CDROMREADCOOKED 0x5315 -#define LINUX_CDROMSEEK 0x5316 -#define LINUX_CDROMPLAYBLK 0x5317 -#define LINUX_CDROMREADALL 0x5318 -#define LINUX_CDROMCLOSETRAY 0x5319 -#define LINUX_CDROMLOADFROMSLOT 0x531a +#define LINUX_CDROMPAUSE 0x5301 +#define LINUX_CDROMRESUME 0x5302 +#define LINUX_CDROMPLAYMSF 0x5303 +#define LINUX_CDROMPLAYTRKIND 0x5304 +#define LINUX_CDROMREADTOCHDR 0x5305 +#define LINUX_CDROMREADTOCENTRY 0x5306 +#define LINUX_CDROMSTOP 0x5307 +#define LINUX_CDROMSTART 0x5308 +#define LINUX_CDROMEJECT 0x5309 +#define LINUX_CDROMVOLCTRL 0x530a +#define LINUX_CDROMSUBCHNL 0x530b +#define LINUX_CDROMREADMODE2 0x530c +#define LINUX_CDROMREADMODE1 0x530d +#define LINUX_CDROMREADAUDIO 0x530e +#define LINUX_CDROMEJECT_SW 0x530f +#define LINUX_CDROMMULTISESSION 0x5310 +#define LINUX_CDROM_GET_UPC 0x5311 +#define LINUX_CDROMRESET 0x5312 +#define LINUX_CDROMVOLREAD 0x5313 +#define LINUX_CDROMREADRAW 0x5314 +#define LINUX_CDROMREADCOOKED 0x5315 +#define LINUX_CDROMSEEK 0x5316 +#define LINUX_CDROMPLAYBLK 0x5317 +#define LINUX_CDROMREADALL 0x5318 +#define LINUX_CDROMCLOSETRAY 0x5319 +#define LINUX_CDROMLOADFROMSLOT 0x531a +#define LINUX_CDROMGETSPINDOWN 0x531d +#define LINUX_CDROMSETSPINDOWN 0x531e +#define LINUX_CDROM_SET_OPTIONS 0x5320 +#define LINUX_CDROM_CLEAR_OPTIONS 0x5321 +#define LINUX_CDROM_SELECT_SPEED 0x5322 +#define LINUX_CDROM_SELECT_DISC 0x5323 +#define LINUX_CDROM_MEDIA_CHANGED 0x5325 +#define LINUX_CDROM_DRIVE_STATUS 0x5326 +#define LINUX_CDROM_DISC_STATUS 0x5327 +#define LINUX_CDROM_CHANGER_NSLOTS 0x5328 +#define LINUX_CDROM_LOCKDOOR 0x5329 +#define LINUX_CDROM_DEBUG 0x5330 +#define LINUX_CDROM_GET_CAPABILITY 0x5331 +#define LINUX_CDROMAUDIOBUFSIZ 0x5382 +#define LINUX_SCSI_GET_IDLUN 0x5382 +#define LINUX_SCSI_GET_BUS_NUMBER 0x5386 +#define LINUX_DVD_READ_STRUCT 0x5390 +#define LINUX_DVD_WRITE_STRUCT 0x5391 +#define LINUX_DVD_AUTH 0x5392 +#define LINUX_CDROM_SEND_PACKET 0x5393 +#define LINUX_CDROM_NEXT_WRITABLE 0x5394 +#define LINUX_CDROM_LAST_WRITTEN 0x5395 #define LINUX_IOCTL_CDROM_MIN LINUX_CDROMPAUSE -#define LINUX_IOCTL_CDROM_MAX LINUX_CDROMLOADFROMSLOT +#define LINUX_IOCTL_CDROM_MAX LINUX_CDROM_LAST_WRITTEN #define LINUX_CDROM_LBA 0x01 #define LINUX_CDROM_MSF 0x02 +#define LINUX_DVD_LU_SEND_AGID 0 +#define LINUX_DVD_HOST_SEND_CHALLENGE 1 +#define LINUX_DVD_LU_SEND_KEY1 2 +#define LINUX_DVD_LU_SEND_CHALLENGE 3 +#define LINUX_DVD_HOST_SEND_KEY2 4 +#define LINUX_DVD_AUTH_ESTABLISHED 5 +#define LINUX_DVD_AUTH_FAILURE 6 +#define LINUX_DVD_LU_SEND_TITLE_KEY 7 +#define LINUX_DVD_LU_SEND_ASF 8 +#define LINUX_DVD_INVALIDATE_AGID 9 +#define LINUX_DVD_LU_SEND_RPC_STATE 10 +#define LINUX_DVD_HOST_SEND_RPC_STATE 11 + +/* + * SG + */ +#define LINUX_SG_SET_TIMEOUT 0x2201 +#define LINUX_SG_GET_TIMEOUT 0x2202 +#define LINUX_SG_EMULATED_HOST 0x2203 +#define LINUX_SG_SET_TRANSFORM 0x2204 +#define LINUX_SG_GET_TRANSFORM 0x2205 +#define LINUX_SG_GET_COMMAND_Q 0x2270 +#define LINUX_SG_SET_COMMAND_Q 0x2271 +#define LINUX_SG_SET_RESERVED_SIZE 0x2275 +#define LINUX_SG_GET_RESERVED_SIZE 0x2272 +#define LINUX_SG_GET_SCSI_ID 0x2276 +#define LINUX_SG_SET_FORCE_LOW_DMA 0x2279 +#define LINUX_SG_GET_LOW_DMA 0x227a +#define LINUX_SG_SET_FORCE_PACK_ID 0x227b +#define LINUX_SG_GET_PACK_ID 0x227c +#define LINUX_SG_GET_NUM_WAITING 0x227d +#define LINUX_SG_SET_DEBUG 0x227e +#define LINUX_SG_GET_SG_TABLESIZE 0x227f +#define LINUX_SG_GET_VERSION_NUM 0x2282 +#define LINUX_SG_NEXT_CMD_LEN 0x2283 +#define LINUX_SG_SCSI_RESET 0x2284 +#define LINUX_SG_IO 0x2285 +#define LINUX_SG_GET_REQUEST_TABLE 0x2286 +#define LINUX_SG_SET_KEEP_ORPHAN 0x2287 +#define LINUX_SG_GET_KEEP_ORPHAN 0x2288 +#define LINUX_SG_GET_ACCESS_COUNT 0x2289 + +#define LINUX_IOCTL_SG_MIN 0x2200 +#define LINUX_IOCTL_SG_MAX 0x22ff + +/* + * VFAT + */ +#define LINUX_VFAT_READDIR_BOTH 0x7201 + +#define LINUX_IOCTL_VFAT_MIN LINUX_VFAT_READDIR_BOTH +#define LINUX_IOCTL_VFAT_MAX LINUX_VFAT_READDIR_BOTH /* * console */ -- 1.7.7.2