diff options
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/pv/hvs.c | 988 |
1 files changed, 988 insertions, 0 deletions
diff --git a/sys/dev/pv/hvs.c b/sys/dev/pv/hvs.c new file mode 100644 index 00000000000..faa6fe08ccc --- /dev/null +++ b/sys/dev/pv/hvs.c @@ -0,0 +1,988 @@ +/*- + * Copyright (c) 2009-2012,2016 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2017 Mike Belopuhov <mike@esdenera.com> + * 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 unmodified, 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. + */ + +/* + * The OpenBSD port was done under funding by Esdenera Networks GmbH. + */ + +#include "bio.h" + +/* #define HVS_DEBUG_IO */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/atomic.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/buf.h> +#include <sys/malloc.h> +#include <sys/task.h> + +#include <machine/bus.h> + +#include <uvm/uvm_extern.h> + +#include <dev/pv/hypervreg.h> +#include <dev/pv/hypervvar.h> + +#include <scsi/scsi_all.h> +#include <scsi/cd.h> +#include <scsi/scsi_disk.h> +#include <scsi/scsiconf.h> + +#define HVS_PROTO_VERSION_WIN6 0x200 +#define HVS_PROTO_VERSION_WIN7 0x402 +#define HVS_PROTO_VERSION_WIN8 0x501 +#define HVS_PROTO_VERSION_WIN8_1 0x600 +#define HVS_PROTO_VERSION_WIN10 0x602 + +#define HVS_MSG_IODONE 0x01 +#define HVS_MSG_DEVGONE 0x02 +#define HVS_MSG_ENUMERATE 0x0b + +#define HVS_REQ_SCSIIO 0x03 +#define HVS_REQ_STARTINIT 0x07 +#define HVS_REQ_FINISHINIT 0x08 +#define HVS_REQ_QUERYPROTO 0x09 +#define HVS_REQ_QUERYPROPS 0x0a + +struct hvs_cmd_hdr { + uint32_t hdr_op; + uint32_t hdr_flags; + uint32_t hdr_status; +#define cmd_op cmd_hdr.hdr_op +#define cmd_flags cmd_hdr.hdr_flags +#define cmd_status cmd_hdr.hdr_status +} __packed; + +/* Negotiate version */ +struct hvs_cmd_ver { + struct hvs_cmd_hdr cmd_hdr; + uint16_t cmd_ver; + uint16_t cmd_rev; +} __packed; + +/* Query channel properties */ +struct hvs_chp { + uint16_t chp_proto; + uint8_t chp_path; + uint8_t chp_target; + uint16_t chp_maxchan; + uint16_t chp_port; + uint32_t chp_chflags; + uint32_t chp_maxfer; + uint64_t chp_chanid; +} __packed; + +struct hvs_cmd_chp { + struct hvs_cmd_hdr cmd_hdr; + struct hvs_chp cmd_chp; +} __packed; + +#define SENSE_DATA_LEN_WIN7 18 +#define SENSE_DATA_LEN 20 +#define MAX_SRB_DATA 20 + +/* SCSI Request Block */ +struct hvs_srb { + uint16_t srb_reqlen; + uint8_t srb_iostatus; + uint8_t srb_scsistatus; + + uint8_t srb_initiator; + uint8_t srb_bus; + uint8_t srb_target; + uint8_t srb_lun; + + uint8_t srb_cdblen; + uint8_t srb_senselen; + uint8_t srb_direction; + uint8_t _reserved; + + uint32_t srb_datalen; + uint8_t srb_data[MAX_SRB_DATA]; +} __packed; + +#define SRB_DATA_WRITE 0 +#define SRB_DATA_READ 1 +#define SRB_DATA_NONE 2 + +#define SRB_STATUS_PENDING 0x00 +#define SRB_STATUS_SUCCESS 0x01 +#define SRB_STATUS_ABORTED 0x02 +#define SRB_STATUS_ERROR 0x04 +#define SRB_STATUS_INVALID_LUN 0x20 +#define SRB_STATUS_QUEUE_FROZEN 0x40 +#define SRB_STATUS_AUTOSENSE_VALID 0x80 + +#define SRB_FLAGS_QUEUE_ACTION_ENABLE 0x00000002 +#define SRB_FLAGS_DISABLE_DISCONNECT 0x00000004 +#define SRB_FLAGS_DISABLE_SYNCH_TRANSFER 0x00000008 +#define SRB_FLAGS_BYPASS_FROZEN_QUEUE 0x00000010 +#define SRB_FLAGS_DISABLE_AUTOSENSE 0x00000020 +#define SRB_FLAGS_DATA_IN 0x00000040 +#define SRB_FLAGS_DATA_OUT 0x00000080 +#define SRB_FLAGS_NO_DATA_TRANSFER 0x00000000 +#define SRB_FLAGS_NO_QUEUE_FREEZE 0x00000100 +#define SRB_FLAGS_ADAPTER_CACHE_ENABLE 0x00000200 +#define SRB_FLAGS_FREE_SENSE_BUFFER 0x00000400 + +/* for Win7 and older */ +struct hvs_cmd_io { + struct hvs_cmd_hdr cmd_hdr; + struct hvs_srb cmd_srb; +} __packed; + +/* SCSI Request Block with Win8 extensions */ +struct hvs_cmd_xio { + struct hvs_cmd_hdr cmd_hdr; + struct hvs_srb cmd_srb; + uint16_t _reserved; + uint8_t cmd_qtag; + uint8_t cmd_qaction; + uint32_t cmd_srbflags; + uint32_t cmd_timeout; + uint32_t cmd_qsortkey; +} __packed; + +struct hvs_dma_mem { + bus_size_t dma_size; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_dma_segment_t *dma_seg; + int dma_nsegs; /* total amount */ + int dma_rsegs; /* used amount */ + caddr_t dma_vaddr; +}; + +#define HVS_CMD_SIZE 64 + +union hvs_cmd { + struct hvs_cmd_hdr cmd_hdr; + struct hvs_cmd_ver ver; + struct hvs_cmd_chp chp; + struct hvs_cmd_io io; + struct hvs_cmd_xio xio; + uint8_t pad[HVS_CMD_SIZE]; +} __packed; + +#define HVS_MAX_SGE (MAXPHYS / PAGE_SIZE + 1) + +struct hvs_ccb { + struct scsi_xfer *ccb_xfer; /* associated transfer */ + bus_dmamap_t ccb_dmap; /* transfer map */ + uint64_t ccb_rid; /* request id */ + struct vmbus_gpa_range *ccb_sgl; + int ccb_nsge; + SIMPLEQ_ENTRY(hvs_ccb) ccb_link; +}; +SIMPLEQ_HEAD(hvs_ccb_queue, hvs_ccb); + +struct hvs_softc { + struct device sc_dev; + struct hv_softc *sc_hvsc; + struct hv_channel *sc_chan; + bus_dma_tag_t sc_dmat; + + int sc_proto; + int sc_flags; +#define HVSF_SCSI 0x0001 +#define HVSF_XIO 0x0002 + struct hvs_chp sc_props; + + union hvs_cmd sc_resp; + struct mutex sc_resplck; + uint32_t sc_rid; + + /* CCBs */ + int sc_nccb; + struct hvs_ccb *sc_ccbs; + struct hvs_ccb_queue sc_ccb_fq; /* free queue */ + struct mutex sc_ccb_fqlck; + + int sc_bus; + int sc_initiator; + + struct scsi_iopool sc_iopool; + struct scsi_adapter sc_switch; + struct scsi_link sc_link; + struct device *sc_scsibus; + struct task sc_probetask; +}; + +int hvs_match(struct device *, void *, void *); +void hvs_attach(struct device *, struct device *, void *); + +void hvs_scsi_cmd(struct scsi_xfer *); +void hvs_scsi_probe(void *arg); +void hvs_intr(void *); +void hvs_complete_cmd(struct hvs_softc *, union hvs_cmd *, uint64_t); +void hvs_scsi_done(struct scsi_xfer *, int); + +int hvs_dma_alloc(struct hvs_softc *, struct hvs_dma_mem *, + bus_size_t, int, int); +void hvs_dma_free(struct hvs_softc *, struct hvs_dma_mem *); + +int hvs_connect(struct hvs_softc *); +int hvs_cmd(struct hvs_softc *, void *, uint64_t, int); + +int hvs_alloc_ccbs(struct hvs_softc *); +void hvs_free_ccbs(struct hvs_softc *); +void *hvs_get_ccb(void *); +void hvs_put_ccb(void *, void *); + +struct cfdriver hvs_cd = { + NULL, "hvs", DV_DULL +}; + +const struct cfattach hvs_ca = { + sizeof(struct hvs_softc), hvs_match, hvs_attach +}; + +int +hvs_match(struct device *parent, void *match, void *aux) +{ + struct hv_attach_args *aa = aux; + + if (/* strcmp("ide", aa->aa_ident) && */ + strcmp("scsi", aa->aa_ident)) + return (0); + + return (1); +} + +void +hvs_attach(struct device *parent, struct device *self, void *aux) +{ + struct hv_attach_args *aa = aux; + struct hvs_softc *sc = (struct hvs_softc *)self; + struct scsibus_attach_args saa; + + sc->sc_hvsc = (struct hv_softc *)parent; + sc->sc_chan = aa->aa_chan; + sc->sc_dmat = aa->aa_dmat; + + if (strcmp("scsi", aa->aa_ident) == 0) + sc->sc_flags |= HVSF_SCSI; + + if (hv_channel_open(sc->sc_chan, 20 * PAGE_SIZE, &sc->sc_props, + sizeof(sc->sc_props), hvs_intr, sc)) { + printf(": failed to open channel\n"); + return; + } + + printf(" channel %u: %s", sc->sc_chan->ch_id, aa->aa_ident); + + if (hvs_connect(sc)) + return; + + printf(", protocol %u.%u\n", (sc->sc_proto >> 8) & 0xff, + sc->sc_proto & 0xff); + + if (sc->sc_proto >= HVS_PROTO_VERSION_WIN8) + sc->sc_flags |= HVSF_XIO; + + if (hvs_alloc_ccbs(sc)) + return; + + task_set(&sc->sc_probetask, hvs_scsi_probe, sc); + + sc->sc_switch.scsi_cmd = hvs_scsi_cmd; + sc->sc_switch.scsi_minphys = scsi_minphys; + + sc->sc_link.adapter = &sc->sc_switch; + sc->sc_link.adapter_softc = self; + sc->sc_link.adapter_buswidth = sc->sc_flags & HVSF_SCSI ? 64 : 1; + sc->sc_link.adapter_target = sc->sc_flags & HVSF_SCSI ? 64 : 1; + sc->sc_link.openings = sc->sc_nccb; + sc->sc_link.pool = &sc->sc_iopool; + + memset(&saa, 0, sizeof(saa)); + saa.saa_sc_link = &sc->sc_link; + sc->sc_scsibus = config_found(self, &saa, scsiprint); +} + +void +hvs_scsi_cmd(struct scsi_xfer *xs) +{ + struct scsi_link *link = xs->sc_link; + struct hvs_softc *sc = link->adapter_softc; + struct hvs_ccb *ccb = xs->io; + union hvs_cmd cmd; + struct hvs_cmd_io *io = &cmd.io; + struct hvs_cmd_xio *xio = &cmd.xio; + struct hvs_srb *srb = &io->cmd_srb; + int i, rv, flags = BUS_DMA_NOWAIT; + uint64_t rid; + + if (xs->cmdlen > HVS_CMD_SIZE) { + printf("%s: CDB is too big: %d\n", sc->sc_dev.dv_xname, + xs->cmdlen); + memset(&xs->sense, 0, sizeof(xs->sense)); + xs->sense.error_code = SSD_ERRCODE_VALID | 0x70; + xs->sense.flags = SKEY_ILLEGAL_REQUEST; + xs->sense.add_sense_code = 0x20; + hvs_scsi_done(xs, XS_SENSE); + return; + } + + KERNEL_UNLOCK(); + + memset(&cmd, 0, sizeof(cmd)); + + srb->srb_initiator = sc->sc_initiator; + srb->srb_bus = sc->sc_bus; + srb->srb_target = link->target; + srb->srb_lun = link->lun; + + srb->srb_cdblen = xs->cmdlen; + memcpy(srb->srb_data, xs->cmd, xs->cmdlen); + + switch (xs->flags & (SCSI_DATA_IN | SCSI_DATA_OUT)) { + case SCSI_DATA_IN: + srb->srb_direction = SRB_DATA_READ; + if (sc->sc_flags & HVSF_XIO) + xio->cmd_srbflags |= SRB_FLAGS_DATA_IN; + flags |= BUS_DMA_WRITE; + break; + case SCSI_DATA_OUT: + srb->srb_direction = SRB_DATA_WRITE; + if (sc->sc_flags & HVSF_XIO) + xio->cmd_srbflags |= SRB_FLAGS_DATA_OUT; + flags |= BUS_DMA_READ; + break; + default: + srb->srb_direction = SRB_DATA_NONE; + if (sc->sc_flags & HVSF_XIO) + xio->cmd_srbflags |= SRB_FLAGS_NO_DATA_TRANSFER; + break; + } + + srb->srb_datalen = xs->datalen; + + if (sc->sc_flags & HVSF_XIO) { + srb->srb_reqlen = sizeof(*xio); + srb->srb_senselen = SENSE_DATA_LEN; + } else { + srb->srb_reqlen = sizeof(*io); + srb->srb_senselen = SENSE_DATA_LEN_WIN7; + } + + cmd.cmd_op = HVS_REQ_SCSIIO; + cmd.cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + rid = (uint64_t)ccb->ccb_rid << 32; + + if (xs->datalen > 0) { + rv = bus_dmamap_load(sc->sc_dmat, ccb->ccb_dmap, xs->data, + xs->datalen, NULL, flags); + if (rv) { + printf("%s: failed to load %d bytes (%d)\n", + sc->sc_dev.dv_xname, xs->datalen, rv); + KERNEL_LOCK(); + hvs_scsi_done(xs, XS_DRIVER_STUFFUP); + return; + } + + ccb->ccb_sgl->gpa_len = xs->datalen; + ccb->ccb_sgl->gpa_ofs = (vaddr_t)xs->data & PAGE_MASK; + for (i = 0; i < ccb->ccb_dmap->dm_nsegs; i++) + ccb->ccb_sgl->gpa_page[i] = + atop(ccb->ccb_dmap->dm_segs[i].ds_addr); + ccb->ccb_nsge = ccb->ccb_dmap->dm_nsegs; + } + + ccb->ccb_xfer = xs; + + if (xs->datalen > 0) { + rv = hv_channel_send_prpl(sc->sc_chan, ccb->ccb_sgl, + ccb->ccb_nsge, &cmd, sizeof(cmd), rid); + if (rv) { + printf("%s: failed to submit operation %x via prpl\n", + sc->sc_dev.dv_xname, xs->cmd->opcode); + bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmap); + } + } else { + rv = hv_channel_send(sc->sc_chan, &cmd, sizeof(cmd), rid, + VMBUS_CHANPKT_TYPE_INBAND, VMBUS_CHANPKT_FLAG_RC); + if (rv) + printf("%s: failed to submit operation %x\n", + sc->sc_dev.dv_xname, xs->cmd->opcode); + } + if (rv) { + KERNEL_LOCK(); + hvs_scsi_done(xs, XS_DRIVER_STUFFUP); + return; + } + +#ifdef HVS_DEBUG_IO + DPRINTF("%s: opcode %#x flags %#x datalen %d\n", sc->sc_dev.dv_xname, + xs->cmd->opcode, xs->flags, xs->datalen); +#endif + + if (xs->flags & SCSI_POLL) { + int timo = 1000; + + do { + if (xs->flags & ITSDONE) + break; + if (xs->flags & SCSI_NOSLEEP) + delay(100); + else + tsleep(xs, PRIBIO, "hvspoll", 1); + hvs_intr(sc); + } while(--timo > 0); + + if (!(xs->flags & ITSDONE)) { + printf("%s: operation %#x datalen %d timed out\n", + sc->sc_dev.dv_xname, xs->cmd->opcode, xs->datalen); + KERNEL_LOCK(); + hvs_scsi_done(xs, XS_TIMEOUT); + return; + } + } + + KERNEL_LOCK(); +} + +void +hvs_scsi_probe(void *arg) +{ + struct hvs_softc *sc = arg; + + if (sc->sc_scsibus) + scsi_probe_bus((void *)sc->sc_scsibus); +} + +void +hvs_intr(void *xsc) +{ + struct hvs_softc *sc = xsc; + union hvs_cmd cmd; + uint64_t rid; + uint32_t rlen; + int rv; + + for (;;) { + rv = hv_channel_recv(sc->sc_chan, &cmd, sizeof(cmd), &rlen, + &rid, 0); + if (rv != 0 || rlen == 0) { + if (rv != EAGAIN) + printf("%s: failed to receive a packet (%d-%u)\n", + sc->sc_dev.dv_xname, rv, rlen); + break; + } + if (rlen != sizeof(cmd)) { + printf("%s: short read: %u\n", sc->sc_dev.dv_xname, + rlen); + return; + } + +#ifdef HVS_DEBUG_IO + DPRINTF("%s: opertaion %u flags %#x status %#x\n", + sc->sc_dev.dv_xname, cmd.cmd_op, cmd.cmd_flags, + cmd.cmd_status); +#endif + + /* Initialization */ + if (rid == 0) { + memcpy(&sc->sc_resp, &cmd, sizeof(cmd)); + wakeup_one(&sc->sc_resp); + continue; + } + + switch (cmd.cmd_op) { + case HVS_MSG_IODONE: + hvs_complete_cmd(sc, &cmd, rid); + break; + case HVS_MSG_ENUMERATE: + task_add(systq, &sc->sc_probetask); + break; + default: + printf("%s: operation %u is not implemented\n", + sc->sc_dev.dv_xname, cmd.cmd_op); + } + } +} + +static inline int +is_inquiry_valid(struct scsi_inquiry_data *inq) +{ + if ((inq->device & SID_TYPE) == T_NODEVICE) + return (0); + if ((inq->device & SID_QUAL) == SID_QUAL_BAD_LU) + return (0); + return (1); +} + +static inline void +fixup_inquiry(struct scsi_xfer *xs, struct hvs_srb *srb) +{ + struct hvs_softc *sc = xs->sc_link->adapter_softc; + struct scsi_inquiry_data *inq = (struct scsi_inquiry_data *)xs->data; + int datalen, resplen; + char vendor[16]; + + resplen = srb->srb_datalen >= 5 ? inq->additional_length + 5 : 0; + datalen = MIN(resplen, srb->srb_datalen); + + /* Fixup wrong response from WS2012 */ + if ((sc->sc_proto == HVS_PROTO_VERSION_WIN8_1 || + sc->sc_proto == HVS_PROTO_VERSION_WIN8 || + sc->sc_proto == HVS_PROTO_VERSION_WIN7) && + !is_inquiry_valid(inq) && datalen >= 4 && + (inq->version == 0 || inq->response_format == 0)) { + inq->version = 0x05; /* SPC-3 */ + inq->response_format = 2; + } else if (datalen >= SID_INQUIRY_HDR + SID_SCSI2_ALEN) { + /* + * Upgrade SPC2 to SPC3 if host is Win8 or WS2012 R2 + * to support UNMAP feature. + */ + scsi_strvis(vendor, inq->vendor, sizeof(vendor)); + if ((sc->sc_proto == HVS_PROTO_VERSION_WIN8_1 || + sc->sc_proto == HVS_PROTO_VERSION_WIN8) && + SCSISPC(inq->version) == 2 && + !strncmp(vendor, "Msft", 4)) + inq->version = 0x05; /* SPC-3 */ + } +} + +void +hvs_complete_cmd(struct hvs_softc *sc, union hvs_cmd *cmd, uint64_t rid) +{ + struct scsi_xfer *xs; + struct hvs_ccb *ccb; + struct hvs_srb *srb; + bus_dmamap_t map; + int error; + + if ((rid & 0xffffffff) != 0 || rid >> 32 >= sc->sc_nccb) { + printf("%s: invalid response %#llx\n", sc->sc_dev.dv_xname, + rid); + return; + } + + ccb = &sc->sc_ccbs[rid >> 32]; + + map = ccb->ccb_dmap; + bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, map); + + xs = ccb->ccb_xfer; + srb = &cmd->io.cmd_srb; + + if (srb->srb_datalen > xs->datalen) + printf("%s: transfer length %u too large: %u\n", + sc->sc_dev.dv_xname, srb->srb_datalen, xs->datalen); + else if (srb->srb_datalen) + xs->resid = xs->datalen - srb->srb_datalen; + + if ((srb->srb_scsistatus & 0xff) == SCSI_CHECK && + srb->srb_iostatus & SRB_STATUS_AUTOSENSE_VALID) + memcpy(&xs->sense, srb->srb_data, MIN(sizeof(xs->sense), + srb->srb_senselen)); + + error = srb->srb_scsistatus & 0xff; + + if (srb->srb_scsistatus != SCSI_OK) { + KERNEL_LOCK(); + hvs_scsi_done(xs, error); + KERNEL_UNLOCK(); + return; + } + + if ((srb->srb_iostatus & ~(SRB_STATUS_AUTOSENSE_VALID | + SRB_STATUS_QUEUE_FROZEN)) != SRB_STATUS_SUCCESS) + error = XS_SELTIMEOUT; + else if (xs->cmd->opcode == INQUIRY) + fixup_inquiry(xs, srb); + + KERNEL_LOCK(); + hvs_scsi_done(xs, error); + KERNEL_UNLOCK(); +} + +void +hvs_scsi_done(struct scsi_xfer *xs, int error) +{ + int s; + + KERNEL_ASSERT_LOCKED(); + + xs->error = error; + + s = splbio(); + scsi_done(xs); + splx(s); +} + +int +hvs_connect(struct hvs_softc *sc) +{ + const uint32_t protos[] = { + HVS_PROTO_VERSION_WIN10, + HVS_PROTO_VERSION_WIN8_1, + HVS_PROTO_VERSION_WIN8, + HVS_PROTO_VERSION_WIN7, + HVS_PROTO_VERSION_WIN6 + }; + union hvs_cmd ucmd; + struct hvs_cmd_ver *cmd; + struct hvs_chp *chp; + int i; + + mtx_init(&sc->sc_resplck, IPL_BIO); + + cmd = (struct hvs_cmd_ver *)&ucmd; + + /* + * Begin initialization + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_STARTINIT; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + if (hvs_cmd(sc, cmd, 0, 100)) { + printf(": failed to send initialization command\n"); + return (-1); + } + if (sc->sc_resp.cmd_op != HVS_MSG_IODONE || + sc->sc_resp.cmd_status != 0) { + printf(": failed to initialize, status %#x\n", + sc->sc_resp.cmd_status); + return (-1); + } + + /* + * Negotiate protocol version + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_QUERYPROTO; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + for (i = 0; i < nitems(protos); i++) { + cmd->cmd_ver = protos[i]; + + if (hvs_cmd(sc, cmd, 0, 100)) { + printf(": failed to send protocol query\n"); + return (-1); + } + if (sc->sc_resp.cmd_op != HVS_MSG_IODONE) { + printf(": failed to negotiate protocol, status %#x\n", + sc->sc_resp.cmd_status); + return (-1); + } + if (sc->sc_resp.cmd_status == 0) { + sc->sc_proto = protos[i]; + break; + } + } + if (!sc->sc_proto) { + printf(": failed to negotiate protocol version\n"); + return (-1); + } + + /* + * Query channel properties + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_QUERYPROPS; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + if (hvs_cmd(sc, cmd, 0, 100)) { + printf(": failed to send channel properties query\n"); + return (-1); + } + if (sc->sc_resp.cmd_op != HVS_MSG_IODONE || + sc->sc_resp.cmd_status != 0) { + printf(": failed to obtain channel properties, status %#x\n", + sc->sc_resp.cmd_status); + return (-1); + } + chp = &sc->sc_resp.chp.cmd_chp; + + DPRINTF(": proto %#x path %u target %u maxchan %u", + chp->chp_proto, chp->chp_path, chp->chp_target, + chp->chp_maxchan); + DPRINTF(" port %u chflags %#x maxfer %u chanid %#llx", + chp->chp_port, chp->chp_chflags, chp->chp_maxfer, + chp->chp_chanid); + + /* XXX */ + sc->sc_bus = chp->chp_path; + sc->sc_initiator = chp->chp_target; + + /* + * Finish initialization + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_FINISHINIT; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + if (hvs_cmd(sc, cmd, 0, 100)) { + printf(": failed to send initialization finish\n"); + return (-1); + } + if (sc->sc_resp.cmd_op != HVS_MSG_IODONE || + sc->sc_resp.cmd_status != 0) { + printf(": failed to finish initialization, status %#x\n", + sc->sc_resp.cmd_status); + return (-1); + } + + return (0); +} + +int +hvs_cmd(struct hvs_softc *sc, void *xcmd, uint64_t tid, int timo) +{ + union hvs_cmd *cmd = xcmd; + int tries = 10; + int rv; + + do { + rv = hv_channel_send(sc->sc_chan, cmd, HVS_CMD_SIZE, + tid, VMBUS_CHANPKT_TYPE_INBAND, + timo ? VMBUS_CHANPKT_FLAG_RC : 0); + if (rv == EAGAIN) { + if (timo) + tsleep(cmd, PRIBIO, "hvsout", timo / 10); + else + delay(100); + } else if (rv) { + DPRINTF("%s: operation %u send error %d\n", + sc->sc_dev.dv_xname, cmd->cmd_op, rv); + return (rv); + } + } while (rv != 0 && --tries > 0); + + if (timo) { + mtx_enter(&sc->sc_resplck); + rv = msleep(&sc->sc_resp, &sc->sc_resplck, PRIBIO, "hvscmd", + timo); + mtx_leave(&sc->sc_resplck); + if (rv == EWOULDBLOCK) + printf("%s: operation %u timed out\n", + sc->sc_dev.dv_xname, cmd->cmd_op); + } + return (rv); +} + +int +hvs_dma_alloc(struct hvs_softc *sc, struct hvs_dma_mem *dma, + bus_size_t size, int nsegs, int mapflags) +{ + int error; + + dma->dma_tag = sc->sc_dmat; + + dma->dma_seg = mallocarray(nsegs, sizeof(bus_dma_segment_t), M_DEVBUF, + M_ZERO | M_NOWAIT); + if (dma->dma_seg == NULL) { + printf("%s: failed to allocate a segment array\n", + sc->sc_dev.dv_xname); + return (ENOMEM); + } + + error = bus_dmamap_create(dma->dma_tag, size, nsegs, PAGE_SIZE, 0, + BUS_DMA_NOWAIT, &dma->dma_map); + if (error) { + printf("%s: failed to create a memory map (%d)\n", + sc->sc_dev.dv_xname, error); + goto errout; + } + + error = bus_dmamem_alloc(dma->dma_tag, size, PAGE_SIZE, 0, + dma->dma_seg, nsegs, &dma->dma_rsegs, BUS_DMA_ZERO | + BUS_DMA_NOWAIT); + if (error) { + printf("%s: failed to allocate DMA memory (%d)\n", + sc->sc_dev.dv_xname, error); + goto destroy; + } + + error = bus_dmamem_map(dma->dma_tag, dma->dma_seg, dma->dma_rsegs, + size, &dma->dma_vaddr, BUS_DMA_NOWAIT); + if (error) { + printf("%s: failed to map DMA memory (%d)\n", + sc->sc_dev.dv_xname, error); + goto free; + } + + error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, + size, NULL, mapflags | BUS_DMA_NOWAIT); + if (error) { + printf("%s: failed to load DMA memory (%d)\n", + sc->sc_dev.dv_xname, error); + goto unmap; + } + + dma->dma_size = size; + dma->dma_nsegs = nsegs; + return (0); + + unmap: + bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, size); + free: + bus_dmamem_free(dma->dma_tag, dma->dma_seg, dma->dma_rsegs); + destroy: + bus_dmamap_destroy(dma->dma_tag, dma->dma_map); + errout: + free(dma->dma_seg, M_DEVBUF, nsegs * sizeof(bus_dma_segment_t)); + dma->dma_map = NULL; + dma->dma_tag = NULL; + return (error); +} + +void +hvs_dma_free(struct hvs_softc *sc, struct hvs_dma_mem *dma) +{ + if (dma->dma_tag == NULL || dma->dma_map == NULL) + return; + bus_dmamap_sync(dma->dma_tag, dma->dma_map, 0, dma->dma_size, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(dma->dma_tag, dma->dma_map); + bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, dma->dma_size); + bus_dmamem_free(dma->dma_tag, dma->dma_seg, dma->dma_rsegs); + bus_dmamap_destroy(dma->dma_tag, dma->dma_map); + free(dma->dma_seg, M_DEVBUF, dma->dma_nsegs * sizeof(bus_dma_segment_t)); + dma->dma_seg = NULL; + dma->dma_map = NULL; + dma->dma_size = 0; +} + +int +hvs_alloc_ccbs(struct hvs_softc *sc) +{ + int i, error; + + SIMPLEQ_INIT(&sc->sc_ccb_fq); + mtx_init(&sc->sc_ccb_fqlck, IPL_BIO); + + sc->sc_nccb = 16; /* XXX */ + + sc->sc_ccbs = mallocarray(sc->sc_nccb, sizeof(struct hvs_ccb), + M_DEVBUF, M_ZERO | M_NOWAIT); + if (sc->sc_ccbs == NULL) { + printf("%s: failed to allocate CCBs\n", sc->sc_dev.dv_xname); + return (-1); + } + + for (i = 0; i < sc->sc_nccb; i++) { + error = bus_dmamap_create(sc->sc_dmat, MAXPHYS, HVS_MAX_SGE, + PAGE_SIZE, PAGE_SIZE, BUS_DMA_NOWAIT, + &sc->sc_ccbs[i].ccb_dmap); + if (error) { + printf("%s: failed to create a CCB memory map (%d)\n", + sc->sc_dev.dv_xname, error); + goto errout; + } + + sc->sc_ccbs[i].ccb_sgl = malloc(sizeof(struct vmbus_gpa_range) * + (HVS_MAX_SGE + 1), M_DEVBUF, M_ZERO | M_NOWAIT); + if (sc->sc_ccbs[i].ccb_sgl == NULL) { + printf("%s: failed to allocate SGL array\n", + sc->sc_dev.dv_xname); + goto errout; + } + + sc->sc_ccbs[i].ccb_rid = i; + hvs_put_ccb(sc, &sc->sc_ccbs[i]); + } + + scsi_iopool_init(&sc->sc_iopool, sc, hvs_get_ccb, hvs_put_ccb); + + return (0); + + errout: + hvs_free_ccbs(sc); + return (-1); +} + +void +hvs_free_ccbs(struct hvs_softc *sc) +{ + struct hvs_ccb *ccb; + int i; + + for (i = 0; i < sc->sc_nccb; i++) { + ccb = &sc->sc_ccbs[i]; + if (ccb->ccb_dmap == NULL) + continue; + bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmap, 0, 0, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmap); + bus_dmamap_destroy(sc->sc_dmat, ccb->ccb_dmap); + + free(ccb->ccb_sgl, M_DEVBUF, sizeof(struct vmbus_gpa_range) * + (HVS_MAX_SGE + 1)); + } + + free(sc->sc_ccbs, M_DEVBUF, sc->sc_nccb * sizeof(struct hvs_ccb)); + sc->sc_ccbs = NULL; + sc->sc_nccb = 0; +} + +void * +hvs_get_ccb(void *xsc) +{ + struct hvs_softc *sc = xsc; + struct hvs_ccb *ccb; + + mtx_enter(&sc->sc_ccb_fqlck); + ccb = SIMPLEQ_FIRST(&sc->sc_ccb_fq); + if (ccb != NULL) + SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_fq, ccb_link); + mtx_leave(&sc->sc_ccb_fqlck); + + return (ccb); +} + +void +hvs_put_ccb(void *xsc, void *io) +{ + struct hvs_softc *sc = xsc; + struct hvs_ccb *ccb = io; + + ccb->ccb_xfer = NULL; + + mtx_enter(&sc->sc_ccb_fqlck); + SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_fq, ccb, ccb_link); + mtx_leave(&sc->sc_ccb_fqlck); +} |