diff options
Diffstat (limited to 'sys/dev/pci/vioblk.c')
-rw-r--r-- | sys/dev/pci/vioblk.c | 619 |
1 files changed, 619 insertions, 0 deletions
diff --git a/sys/dev/pci/vioblk.c b/sys/dev/pci/vioblk.c new file mode 100644 index 00000000000..c87278b8a7d --- /dev/null +++ b/sys/dev/pci/vioblk.c @@ -0,0 +1,619 @@ +/* + * Copyright (c) 2012 Stefan Fritsch. + * Copyright (c) 2010 Minoura Makoto. + * Copyright (c) 1998, 2001 Manuel Bouyer. + * All rights reserved. + * + * This code is based in part on the NetBSD ld_virtio driver and the + * OpenBSD vdsk driver. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ + +/* + * Copyright (c) 2009, 2011 Mark Kettenis + * + * 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 <sys/cdefs.h> +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <machine/bus.h> + +#include <sys/device.h> +#include <sys/stat.h> +#include <sys/buf.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/virtioreg.h> +#include <dev/pci/virtiovar.h> +#include <dev/pci/vioblkreg.h> + +#include <scsi/scsi_all.h> +#include <scsi/scsi_disk.h> +#include <scsi/scsiconf.h> + +#define VIOBLK_DONE -1 + +struct virtio_feature_name vioblk_feature_names[] = { + { VIRTIO_BLK_F_BARRIER, "Barrier" }, + { VIRTIO_BLK_F_SIZE_MAX, "SizeMax" }, + { VIRTIO_BLK_F_SEG_MAX, "SegMax" }, + { VIRTIO_BLK_F_GEOMETRY, "Geometry" }, + { VIRTIO_BLK_F_RO, "RO" }, + { VIRTIO_BLK_F_BLK_SIZE, "BlkSize" }, + { VIRTIO_BLK_F_SCSI, "SCSI" }, + { VIRTIO_BLK_F_FLUSH, "Flush" }, + { VIRTIO_BLK_F_TOPOLOGY, "Topology" }, + { 0, NULL } +}; + +struct virtio_blk_req { + struct virtio_blk_req_hdr vr_hdr; + uint8_t vr_status; + struct scsi_xfer *vr_xs; + int vr_len; + bus_dmamap_t vr_cmdsts; + bus_dmamap_t vr_payload; +}; + +struct vioblk_softc { + struct device sc_dev; + struct virtio_softc *sc_virtio; + + struct virtqueue sc_vq[1]; + struct virtio_blk_req *sc_reqs; + bus_dma_segment_t sc_reqs_segs[1]; + + struct scsi_adapter sc_switch; + struct scsi_link sc_link; + + int sc_notify_on_empty; + + uint32_t sc_queued; + + /* device configuration */ + uint64_t sc_capacity; + uint32_t sc_xfer_max; + uint32_t sc_seg_max; +}; + +int vioblk_match(struct device *, void *, void *); +void vioblk_attach(struct device *, struct device *, void *); +int vioblk_alloc_reqs(struct vioblk_softc *, int); +int vioblk_vq_done(struct virtqueue *); +void vioblk_vq_done1(struct vioblk_softc *, struct virtio_softc *, + struct virtqueue *, int); +void vioblk_minphys(struct buf *, struct scsi_link *); + +void vioblk_scsi_cmd(struct scsi_xfer *); +int vioblk_dev_probe(struct scsi_link *); +void vioblk_dev_free(struct scsi_link *); + +void vioblk_scsi_inq(struct scsi_xfer *); +void vioblk_scsi_capacity(struct scsi_xfer *); +void vioblk_scsi_capacity16(struct scsi_xfer *); +void vioblk_scsi_done(struct scsi_xfer *, int); + +struct cfattach vioblk_ca = { + sizeof(struct vioblk_softc), + vioblk_match, + vioblk_attach, + NULL +}; + +struct cfdriver vioblk_cd = { + NULL, "vioblk", DV_DULL +}; + + +int vioblk_match(struct device *parent, void *match, void *aux) +{ + struct virtio_softc *va = aux; + if (va->sc_childdevid == PCI_PRODUCT_VIRTIO_BLOCK) + return 1; + return 0; +} + +#if VIRTIO_DEBUG > 0 +#define DBGPRINT(fmt, args...) printf("%s: " fmt "\n", __func__, ## args) +#else +#define DBGPRINT(fmt, args...) do {} while (0) +#endif + +void +vioblk_minphys(struct buf *bp, struct scsi_link *sl) +{ + struct vioblk_softc *sc = sl->adapter_softc; + if (bp->b_bcount > sc->sc_xfer_max) + bp->b_bcount = sc->sc_xfer_max; +} + +void +vioblk_attach(struct device *parent, struct device *self, void *aux) +{ + struct vioblk_softc *sc = (struct vioblk_softc *)self; + struct virtio_softc *vsc = (struct virtio_softc *)parent; + struct scsibus_attach_args saa; + uint32_t features; + int qsize; + + vsc->sc_vqs = &sc->sc_vq[0]; + vsc->sc_nvqs = 1; + vsc->sc_config_change = 0; + if (vsc->sc_child) + panic("already attached to something else"); + vsc->sc_child = self; + vsc->sc_ipl = IPL_BIO; + vsc->sc_intrhand = virtio_vq_intr; + sc->sc_virtio = vsc; + + features = virtio_negotiate_features(vsc, + (VIRTIO_BLK_F_RO | VIRTIO_F_NOTIFY_ON_EMPTY | + VIRTIO_BLK_F_SIZE_MAX | VIRTIO_BLK_F_SEG_MAX | + VIRTIO_BLK_F_FLUSH), + vioblk_feature_names); + + + if (features & VIRTIO_BLK_F_SIZE_MAX) { + uint32_t size_max = virtio_read_device_config_4(vsc, + VIRTIO_BLK_CONFIG_SIZE_MAX); + if (size_max < NBPG) { + printf("\nMax segment size %u too low\n", size_max); + goto err; + } + } + + if (features & VIRTIO_BLK_F_SEG_MAX) { + sc->sc_seg_max = virtio_read_device_config_4(vsc, + VIRTIO_BLK_CONFIG_SEG_MAX); + sc->sc_seg_max = MIN(sc->sc_seg_max, MAXPHYS/NBPG + 2); + } else { + sc->sc_seg_max = MAXPHYS/NBPG + 2; + } + sc->sc_xfer_max = (sc->sc_seg_max - 2) * NBPG; + + sc->sc_capacity = virtio_read_device_config_8(vsc, + VIRTIO_BLK_CONFIG_CAPACITY); + + if (virtio_alloc_vq(vsc, &sc->sc_vq[0], 0, sc->sc_xfer_max, + sc->sc_seg_max, "I/O request") != 0) { + printf("\nCan't alloc virtqueue\n"); + goto err; + } + qsize = sc->sc_vq[0].vq_num; + sc->sc_vq[0].vq_done = vioblk_vq_done; + if (vioblk_alloc_reqs(sc, qsize) < 0) { + printf("\nCan't alloc reqs\n"); + goto err; + } + + if (features & VIRTIO_F_NOTIFY_ON_EMPTY) { + virtio_stop_vq_intr(vsc, &sc->sc_vq[0]); + sc->sc_notify_on_empty = 1; + } + else { + sc->sc_notify_on_empty = 0; + } + + sc->sc_queued = 0; + + sc->sc_switch.scsi_cmd = vioblk_scsi_cmd; + sc->sc_switch.scsi_minphys = vioblk_minphys; + sc->sc_switch.dev_probe = vioblk_dev_probe; + sc->sc_switch.dev_free = vioblk_dev_free; + + sc->sc_link.adapter = &sc->sc_switch; + sc->sc_link.adapter_softc = self; + sc->sc_link.adapter_buswidth = 2; + sc->sc_link.luns = 1; + sc->sc_link.adapter_target = 2; + sc->sc_link.openings = qsize; + DBGPRINT("; qsize: %d seg_max: %d", qsize, sc->sc_seg_max); + if (features & VIRTIO_BLK_F_RO) + sc->sc_link.flags |= SDEV_READONLY; + + bzero(&saa, sizeof(saa)); + saa.saa_sc_link = &sc->sc_link; + printf("\n"); + config_found(self, &saa, scsiprint); + + return; +err: + vsc->sc_child = VIRTIO_CHILD_ERROR; + return; +} + +int +vioblk_vq_done(struct virtqueue *vq) +{ + struct virtio_softc *vsc = vq->vq_owner; + struct vioblk_softc *sc = (struct vioblk_softc *)vsc->sc_child; + int slot; + int ret = 0; + + if (!sc->sc_notify_on_empty) + virtio_stop_vq_intr(vsc, vq); + for (;;) { + if (virtio_dequeue(vsc, vq, &slot, NULL) != 0) { + if (sc->sc_notify_on_empty) + break; + virtio_start_vq_intr(vsc, vq); + if (virtio_dequeue(vsc, vq, &slot, NULL) != 0) + break; + } + vioblk_vq_done1(sc, vsc, vq, slot); + ret = 1; + } + return ret; +} + +void +vioblk_vq_done1(struct vioblk_softc *sc, struct virtio_softc *vsc, + struct virtqueue *vq, int slot) +{ + struct virtio_blk_req *vr = &sc->sc_reqs[slot]; + struct scsi_xfer *xs = vr->vr_xs; + KASSERT(vr->vr_len != VIOBLK_DONE); + bus_dmamap_sync(vsc->sc_dmat, vr->vr_cmdsts, 0, + sizeof(struct virtio_blk_req_hdr), BUS_DMASYNC_POSTWRITE); + if (vr->vr_hdr.type != VIRTIO_BLK_T_FLUSH) { + bus_dmamap_sync(vsc->sc_dmat, vr->vr_payload, 0, vr->vr_len, + (vr->vr_hdr.type == VIRTIO_BLK_T_IN) ? + BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE); + } + bus_dmamap_sync(vsc->sc_dmat, vr->vr_cmdsts, + sizeof(struct virtio_blk_req_hdr), sizeof(uint8_t), + BUS_DMASYNC_POSTREAD); + + + if (vr->vr_status != VIRTIO_BLK_S_OK) { + DBGPRINT("EIO"); + xs->error = XS_DRIVER_STUFFUP; + xs->resid = xs->datalen; + } else { + xs->error = XS_NOERROR; + xs->resid = xs->datalen - vr->vr_len; + } + scsi_done(xs); + vr->vr_len = VIOBLK_DONE; + + virtio_dequeue_commit(vq, slot); +} + +void +vioblk_scsi_cmd(struct scsi_xfer *xs) +{ + struct scsi_rw *rw; + struct scsi_rw_big *rwb; + u_int64_t lba = 0; + u_int32_t sector_count; + uint8_t operation; + int isread; + + switch (xs->cmd->opcode) { + case READ_BIG: + case READ_COMMAND: + operation = VIRTIO_BLK_T_IN; + isread = 1; + break; + case WRITE_BIG: + case WRITE_COMMAND: + operation = VIRTIO_BLK_T_OUT; + isread = 0; + break; + + case SYNCHRONIZE_CACHE: + operation = VIRTIO_BLK_T_FLUSH; + break; + + case INQUIRY: + vioblk_scsi_inq(xs); + return; + case READ_CAPACITY: + vioblk_scsi_capacity(xs); + return; + case READ_CAPACITY_16: + vioblk_scsi_capacity16(xs); + return; + + case TEST_UNIT_READY: + case START_STOP: + case PREVENT_ALLOW: + vioblk_scsi_done(xs, XS_NOERROR); + return; + + default: + printf("%s cmd 0x%02x\n", __func__, xs->cmd->opcode); + case MODE_SENSE: + case MODE_SENSE_BIG: + case REPORT_LUNS: + vioblk_scsi_done(xs, XS_DRIVER_STUFFUP); + return; + } + + if (xs->cmdlen == 6) { + rw = (struct scsi_rw *)xs->cmd; + lba = _3btol(rw->addr) & (SRW_TOPADDR << 16 | 0xffff); + sector_count = rw->length ? rw->length : 0x100; + } else { + rwb = (struct scsi_rw_big *)xs->cmd; + lba = _4btol(rwb->addr); + sector_count = _2btol(rwb->length); + } + +{ + struct vioblk_softc *sc = xs->sc_link->adapter_softc; + struct virtqueue *vq = &sc->sc_vq[0]; + struct virtio_blk_req *vr; + struct virtio_softc *vsc = sc->sc_virtio; + int len, s; + int timeout; + int slot, ret, nsegs; + + s = splbio(); + ret = virtio_enqueue_prep(vq, &slot); + if (ret) { + DBGPRINT("virtio_enqueue_prep: %d, vq_num: %d, sc_queued: %d", + ret, vq->vq_num, sc->sc_queued); + vioblk_scsi_done(xs, XS_NO_CCB); + splx(s); + return; + } + vr = &sc->sc_reqs[slot]; + if (operation != VIRTIO_BLK_T_FLUSH) { + len = MIN(xs->datalen, sector_count * VIRTIO_BLK_SECTOR_SIZE); + ret = bus_dmamap_load(vsc->sc_dmat, vr->vr_payload, + xs->data, len, NULL, + ((isread ? BUS_DMA_READ : BUS_DMA_WRITE) | + BUS_DMA_NOWAIT)); + if (ret) { + DBGPRINT("bus_dmamap_load: %d", ret); + goto out_enq_abort; + } + nsegs = vr->vr_payload->dm_nsegs + 2; + } else { + len = 0; + nsegs = 2; + } + ret = virtio_enqueue_reserve(vq, slot, nsegs); + if (ret) { + DBGPRINT("virtio_enqueue_reserve: %d", ret); + bus_dmamap_unload(vsc->sc_dmat, vr->vr_payload); + goto out_done; + } + vr->vr_xs = xs; + vr->vr_hdr.type = operation; + vr->vr_hdr.ioprio = 0; + vr->vr_hdr.sector = lba; + vr->vr_len = len; + + bus_dmamap_sync(vsc->sc_dmat, vr->vr_cmdsts, + 0, sizeof(struct virtio_blk_req_hdr), + BUS_DMASYNC_PREWRITE); + if (operation != VIRTIO_BLK_T_FLUSH) { + bus_dmamap_sync(vsc->sc_dmat, vr->vr_payload, 0, len, + isread ? BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE); + } + bus_dmamap_sync(vsc->sc_dmat, vr->vr_cmdsts, + offsetof(struct virtio_blk_req, vr_status), sizeof(uint8_t), + BUS_DMASYNC_PREREAD); + + virtio_enqueue_p(vq, slot, vr->vr_cmdsts, 0, + sizeof(struct virtio_blk_req_hdr), 1); + if (operation != VIRTIO_BLK_T_FLUSH) + virtio_enqueue(vq, slot, vr->vr_payload, !isread); + virtio_enqueue_p(vq, slot, vr->vr_cmdsts, + offsetof(struct virtio_blk_req, vr_status), sizeof(uint8_t), 0); + virtio_enqueue_commit(vsc, vq, slot, 1); + sc->sc_queued++; + + if (!ISSET(xs->flags, SCSI_POLL)) { + /* check if some xfers are done: */ + if (sc->sc_queued > 1) + vioblk_vq_done(vq); + splx(s); + return; + } + + timeout = 1000; + do { + if (vsc->sc_ops->intr(vsc) && vr->vr_len == VIOBLK_DONE) + break; + + delay(1000); + } while(--timeout > 0); + splx(s); + return; + +out_enq_abort: + virtio_enqueue_abort(vq, slot); +out_done: + vioblk_scsi_done(xs, XS_NO_CCB); + vr->vr_len = VIOBLK_DONE; + splx(s); +} +} + +void +vioblk_scsi_inq(struct scsi_xfer *xs) +{ + struct scsi_inquiry *inq = (struct scsi_inquiry *)xs->cmd; + struct scsi_inquiry_data inqd; + + if (ISSET(inq->flags, SI_EVPD)) { + vioblk_scsi_done(xs, XS_DRIVER_STUFFUP); + return; + } + + bzero(&inqd, sizeof(inqd)); + + inqd.device = T_DIRECT; + inqd.version = 0x05; /* SPC-3 */ + inqd.response_format = 2; + inqd.additional_length = 32; + inqd.flags |= SID_CmdQue; + bcopy("VirtIO ", inqd.vendor, sizeof(inqd.vendor)); + bcopy("Block Device ", inqd.product, sizeof(inqd.product)); + + bcopy(&inqd, xs->data, MIN(sizeof(inqd), xs->datalen)); + vioblk_scsi_done(xs, XS_NOERROR); +} + +void +vioblk_scsi_capacity(struct scsi_xfer *xs) +{ + struct vioblk_softc *sc = xs->sc_link->adapter_softc; + struct scsi_read_cap_data rcd; + uint64_t capacity; + + bzero(&rcd, sizeof(rcd)); + + capacity = sc->sc_capacity - 1; + if (capacity > 0xffffffff) + capacity = 0xffffffff; + + _lto4b(capacity, rcd.addr); + _lto4b(VIRTIO_BLK_SECTOR_SIZE, rcd.length); + + bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen)); + vioblk_scsi_done(xs, XS_NOERROR); +} + +void +vioblk_scsi_capacity16(struct scsi_xfer *xs) +{ + struct vioblk_softc *sc = xs->sc_link->adapter_softc; + struct scsi_read_cap_data_16 rcd; + + bzero(&rcd, sizeof(rcd)); + + _lto8b(sc->sc_capacity - 1, rcd.addr); + _lto4b(VIRTIO_BLK_SECTOR_SIZE, rcd.length); + + bcopy(&rcd, xs->data, MIN(sizeof(rcd), xs->datalen)); + vioblk_scsi_done(xs, XS_NOERROR); +} + +void +vioblk_scsi_done(struct scsi_xfer *xs, int error) +{ + xs->error = error; + scsi_done(xs); +} + +int +vioblk_dev_probe(struct scsi_link *link) +{ + KASSERT(link->lun == 0); + if (link->target == 0) + return (0); + return (ENODEV); +} + +void +vioblk_dev_free(struct scsi_link *link) +{ + printf("%s\n", __func__); +} + +int +vioblk_alloc_reqs(struct vioblk_softc *sc, int qsize) +{ + int allocsize, r, rsegs, i; + void *vaddr; + + allocsize = sizeof(struct virtio_blk_req) * qsize; + r = bus_dmamem_alloc(sc->sc_virtio->sc_dmat, allocsize, 0, 0, + &sc->sc_reqs_segs[0], 1, &rsegs, BUS_DMA_NOWAIT); + if (r != 0) { + printf("DMA memory allocation failed, size %d, error %d\n", + allocsize, r); + goto err_none; + } + r = bus_dmamem_map(sc->sc_virtio->sc_dmat, &sc->sc_reqs_segs[0], 1, + allocsize, (caddr_t *)&vaddr, BUS_DMA_NOWAIT); + if (r != 0) { + printf("DMA memory map failed, error %d\n", r); + goto err_dmamem_alloc; + } + sc->sc_reqs = vaddr; + memset(vaddr, 0, allocsize); + for (i = 0; i < qsize; i++) { + struct virtio_blk_req *vr = &sc->sc_reqs[i]; + vr->vr_len = VIOBLK_DONE; + r = bus_dmamap_create(sc->sc_virtio->sc_dmat, + offsetof(struct virtio_blk_req, vr_xs), 1, + offsetof(struct virtio_blk_req, vr_xs), 0, + BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &vr->vr_cmdsts); + if (r != 0) { + printf("cmd dmamap creation failed, err %d\n", r); + goto err_reqs; + } + r = bus_dmamap_load(sc->sc_virtio->sc_dmat, vr->vr_cmdsts, + &vr->vr_hdr, offsetof(struct virtio_blk_req, vr_xs), NULL, + BUS_DMA_NOWAIT); + if (r != 0) { + printf("command dmamap load failed, err %d\n", r); + goto err_reqs; + } + r = bus_dmamap_create(sc->sc_virtio->sc_dmat, MAXPHYS, + sc->sc_seg_max, MAXPHYS, 0, + BUS_DMA_NOWAIT|BUS_DMA_ALLOCNOW, &vr->vr_payload); + if (r != 0) { + printf("payload dmamap creation failed, err %d\n", r); + goto err_reqs; + } + } + return 0; + +err_reqs: + for (i = 0; i < qsize; i++) { + struct virtio_blk_req *vr = &sc->sc_reqs[i]; + if (vr->vr_cmdsts) { + bus_dmamap_destroy(sc->sc_virtio->sc_dmat, + vr->vr_cmdsts); + vr->vr_cmdsts = 0; + } + if (vr->vr_payload) { + bus_dmamap_destroy(sc->sc_virtio->sc_dmat, + vr->vr_payload); + vr->vr_payload = 0; + } + } + bus_dmamem_unmap(sc->sc_virtio->sc_dmat, (caddr_t)sc->sc_reqs, + allocsize); +err_dmamem_alloc: + bus_dmamem_free(sc->sc_virtio->sc_dmat, &sc->sc_reqs_segs[0], 1); +err_none: + return -1; +} |