diff options
-rw-r--r-- | sys/cam/scsi/scsi_sg.c | 987 | ||||
-rw-r--r-- | sys/cam/scsi/scsi_sg.h | 139 | ||||
-rw-r--r-- | sys/compat/linux/linux_ioctl.c | 27 | ||||
-rw-r--r-- | sys/compat/linux/linux_ioctl.h | 34 | ||||
-rw-r--r-- | sys/conf/NOTES | 5 | ||||
-rw-r--r-- | sys/conf/files | 1 | ||||
-rw-r--r-- | sys/modules/cam/Makefile | 1 |
7 files changed, 1194 insertions, 0 deletions
diff --git a/sys/cam/scsi/scsi_sg.c b/sys/cam/scsi/scsi_sg.c new file mode 100644 index 0000000..1601139 --- /dev/null +++ b/sys/cam/scsi/scsi_sg.c @@ -0,0 +1,987 @@ +/*- + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/types.h> +#include <sys/bio.h> +#include <sys/malloc.h> +#include <sys/fcntl.h> +#include <sys/ioccom.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/devicestat.h> +#include <sys/proc.h> +#include <sys/uio.h> + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_periph.h> +#include <cam/cam_queue.h> +#include <cam/cam_xpt_periph.h> +#include <cam/cam_debug.h> +#include <cam/cam_sim.h> + +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> +#include <cam/scsi/scsi_sg.h> + +#include <compat/linux/linux_ioctl.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_INPROG, + SG_RDWR_WAITING, + 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; + uint8_t pd_type; + struct devstat *device_stats; + TAILQ_HEAD(, sg_rdwr) rdwr_done; + struct cdev *dev; + struct cdev *devalias; + 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 cdevsw sg_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = sgopen, + .d_close = sgclose, + .d_ioctl = sgioctl, + .d_write = sgwrite, + .d_read = sgread, + .d_name = "sg", +}; + +static int sg_version = 30125; + +static void +sginit(void) +{ + cam_status status; + struct cam_path *path; + + /* + * Install a global async callback. This callback will receive aync + * callbacks like "new device found". + */ + status = xpt_create_path(&path, /*periph*/NULL, CAM_XPT_PATH_ID, + CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); + + if (status == CAM_REQ_CMP) { + struct ccb_setasync csa; + + xpt_setup_ccb(&csa.ccb_h, path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_FOUND_DEVICE; + csa.callback = sgasync; + csa.callback_arg = NULL; + xpt_action((union ccb *)&csa); + status = csa.ccb_h.status; + xpt_free_path(path); + } + + if (status != CAM_REQ_CMP) { + printf("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; + struct ccb_setasync csa; + + softc = (struct sg_softc *)periph->softc; + + /* + * Deregister any async callbacks. + */ + xpt_setup_ccb(&csa.ccb_h, periph->path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = 0; + csa.callback = sgasync; + csa.callback_arg = periph; + xpt_action((union ccb *)&csa); + + 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; + devstat_remove_entry(softc->device_stats); + destroy_dev(softc->dev); + destroy_dev(softc->devalias); + if (bootverbose) { + xpt_print(periph->path, "removing device entry\n"); + } + free(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; + + /* + * 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); + printf("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_setasync csa; + struct ccb_getdev *cgd; + int no_tags; + + cgd = (struct ccb_getdev *)arg; + if (periph == NULL) { + printf("sgregister: periph was NULL!!\n"); + return (CAM_REQ_CMP_ERR); + } + + if (cgd == NULL) { + printf("sgregister: no getdev CCB, can't register device\n"); + return (CAM_REQ_CMP_ERR); + } + + softc = (struct sg_softc *)malloc(sizeof(*softc), M_DEVBUF, M_NOWAIT); + if (softc == NULL) { + printf("sgregister: Unable to allocate softc\n"); + return (CAM_REQ_CMP_ERR); + } + + bzero(softc, sizeof(*softc)); + softc->state = SG_STATE_NORMAL; + softc->pd_type = SID_TYPE(&cgd->inq_data); + 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. + */ + no_tags = (cgd->inq_data.flags & SID_CmdQue) == 0; + softc->device_stats = devstat_new_entry("sg", + unit2minor(periph->unit_number), 0, + DEVSTAT_NO_BLOCKSIZE + | (no_tags ? DEVSTAT_NO_ORDERED_TAGS : 0), + softc->pd_type | + DEVSTAT_TYPE_IF_SCSI | + DEVSTAT_TYPE_PASS, + DEVSTAT_PRIORITY_PASS); + + /* Register the device */ + softc->dev = make_dev(&sg_cdevsw, unit2minor(periph->unit_number), + UID_ROOT, GID_OPERATOR, 0600, "%s%d", + periph->periph_name, periph->unit_number); + softc->devalias = make_dev_alias(softc->dev, "sg%c", + 'a' + periph->unit_number); + softc->dev->si_drv1 = periph; + + /* + * Add as async callback so that we get + * notified if this device goes away. + */ + xpt_setup_ccb(&csa.ccb_h, periph->path, /*priority*/5); + csa.ccb_h.func_code = XPT_SASYNC_CB; + csa.event_enable = AC_LOST_DEVICE; + csa.callback = sgasync; + csa.callback_arg = periph; + xpt_action((union ccb *)&csa); + + 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, + NULL, NULL); + + rdwr = done_ccb->ccb_h.ccb_rdwr; + state = rdwr->state; + rdwr->state = SG_RDWR_DONE; + if (state == SG_RDWR_WAITING) + wakeup(rdwr); + break; + } + default: + panic("unknown sg CCB type"); + } +} + +static int +sgopen(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct cam_periph *periph; + struct sg_softc *softc; + int error = 0; + + periph = (struct cam_periph *)dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + softc = (struct sg_softc *)periph->softc; + if (softc->flags & SG_FLAG_INVALID) + return (ENXIO); + + /* + * Don't allow access when we're running at a high securelevel. + */ + error = securelevel_gt(td->td_ucred, 1); + if (error) + return (error); + + if ((error = cam_periph_lock(periph, PRIBIO | PCATCH)) != 0) + return (error); + + if ((softc->flags & SG_FLAG_OPEN) == 0) { + if (cam_periph_acquire(periph) != CAM_REQ_CMP) + return (ENXIO); + softc->flags |= SG_FLAG_OPEN; + } + + cam_periph_unlock(periph); + + return (error); +} + +static int +sgclose(struct cdev *dev, int flag, int fmt, struct thread *td) +{ + struct cam_periph *periph; + struct sg_softc *softc; + int error; + + periph = (struct cam_periph *)dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + softc = (struct sg_softc *)periph->softc; + + if ((error = cam_periph_lock(periph, PRIBIO)) != 0) + return (error); + + softc->flags &= ~SG_FLAG_OPEN; + + cam_periph_unlock(periph); + cam_periph_release(periph); + + return (0); +} + +static int +sgioctl(struct cdev *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 *)dev->si_drv1; + if (periph == NULL) + return (ENXIO); + + softc = (struct sg_softc *)periph->softc; + error = 0; + + switch (cmd) { + case LINUX_SCSI_GET_BUS_NUMBER: { + int busno; + + busno = xpt_path_path_id(periph->path); + error = copyout(&busno, arg, 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, arg, sizeof(idlun)); + break; + } + case SG_GET_VERSION_NUM: + case LINUX_SG_GET_VERSION_NUM: + error = copyout(&sg_version, arg, sizeof(sg_version)); + break; + case SG_SET_TIMEOUT: + case LINUX_SG_SET_TIMEOUT: + break; + case SG_GET_TIMEOUT: + case LINUX_SG_GET_TIMEOUT: + /* + * XXX This ioctl is highly brain damaged because it requires + * that the value be returned in the syscall return value. + * The linuxolator seems to have a hard time with this, + * so just return 0 and hope that apps can cope. + */ + error = 0; + break; + case SG_IO: + case LINUX_SG_IO: + error = copyin(arg, &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, arg, 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, arg, 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, arg, 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 + printf("sgioctl: rejecting cmd 0x%lx\n", cmd); +#endif + error = ENODEV; + break; + } + + return (error); +} + +static int +sgwrite(struct cdev *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; + + periph = dev->si_drv1; + sc = periph->softc; + rdwr = malloc(sizeof(*rdwr), M_DEVBUF, M_WAITOK); + 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(hdr, sizeof(*hdr), uio); + if (error) + goto out_hdr; + + ccb = xpt_alloc_ccb(); + if (ccb == NULL) { + error = ENOMEM; + goto out_hdr; + } + xpt_setup_ccb(&ccb->ccb_h, periph->path, /*priority*/5); + 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 = malloc(buf_len, M_DEVBUF, M_WAITOK); + error = uiomove(buf, buf_len, uio); + if (error) + goto out_buf; + dir = CAM_DIR_OUT; + } else if (hdr->reply_len != 0) { + buf = malloc(hdr->reply_len, M_DEVBUF, M_WAITOK); + buf_len = hdr->reply_len; + dir = CAM_DIR_IN; + } else { + buf = NULL; + buf_len = 0; + dir = CAM_DIR_NONE; + } + + cam_fill_csio(csio, + /*retries*/1, + sgdone, + dir|CAM_DEV_QFRZDIS, + MSG_SIMPLE_Q_TAG, + buf, + buf_len, + SG_MAX_SENSE, + cdb_len, + 60*hz); + + /* + * 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); + return (sgsendrdwr(periph, ccb)); + +out_buf: + free(buf, M_DEVBUF); +out_ccb: + xpt_free_ccb(ccb); +out_hdr: + free(rdwr, M_DEVBUF); + return (error); +} + +static int +sgread(struct cdev *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; + + periph = dev->si_drv1; + sc = periph->softc; + + /* 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(&pack_len, 4, uio)) != 0) + return (error); + if ((error = uiomove(&reply_len, 4, uio)) != 0) + return (error); + if ((error = uiomove(&pack_id, 4, uio)) != 0) + return (error); + uio->uio_rw = UIO_READ; + +search: + TAILQ_FOREACH(rdwr, &sc->rdwr_done, rdwr_link) { + if (rdwr->tag == pack_id) + break; + } + if ((rdwr == NULL) || (rdwr->state != SG_RDWR_DONE)) { + rdwr->state = SG_RDWR_WAITING; + if (tsleep(rdwr, PCATCH, "sgread", 0) == ERESTART) + return (EAGAIN); + goto search; + } + TAILQ_REMOVE(&sc->rdwr_done, rdwr, rdwr_link); + + 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(&hdr->result, sizeof(*hdr) - + offsetof(struct sg_header, result), uio); + if ((error == 0) && (hdr->result == 0)) + error = uiomove(rdwr->buf, rdwr->buf_len, uio); + + xpt_free_ccb(rdwr->ccb); + free(rdwr->buf, M_DEVBUF); + free(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)); + error = cam_periph_mapmem(ccb, &mapinfo); + 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, NULL); + 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; + 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; + 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; + 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/cam/scsi/scsi_sg.h b/sys/cam/scsi/scsi_sg.h new file mode 100644 index 0000000..a9f7d03 --- /dev/null +++ b/sys/cam/scsi/scsi_sg.h @@ -0,0 +1,139 @@ +/* + * Structures and definitions for SCSI commands to the SG passthrough device. + * + * $FreeBSD$ + */ + +#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 + +#endif /* !_SCSI_SG_H */ diff --git a/sys/compat/linux/linux_ioctl.c b/sys/compat/linux/linux_ioctl.c index 09e1cc0..9583551 100644 --- a/sys/compat/linux/linux_ioctl.c +++ b/sys/compat/linux/linux_ioctl.c @@ -86,6 +86,7 @@ static linux_ioctl_function_t linux_ioctl_sound; static linux_ioctl_function_t linux_ioctl_termio; static linux_ioctl_function_t linux_ioctl_private; static linux_ioctl_function_t linux_ioctl_drm; +static linux_ioctl_function_t linux_ioctl_sg; static linux_ioctl_function_t linux_ioctl_special; static struct linux_ioctl_handler cdrom_handler = @@ -108,6 +109,8 @@ static struct linux_ioctl_handler private_handler = { linux_ioctl_private, LINUX_IOCTL_PRIVATE_MIN, LINUX_IOCTL_PRIVATE_MAX }; static struct linux_ioctl_handler drm_handler = { linux_ioctl_drm, LINUX_IOCTL_DRM_MIN, LINUX_IOCTL_DRM_MAX }; +static struct linux_ioctl_handler sg_handler = +{ linux_ioctl_sg, LINUX_IOCTL_SG_MIN, LINUX_IOCTL_SG_MAX }; DATA_SET(linux_ioctl_handler_set, cdrom_handler); DATA_SET(linux_ioctl_handler_set, vfat_handler); @@ -119,6 +122,7 @@ DATA_SET(linux_ioctl_handler_set, sound_handler); DATA_SET(linux_ioctl_handler_set, termio_handler); DATA_SET(linux_ioctl_handler_set, private_handler); DATA_SET(linux_ioctl_handler_set, drm_handler); +DATA_SET(linux_ioctl_handler_set, sg_handler); struct handler_element { @@ -1583,6 +1587,11 @@ linux_ioctl_cdrom(struct thread *td, struct linux_ioctl_args *args) break; } + case LINUX_SCSI_GET_BUS_NUMBER: + case LINUX_SCSI_GET_IDLUN: + error = linux_ioctl_sg(td, args); + break; + /* LINUX_CDROM_SEND_PACKET */ /* LINUX_CDROM_NEXT_WRITABLE */ /* LINUX_CDROM_LAST_WRITTEN */ @@ -2522,6 +2531,24 @@ linux_ioctl_drm(struct thread *td, struct linux_ioctl_args *args) return ioctl(td, (struct ioctl_args *)args); } +static int +linux_ioctl_sg(struct thread *td, struct linux_ioctl_args *args) +{ + struct file *fp; + u_long cmd; + int error; + + if ((error = fget(td, args->fd, &fp)) != 0) { + printf("sg_linux_ioctl: fget returned %d\n", error); + return (error); + } + cmd = args->cmd; + + error = (fo_ioctl(fp, cmd, (caddr_t)args->arg, td->td_ucred, td)); + fdrop(fp, td); + return (error); +} + /* * Special ioctl handler */ diff --git a/sys/compat/linux/linux_ioctl.h b/sys/compat/linux/linux_ioctl.h index 17e5b164..4fc69b2 100644 --- a/sys/compat/linux/linux_ioctl.h +++ b/sys/compat/linux/linux_ioctl.h @@ -103,6 +103,8 @@ #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 @@ -130,6 +132,38 @@ #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 diff --git a/sys/conf/NOTES b/sys/conf/NOTES index 43602d5..37cdf46 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -1215,6 +1215,10 @@ hint.sa.1.target="6" # # The pt driver drives SCSI Processor devices. # +# The sg driver provides a passthrough API that is compatible with the +# Linux SG driver. It will work in conjunction with the COMPAT_LINUX +# option to run linux SG apps. It can also stand on its own and provide +# source level API compatiblity for porting apps to FreeBSD. # # Target Mode support is provided here but also requires that a SIM # (SCSI Host Adapter Driver) provide support as well. @@ -1241,6 +1245,7 @@ device pt #SCSI processor device targ #SCSI Target Mode Code device targbh #SCSI Target Mode Blackhole Device device pass #CAM passthrough driver +device sg #Linux SCSI passthrough # CAM OPTIONS: # debugging options: diff --git a/sys/conf/files b/sys/conf/files index 4f18935..dae96ba 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -100,6 +100,7 @@ cam/scsi/scsi_pass.c optional pass cam/scsi/scsi_pt.c optional pt cam/scsi/scsi_sa.c optional sa cam/scsi/scsi_ses.c optional ses +cam/scsi/scsi_sg.c optional sg cam/scsi/scsi_targ_bh.c optional targbh cam/scsi/scsi_target.c optional targ coda/coda_fbsd.c optional vcoda diff --git a/sys/modules/cam/Makefile b/sys/modules/cam/Makefile index af908c7..6f88b35 100644 --- a/sys/modules/cam/Makefile +++ b/sys/modules/cam/Makefile @@ -22,6 +22,7 @@ SRCS+= scsi_pass.c SRCS+= scsi_pt.c SRCS+= scsi_sa.c SRCS+= scsi_ses.c +SRCS+= scsi_sg.c SRCS+= scsi_targ_bh.c scsi_target.c EXPORT_SYMS= YES # XXX evaluate |