summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/cam/scsi/scsi_sg.c987
-rw-r--r--sys/cam/scsi/scsi_sg.h139
-rw-r--r--sys/compat/linux/linux_ioctl.c27
-rw-r--r--sys/compat/linux/linux_ioctl.h34
-rw-r--r--sys/conf/NOTES5
-rw-r--r--sys/conf/files1
-rw-r--r--sys/modules/cam/Makefile1
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
OpenPOWER on IntegriCloud