From e0dd152a3bdf42702910b6fc21950072e963e8e4 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Fri, 20 Dec 2013 21:50:50 +0000 Subject: Add vioscsi(4) driver. Still a work-in-progress, but functional enough to work with both Google Compute Engine and RHEVM. Committing so additional testing and cleanup can be handled in tree. thx Jiri B for RHEVM testing ok dlg, deraadt --- sys/dev/pci/files.pci | 6 +- sys/dev/pci/vioscsi.c | 439 +++++++++++++++++++++++++++++++++++++++++++++++ sys/dev/pci/vioscsireg.h | 72 ++++++++ 3 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 sys/dev/pci/vioscsi.c create mode 100644 sys/dev/pci/vioscsireg.h (limited to 'sys/dev') diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index 2a1847ca64a..c62ba3b898f 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.299 2013/11/04 14:07:14 deraadt Exp $ +# $OpenBSD: files.pci,v 1.300 2013/12/20 21:50:49 matthew Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -841,5 +841,9 @@ device viomb attach viomb at virtio file dev/pci/viomb.c viomb +device vioscsi: scsi +attach vioscsi at virtio +file dev/pci/vioscsi.c vioscsi + include "dev/pci/files.agp" include "dev/pci/drm/files.drm" diff --git a/sys/dev/pci/vioscsi.c b/sys/dev/pci/vioscsi.c new file mode 100644 index 00000000000..614e6133636 --- /dev/null +++ b/sys/dev/pci/vioscsi.c @@ -0,0 +1,439 @@ +/* $OpenBSD: vioscsi.c,v 1.1 2013/12/20 21:50:49 matthew Exp $ */ +/* + * Copyright (c) 2013 Google Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +enum { vioscsi_debug = 0 }; +#define DPRINTF(f...) do { if (vioscsi_debug) printf(f); } while (0) + +struct vioscsi_req { + struct virtio_scsi_req_hdr vr_req; + struct virtio_scsi_res_hdr vr_res; + struct scsi_xfer *vr_xs; + bus_dmamap_t vr_control; + bus_dmamap_t vr_data; +}; + +struct vioscsi_softc { + struct device sc_dev; + struct scsi_link sc_link; + struct scsibus *sc_scsibus; + struct scsi_iopool sc_iopool; + + struct virtqueue sc_vqs[3]; + struct vioscsi_req *sc_reqs; + bus_dma_segment_t sc_reqs_segs[1]; + + u_int32_t sc_seg_max; +}; + +int vioscsi_match(struct device *, void *, void *); +void vioscsi_attach(struct device *, struct device *, void *); + +int vioscsi_alloc_reqs(struct vioscsi_softc *, + struct virtio_softc *, int, uint32_t); +void vioscsi_scsi_cmd(struct scsi_xfer *); +int vioscsi_vq_done(struct virtqueue *); +void vioscsi_req_done(struct vioscsi_softc *, struct virtio_softc *, + struct vioscsi_req *); +void *vioscsi_req_get(void *); +void vioscsi_req_put(void *, void *); + +struct cfattach vioscsi_ca = { + sizeof(struct vioscsi_softc), + vioscsi_match, + vioscsi_attach, +}; + +struct cfdriver vioscsi_cd = { + NULL, + "vioscsi", + DV_DULL, +}; + +struct scsi_adapter vioscsi_switch = { + vioscsi_scsi_cmd, + scsi_minphys, +}; + +const char *const vioscsi_vq_names[] = { + "control", + "event", + "request", +}; + +int +vioscsi_match(struct device *parent, void *self, void *aux) +{ + struct virtio_softc *va = (struct virtio_softc *)aux; + + if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_SCSI) + return (1); + return (0); +} + +void +vioscsi_attach(struct device *parent, struct device *self, void *aux) +{ + struct virtio_softc *vsc = (struct virtio_softc *)parent; + struct vioscsi_softc *sc = (struct vioscsi_softc *)self; + struct scsibus_attach_args saa; + int i, rv; + + if (vsc->sc_child != NULL) { + printf(": parent already has a child\n"); + return; + } + vsc->sc_child = &sc->sc_dev; + vsc->sc_ipl = IPL_BIO; + vsc->sc_intrhand = virtio_vq_intr; + + // TODO(matthew): Negotiate hotplug. + + vsc->sc_vqs = sc->sc_vqs; + vsc->sc_nvqs = nitems(sc->sc_vqs); + + uint32_t cmd_per_lun = virtio_read_device_config_4(vsc, + VIRTIO_SCSI_CONFIG_CMD_PER_LUN); + uint32_t seg_max = virtio_read_device_config_4(vsc, + VIRTIO_SCSI_CONFIG_SEG_MAX); + uint16_t max_target = virtio_read_device_config_2(vsc, + VIRTIO_SCSI_CONFIG_MAX_TARGET); + + sc->sc_seg_max = seg_max; + + for (i = 0; i < nitems(sc->sc_vqs); i++) { + rv = virtio_alloc_vq(vsc, &sc->sc_vqs[i], i, MAXPHYS, + 1 + howmany(MAXPHYS, NBPG), vioscsi_vq_names[i]); + if (rv) { + printf(": failed to allocate virtqueue %d\n", i); + return; + } + sc->sc_vqs[i].vq_done = vioscsi_vq_done; + } + + int qsize = sc->sc_vqs[2].vq_num; + printf(": qsize %d\n", qsize); + if (vioscsi_alloc_reqs(sc, vsc, qsize, seg_max)) + return; + + scsi_iopool_init(&sc->sc_iopool, sc, vioscsi_req_get, vioscsi_req_put); + + sc->sc_link.adapter = &vioscsi_switch; + sc->sc_link.adapter_softc = sc; + sc->sc_link.adapter_target = max_target; + sc->sc_link.adapter_buswidth = max_target; + sc->sc_link.openings = cmd_per_lun; + sc->sc_link.pool = &sc->sc_iopool; + + bzero(&saa, sizeof(saa)); + saa.saa_sc_link = &sc->sc_link; + + sc->sc_scsibus = (struct scsibus *)config_found(self, &saa, scsiprint); +} + +void +vioscsi_scsi_cmd(struct scsi_xfer *xs) +{ + struct vioscsi_softc *sc = xs->sc_link->adapter_softc; + struct virtio_softc *vsc = (struct virtio_softc *)sc->sc_dev.dv_parent; + struct vioscsi_req *vr = xs->io; + struct virtio_scsi_req_hdr *req = &vr->vr_req; + struct virtqueue *vq = &sc->sc_vqs[2]; + int slot = vr - sc->sc_reqs; + + DPRINTF("vioscsi_scsi_cmd: enter\n"); + + // TODO(matthew): Support bidirectional SCSI commands? + if ((xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) + == (SCSI_DATA_IN | SCSI_DATA_OUT)) { + stuffup: + xs->error = XS_DRIVER_STUFFUP; + xs->resid = xs->datalen; + DPRINTF("vioscsi_scsi_cmd: stuffup\n"); + scsi_done(xs); + return; + } + + vr->vr_xs = xs; + + /* + * "The only supported format for the LUN field is: first byte set to + * 1, second byte set to target, third and fourth byte representing a + * single level LUN structure, followed by four zero bytes." + */ + if (xs->sc_link->target >= 256 || xs->sc_link->lun >= 16384) + goto stuffup; + req->lun[0] = 1; + req->lun[1] = xs->sc_link->target; + req->lun[2] = 0x40 | (xs->sc_link->lun >> 8); + req->lun[3] = xs->sc_link->lun; + memset(req->lun + 4, 0, 4); + + if ((size_t)xs->cmdlen > sizeof(req->cdb)) + goto stuffup; + memset(req->cdb, 0, sizeof(req->cdb)); + memcpy(req->cdb, xs->cmd, xs->cmdlen); + + int isread = !!(xs->flags & SCSI_DATA_IN); + + int nsegs = 2; + if (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) { + if (bus_dmamap_load(vsc->sc_dmat, vr->vr_data, + xs->data, xs->datalen, NULL, + ((isread ? BUS_DMA_READ : BUS_DMA_WRITE) | + BUS_DMA_NOWAIT))) + goto stuffup; + nsegs += vr->vr_data->dm_nsegs; + } + + int s = splbio(); + int r = virtio_enqueue_reserve(vq, slot, nsegs); + splx(s); + if (r) + goto stuffup; + + bus_dmamap_sync(vsc->sc_dmat, vr->vr_control, + offsetof(struct vioscsi_req, vr_req), + sizeof(struct virtio_scsi_req_hdr), + BUS_DMASYNC_PREWRITE); + bus_dmamap_sync(vsc->sc_dmat, vr->vr_control, + offsetof(struct vioscsi_req, vr_res), + sizeof(struct virtio_scsi_res_hdr), + BUS_DMASYNC_PREREAD); + if (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) + bus_dmamap_sync(vsc->sc_dmat, vr->vr_data, 0, xs->datalen, + isread ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); + + s = splbio(); + virtio_enqueue_p(vq, slot, vr->vr_control, + offsetof(struct vioscsi_req, vr_req), + sizeof(struct virtio_scsi_req_hdr), + 1); + if (xs->flags & SCSI_DATA_OUT) + virtio_enqueue(vq, slot, vr->vr_data, 1); + virtio_enqueue_p(vq, slot, vr->vr_control, + offsetof(struct vioscsi_req, vr_res), + sizeof(struct virtio_scsi_res_hdr), + 0); + if (xs->flags & SCSI_DATA_IN) + virtio_enqueue(vq, slot, vr->vr_data, 0); + + virtio_enqueue_commit(vsc, vq, slot, 1); + + if (ISSET(xs->flags, SCSI_POLL)) { + DPRINTF("vioscsi_scsi_cmd: polling...\n"); + int timeout = 1000; + do { + vsc->sc_ops->intr(vsc); + if (vr->vr_xs != xs) + break; + delay(1000); + } while (--timeout > 0); + if (vr->vr_xs == xs) { + // TODO(matthew): Abort the request. + xs->error = XS_TIMEOUT; + xs->resid = xs->datalen; + DPRINTF("vioscsi_scsi_cmd: polling timeout\n"); + scsi_done(xs); + } + DPRINTF("vioscsi_scsi_cmd: done (timeout=%d)\n", timeout); + } + splx(s); +} + +void +vioscsi_req_done(struct vioscsi_softc *sc, struct virtio_softc *vsc, + struct vioscsi_req *vr) +{ + struct scsi_xfer *xs = vr->vr_xs; + + DPRINTF("vioscsi_req_done: enter\n"); + + int isread = !!(xs->flags & SCSI_DATA_IN); + bus_dmamap_sync(vsc->sc_dmat, vr->vr_control, + offsetof(struct vioscsi_req, vr_req), + sizeof(struct virtio_scsi_req_hdr), + BUS_DMASYNC_POSTWRITE); + bus_dmamap_sync(vsc->sc_dmat, vr->vr_control, + offsetof(struct vioscsi_req, vr_res), + sizeof(struct virtio_scsi_res_hdr), + BUS_DMASYNC_POSTREAD); + if (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) + bus_dmamap_sync(vsc->sc_dmat, vr->vr_data, 0, xs->datalen, + isread ? BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); + + if (vr->vr_res.response != VIRTIO_SCSI_S_OK) { + xs->error = XS_DRIVER_STUFFUP; + xs->resid = xs->datalen; + DPRINTF("vioscsi_req_done: stuffup: %d\n", vr->vr_res.response); + goto done; + } + + size_t sense_len = MIN(sizeof(xs->sense), vr->vr_res.sense_len); + memcpy(&xs->sense, vr->vr_res.sense, sense_len); + xs->error = (sense_len == 0) ? XS_NOERROR : XS_SENSE; + + xs->status = vr->vr_res.status; + xs->resid = vr->vr_res.residual; + + DPRINTF("vioscsi_req_done: done %d, %d, %d\n", + xs->error, xs->status, xs->resid); + +done: + vr->vr_xs = NULL; + scsi_done(xs); +} + +int +vioscsi_vq_done(struct virtqueue *vq) +{ + struct virtio_softc *vsc = vq->vq_owner; + struct vioscsi_softc *sc = (struct vioscsi_softc *)vsc->sc_child; + int ret = 0; + + DPRINTF("vioscsi_vq_done: enter\n"); + + for (;;) { + int r, s, slot; + s = splbio(); + r = virtio_dequeue(vsc, vq, &slot, NULL); + splx(s); + if (r != 0) + break; + + DPRINTF("vioscsi_vq_done: slot=%d\n", slot); + vioscsi_req_done(sc, vsc, &sc->sc_reqs[slot]); + ret = 1; + } + + DPRINTF("vioscsi_vq_done: exit %d\n", ret); + + return (ret); +} + +void * +vioscsi_req_get(void *cookie) +{ + struct vioscsi_softc *sc = cookie; + struct virtio_softc *vsc = (struct virtio_softc *)sc->sc_dev.dv_parent; + struct virtqueue *vq = &sc->sc_vqs[2]; + struct vioscsi_req *vr = NULL; + int r, s, slot; + + s = splbio(); + if (virtio_enqueue_prep(vq, &slot) == 0) + vr = &sc->sc_reqs[slot]; + splx(s); + + if (vr == NULL) + goto err1; + + vr->vr_req.id = slot; + vr->vr_req.task_attr = VIRTIO_SCSI_S_SIMPLE; + + r = bus_dmamap_create(vsc->sc_dmat, + offsetof(struct vioscsi_req, vr_xs), 1, + offsetof(struct vioscsi_req, vr_xs), 0, + BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &vr->vr_control); + if (r != 0) + goto err2; + r = bus_dmamap_create(vsc->sc_dmat, MAXPHYS, sc->sc_seg_max, + MAXPHYS, 0, BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &vr->vr_data); + if (r != 0) + goto err3; + r = bus_dmamap_load(vsc->sc_dmat, vr->vr_control, + vr, offsetof(struct vioscsi_req, vr_xs), NULL, + BUS_DMA_NOWAIT); + if (r != 0) + goto err4; + + DPRINTF("vioscsi_req_get: %p, %d\n", vr, slot); + + return (vr); + +err4: + bus_dmamap_destroy(vsc->sc_dmat, vr->vr_data); +err3: + bus_dmamap_destroy(vsc->sc_dmat, vr->vr_control); +err2: + s = splbio(); + virtio_enqueue_abort(vq, slot); + splx(s); +err1: + return (NULL); +} + +void +vioscsi_req_put(void *cookie, void *io) +{ + struct vioscsi_softc *sc = cookie; + struct virtio_softc *vsc = (struct virtio_softc *)sc->sc_dev.dv_parent; + struct virtqueue *vq = &sc->sc_vqs[2]; + struct vioscsi_req *vr = io; + int slot = vr - sc->sc_reqs; + + DPRINTF("vioscsi_req_put: %p, %d\n", vr, slot); + + bus_dmamap_destroy(vsc->sc_dmat, vr->vr_control); + bus_dmamap_destroy(vsc->sc_dmat, vr->vr_data); + + int s = splbio(); + virtio_dequeue_commit(vq, slot); + splx(s); +} + +int +vioscsi_alloc_reqs(struct vioscsi_softc *sc, struct virtio_softc *vsc, + int qsize, uint32_t seg_max) +{ + size_t allocsize; + int r, rsegs; + void *vaddr; + + allocsize = qsize * sizeof(struct vioscsi_req); + r = bus_dmamem_alloc(vsc->sc_dmat, allocsize, 0, 0, + &sc->sc_reqs_segs[0], 1, &rsegs, BUS_DMA_NOWAIT); + if (r != 0) { + printf("bus_dmamem_alloc, size %d, error %d\n", + allocsize, r); + return 1; + } + r = bus_dmamem_map(vsc->sc_dmat, &sc->sc_reqs_segs[0], 1, + allocsize, (caddr_t *)&vaddr, BUS_DMA_NOWAIT); + if (r != 0) { + printf("bus_dmamem_map failed, error %d\n", r); + bus_dmamem_free(vsc->sc_dmat, &sc->sc_reqs_segs[0], 1); + return 1; + } + sc->sc_reqs = vaddr; + memset(vaddr, 0, allocsize); + return 0; +} diff --git a/sys/dev/pci/vioscsireg.h b/sys/dev/pci/vioscsireg.h new file mode 100644 index 00000000000..f295b6bb6bd --- /dev/null +++ b/sys/dev/pci/vioscsireg.h @@ -0,0 +1,72 @@ +/* $OpenBSD: vioscsireg.h,v 1.1 2013/12/20 21:50:49 matthew Exp $ */ +/* + * Copyright (c) 2013 Google Inc. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Configuration registers */ +#define VIRTIO_SCSI_CONFIG_NUM_QUEUES 0 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_SEG_MAX 4 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_MAX_SECTORS 8 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_CMD_PER_LUN 12 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_EVENT_INFO_SIZE 16 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_SENSE_SIZE 20 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_CDB_SIZE 24 /* 32bit */ +#define VIRTIO_SCSI_CONFIG_MAX_CHANNEL 28 /* 16bit */ +#define VIRTIO_SCSI_CONFIG_MAX_TARGET 30 /* 16bit */ +#define VIRTIO_SCSI_CONFIG_MAX_LUN 32 /* 32bit */ + +/* Feature bits */ +#define VIRTIO_SCSI_F_INOUT (1<<0) +#define VIRTIO_SCSI_F_HOTPLUG (1<<1) + +/* Response status values */ +#define VIRTIO_SCSI_S_OK 0 +#define VIRTIO_SCSI_S_OVERRUN 1 +#define VIRTIO_SCSI_S_ABORTED 2 +#define VIRTIO_SCSI_S_BAD_TARGET 3 +#define VIRTIO_SCSI_S_RESET 4 +#define VIRTIO_SCSI_S_BUSY 5 +#define VIRTIO_SCSI_S_TRANSPORT_FAILURE 6 +#define VIRTIO_SCSI_S_TARGET_FAILURE 7 +#define VIRTIO_SCSI_S_NEXUS_FAILURE 8 +#define VIRTIO_SCSI_S_FAILURE 9 + +/* Task attributes */ +#define VIRTIO_SCSI_S_SIMPLE 0 +#define VIRTIO_SCSI_S_ORDERED 1 +#define VIRTIO_SCSI_S_HEAD 2 +#define VIRTIO_SCSI_S_ACA 3 + +/* Request header structure */ +struct virtio_scsi_req_hdr { + uint8_t lun[8]; + uint64_t id; + uint8_t task_attr; + uint8_t prio; + uint8_t crn; + uint8_t cdb[32]; +} __packed; +/* Followed by data-out. */ + +/* Response header structure */ +struct virtio_scsi_res_hdr { + uint32_t sense_len; + uint32_t residual; + uint16_t status_qualifier; + uint8_t status; + uint8_t response; + uint8_t sense[96]; +} __packed; +/* Followed by data-in. */ -- cgit v1.2.3