From 0a372e49708cb0209e31ca6cf9fe86559ea2b38d Mon Sep 17 00:00:00 2001 From: Kimihiro Nonaka Date: Mon, 30 Apr 2018 02:56:37 +0900 Subject: [PATCH 3/5] hvs(4): Hyper-V StorVSC (WIP) --- sys/arch/amd64/conf/GENERIC | 2 +- sys/arch/x86/conf/files.x86 | 4 + sys/arch/x86/x86/hvs.c | 1122 +++++++++++++++++++++++++++++++++++ 3 files changed, 1127 insertions(+), 1 deletion(-) create mode 100644 sys/arch/x86/x86/hvs.c diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index 7a877e335b9..6e85ab4ab98 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1251,7 +1251,7 @@ vioscsi* at virtio? # Virtio SCSI device # Hyper-V devices vmbus* at acpi? # Hyper-V VMBus #hvn* at vmbus? # Hyper-V NetVSC -#hvs* at vmbus? # Hyper-V StorVSC +hvs* at vmbus? # Hyper-V StorVSC hvheartbeat* at vmbus? # Hyper-V Heartbeat hvshutdown* at vmbus? # Hyper-V Shutdown hvtimesync* at vmbus? # Hyper-V Timesync diff --git a/sys/arch/x86/conf/files.x86 b/sys/arch/x86/conf/files.x86 index f4ae0dfc39c..b4f4f5bdc4e 100644 --- a/sys/arch/x86/conf/files.x86 +++ b/sys/arch/x86/conf/files.x86 @@ -81,6 +81,10 @@ define hypervvmbus {} device vmbus: hypervvmbus file arch/x86/x86/vmbus.c vmbus needs-flag +device hvs: scsi +attach hvs at hypervvmbus +file arch/x86/x86/hvs.c hvs + device hvheartbeat attach hvheartbeat at hypervvmbus file arch/x86/x86/hvheartbeat.c hvheartbeat diff --git a/sys/arch/x86/x86/hvs.c b/sys/arch/x86/x86/hvs.c new file mode 100644 index 00000000000..d16214c9842 --- /dev/null +++ b/sys/arch/x86/x86/hvs.c @@ -0,0 +1,1122 @@ +/* $NetBSD$ */ +/* $OpenBSD: hvs.c,v 1.17 2017/08/10 17:22:48 mikeb Exp $ */ + +/*- + * Copyright (c) 2009-2012,2016 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2017 Mike Belopuhov + * 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. + */ + +/* #define HVS_DEBUG_IO */ + +#include +__KERNEL_RCSID(0, "$NetBSD$"); + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +#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 +#define HVS_REQ_CREATEMULTICHANNELS 0x0d + +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; +#define CHP_CHFLAGS_MULTI_CHANNEL 0x1 + 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 + +struct hvs_cmd_io { + struct hvs_cmd_hdr cmd_hdr; + struct hvs_srb cmd_srb; + /* Win8 extensions */ + uint16_t _reserved; + uint8_t cmd_qtag; + uint8_t cmd_qaction; + uint32_t cmd_srbflags; + uint32_t cmd_timeout; + uint32_t cmd_qsortkey; +} __packed; + +#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; + uint16_t multi_chans_cnt; + uint8_t pad[HVS_CMD_SIZE]; +} __packed; + +#define HVS_RING_SIZE (20 * PAGE_SIZE) +#define HVS_MAX_CCB 128 +#define HVS_MAX_SGE (MAXPHYS / PAGE_SIZE + 1) + +struct hvs_softc; + +struct hvs_ccb { + struct scsipi_xfer *ccb_xfer; /* associated transfer */ + union hvs_cmd *ccb_cmd; /* associated command */ + union hvs_cmd ccb_rsp; /* response */ + bus_dmamap_t ccb_dmap; /* transfer map */ + uint64_t ccb_rid; /* request id */ + struct vmbus_gpa_range *ccb_sgl; + int ccb_nsge; + void (*ccb_done)(struct hvs_ccb *); + void *ccb_cookie; + SIMPLEQ_ENTRY(hvs_ccb) ccb_link; +}; +SIMPLEQ_HEAD(hvs_ccb_queue, hvs_ccb); + +struct hvs_config; + +struct hvs_softc { + device_t sc_dev; + bus_dma_tag_t sc_dmat; + + struct vmbus_channel *sc_chan; + + const struct hvs_config *sc_config; + + struct hvs_chp sc_props; + + /* CCBs */ + int sc_nccb; + struct hvs_ccb *sc_ccbs; + struct hvs_ccb_queue sc_ccb_fq; /* free queue */ + kmutex_t sc_ccb_fqlck; + + int sc_bus; + + struct scsipi_adapter sc_adapter; + struct scsipi_channel sc_channel; + device_t sc_scsibus; +#if notyet /* XXX subchannel */ + u_int sc_nchan; + struct vmbus_channel *sc_sel_chan[MAXCPUS]; +#endif +}; + +static int hvs_match(device_t, cfdata_t, void *); +static void hvs_attach(device_t, device_t, void *); +static int hvs_detach(device_t, int); + +CFATTACH_DECL_NEW(hvs, sizeof(struct hvs_softc), + hvs_match, hvs_attach, hvs_detach, NULL); + +static void hvs_scsipi_request(struct scsipi_channel *, + scsipi_adapter_req_t, void *); +static void hvs_scsi_cmd_done(struct hvs_ccb *); +static int hvs_start(struct hvs_softc *, struct vmbus_channel *, + struct hvs_ccb *); +static int hvs_poll(struct hvs_softc *, struct vmbus_channel *, + struct hvs_ccb *); +static void hvs_poll_done(struct hvs_ccb *); +static void hvs_intr(void *); +static void hvs_scsi_probe(void *arg); +static void hvs_scsi_done(struct scsipi_xfer *, int); + +static int hvs_connect(struct hvs_softc *); +static void hvs_empty_done(struct hvs_ccb *); + +static int hvs_alloc_ccbs(struct hvs_softc *); +static void hvs_free_ccbs(struct hvs_softc *); +static struct hvs_ccb * + hvs_get_ccb(struct hvs_softc *); +static void hvs_put_ccb(struct hvs_softc *, struct hvs_ccb *); + +static const struct hvs_config { + uint32_t proto_version; + uint16_t reqlen; + uint8_t senselen; + bool fixup_wrong_response; + bool upgrade_spc2_to_spc3; + bool use_win8ext_flags; +} hvs_config_list[] = { + { + .proto_version = HVS_PROTO_VERSION_WIN10, + .reqlen = sizeof(struct hvs_cmd_io), + .senselen = SENSE_DATA_LEN, + .fixup_wrong_response = false, + .upgrade_spc2_to_spc3 = false, + .use_win8ext_flags = true, + }, + { + .proto_version = HVS_PROTO_VERSION_WIN8_1, + .reqlen = sizeof(struct hvs_cmd_io), + .senselen = SENSE_DATA_LEN, + .fixup_wrong_response = true, + .upgrade_spc2_to_spc3 = true, + .use_win8ext_flags = true, + }, + { + .proto_version = HVS_PROTO_VERSION_WIN8, + .reqlen = sizeof(struct hvs_cmd_io), + .senselen = SENSE_DATA_LEN, + .fixup_wrong_response = true, + .upgrade_spc2_to_spc3 = true, + .use_win8ext_flags = true, + }, + { + .proto_version = HVS_PROTO_VERSION_WIN7, + .reqlen = offsetof(struct hvs_cmd_io, _reserved), + .senselen = SENSE_DATA_LEN_WIN7, + .fixup_wrong_response = true, + .upgrade_spc2_to_spc3 = false, + .use_win8ext_flags = false, + }, + { + .proto_version = HVS_PROTO_VERSION_WIN6, + .reqlen = offsetof(struct hvs_cmd_io, _reserved), + .senselen = SENSE_DATA_LEN_WIN7, + .fixup_wrong_response = false, + .upgrade_spc2_to_spc3 = false, + .use_win8ext_flags = false, + }, +}; + +#if notyet /* XXX subchannel */ +static int hvs_chan_cnt; +#endif + +static int +hvs_match(device_t parent, cfdata_t cf, void *aux) +{ + struct vmbus_attach_args *aa = aux; + + if (memcmp(aa->aa_type, &hyperv_guid_ide, sizeof(*aa->aa_type)) != 0 && + memcmp(aa->aa_type, &hyperv_guid_scsi, sizeof(*aa->aa_type)) != 0) + return 0; + return 1; +} + +static void +hvs_attach(device_t parent, device_t self, void *aux) +{ + struct hvs_softc *sc = device_private(self); + struct vmbus_attach_args *aa = aux; + struct scsipi_adapter *adapt = &sc->sc_adapter; + struct scsipi_channel *chan = &sc->sc_channel; + const char *bus; + bool is_scsi; +#if 0 /* XXX pciide_skip_ata */ + extern int pciide_skip_ata; +#endif + + sc->sc_dev = self; + sc->sc_chan = aa->aa_chan; + sc->sc_dmat = sc->sc_chan->ch_sc->sc_dmat; +#if notyet /* XXX subchannel */ + sc->sc_nchan = 1; + sc->sc_sel_chan[0] = sc->sc_chan; +#endif + + if (memcmp(aa->aa_type, &hyperv_guid_scsi, sizeof(*aa->aa_type)) == 0) { + is_scsi = true; + bus = "SCSI"; + } else { + is_scsi = false; + bus = "IDE"; + } + + aprint_naive("\n"); + aprint_normal(": Hyper-V StorVSC %s\n", bus); + + if (vmbus_channel_setdeferred(sc->sc_chan, device_xname(self))) { + aprint_error_dev(self, + "failed to create the interrupt thread\n"); + return; + } + + if (vmbus_channel_open(sc->sc_chan, HVS_RING_SIZE, &sc->sc_props, + sizeof(sc->sc_props), hvs_intr, sc)) { + aprint_error_dev(self, "failed to open channel\n"); + return; + } + + if (hvs_alloc_ccbs(sc)) + return; + + if (hvs_connect(sc)) + return; + + aprint_normal_dev(self, "protocol %u.%u\n", + (sc->sc_config->proto_version >> 8) & 0xff, + sc->sc_config->proto_version & 0xff); + + adapt = &sc->sc_adapter; + adapt->adapt_dev = self; + adapt->adapt_nchannels = 1; + adapt->adapt_openings = sc->sc_nccb; + adapt->adapt_max_periph = adapt->adapt_openings; + adapt->adapt_request = hvs_scsipi_request; + adapt->adapt_minphys = minphys; + adapt->adapt_flags = SCSIPI_ADAPT_MPSAFE; + + chan = &sc->sc_channel; + chan->chan_adapter = adapt; + chan->chan_bustype = &scsi_bustype; /* XXX IDE/ATAPI */ + chan->chan_channel = 0; + chan->chan_ntargets = 2; + chan->chan_nluns = is_scsi ? 64 : 1; + chan->chan_id = 0; + chan->chan_flags = SCSIPI_CHAN_NOSETTLE; + chan->chan_defquirks |= PQUIRK_ONLYBIG; + + sc->sc_scsibus = config_found(self, &sc->sc_channel, scsiprint); + + /* + * If the driver has successfully attached to an IDE + * device, we need to make sure that the same disk is + * not available to the system via pciide(4) causing + * DUID conflicts and preventing system from booting. + */ +#if 0 /* XXX pciide_skip_ata */ + if (!is_scsi && sc->sc_scsibus != NULL) + pciide_skip_ata = 1; +#endif +} + +static int +hvs_detach(device_t self, int flags) +{ + + /* XXX detach */ + + return 0; +} + +#define XS2DMA(xs) \ + ((((xs)->xs_control & XS_CTL_DATA_IN) ? BUS_DMA_READ : BUS_DMA_WRITE) | \ + (((xs)->xs_control & XS_CTL_NOSLEEP) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK) | \ + BUS_DMA_STREAMING) + +#define XS2DMAPRE(xs) (((xs)->xs_control & XS_CTL_DATA_IN) ? \ + BUS_DMASYNC_PREREAD : BUS_DMASYNC_PREWRITE) + +#define XS2DMAPOST(xs) (((xs)->xs_control & XS_CTL_DATA_IN) ? \ + BUS_DMASYNC_POSTREAD : BUS_DMASYNC_POSTWRITE) + +static void +hvs_scsipi_request(struct scsipi_channel *chan, scsipi_adapter_req_t request, + void *arg) +{ + struct scsipi_adapter *adapt = chan->chan_adapter; + struct hvs_softc *sc = device_private(adapt->adapt_dev); + struct scsipi_xfer *xs; + struct scsipi_xfer_mode *xm; + struct scsipi_periph *periph; + struct hvs_ccb *ccb; + union hvs_cmd cmd; + struct hvs_cmd_io *io = &cmd.io; + struct hvs_srb *srb = &io->cmd_srb; + int i, error; + + switch (request) { + default: + aprint_error_dev(sc->sc_dev, + "%s: unhandled request %u\n", __func__, request); + return; + + case ADAPTER_REQ_GROW_RESOURCES: + /* Not supported. */ + return; + + case ADAPTER_REQ_SET_XFER_MODE: + xm = arg; + xm->xm_mode = PERIPH_CAP_TQING; + xm->xm_period = 0; + xm->xm_offset = 0; + scsipi_async_event(chan, ASYNC_EVENT_XFER_MODE, xm); + return; + + case ADAPTER_REQ_RUN_XFER: + break; + } + + xs = arg; + + if (xs->cmdlen > MAX_SRB_DATA) { + aprint_error_dev(sc->sc_dev, "CDB is too big: %d\n", + xs->cmdlen); + memset(&xs->sense, 0, sizeof(xs->sense)); + xs->sense.scsi_sense.response_code = + SSD_RCODE_VALID | SSD_RCODE_CURRENT; + xs->sense.scsi_sense.flags = SSD_ILI; + xs->sense.scsi_sense.asc = 0x20; + hvs_scsi_done(xs, XS_SENSE); + return; + } + + ccb = hvs_get_ccb(sc); + if (ccb == NULL) { + aprint_error_dev(sc->sc_dev, "failed to allocate ccb\n"); + hvs_scsi_done(xs, XS_RESOURCE_SHORTAGE); + return; + } + + periph = xs->xs_periph; + + memset(&cmd, 0, sizeof(cmd)); + + srb->srb_initiator = chan->chan_id; + srb->srb_bus = sc->sc_bus; + srb->srb_target = periph->periph_target - 1; + srb->srb_lun = periph->periph_lun; + srb->srb_cdblen = xs->cmdlen; + memcpy(srb->srb_data, xs->cmd, xs->cmdlen); + + if (sc->sc_config->use_win8ext_flags) { + io->cmd_timeout = 60; + SET(io->cmd_srbflags, SRB_FLAGS_DISABLE_SYNCH_TRANSFER); + } + + switch (xs->xs_control & (XS_CTL_DATA_IN | XS_CTL_DATA_OUT)) { + case XS_CTL_DATA_IN: + srb->srb_direction = SRB_DATA_READ; + if (sc->sc_config->use_win8ext_flags) + SET(io->cmd_srbflags, SRB_FLAGS_DATA_IN); + break; + case XS_CTL_DATA_OUT: + srb->srb_direction = SRB_DATA_WRITE; + if (sc->sc_config->use_win8ext_flags) + SET(io->cmd_srbflags, SRB_FLAGS_DATA_OUT); + break; + default: + srb->srb_direction = SRB_DATA_NONE; + if (sc->sc_config->use_win8ext_flags) + SET(io->cmd_srbflags, SRB_FLAGS_NO_DATA_TRANSFER); + break; + } + + srb->srb_datalen = xs->datalen; + srb->srb_reqlen = sc->sc_config->reqlen; + srb->srb_senselen = sc->sc_config->senselen; + + cmd.cmd_op = HVS_REQ_SCSIIO; + cmd.cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + if (xs->datalen > 0) { + error = bus_dmamap_load(sc->sc_dmat, ccb->ccb_dmap, xs->data, + xs->datalen, NULL, XS2DMA(xs)); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to load %d bytes (%d)\n", xs->datalen, + error); + hvs_put_ccb(sc, ccb); + hvs_scsi_done(xs, (error == ENOMEM || error == EAGAIN) ? + XS_RESOURCE_SHORTAGE : XS_DRIVER_STUFFUP); + return; + } + bus_dmamap_sync(sc->sc_dmat, ccb->ccb_dmap, 0, xs->datalen, + XS2DMAPRE(xs)); + + 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; + } else + ccb->ccb_nsge = 0; + + ccb->ccb_xfer = xs; + ccb->ccb_cmd = &cmd; + ccb->ccb_done = hvs_scsi_cmd_done; + +#ifdef HVS_DEBUG_IO + printf("%s: %u.%u: rid %"PRIu64" opcode %#x control %#x datalen %d\n", + device_xname(sc->sc_dev), periph->periph_target, periph->periph_lun, + ccb->ccb_rid, xs->cmd->opcode, xs->xs_control, xs->datalen); +#endif + + if (xs->xs_control & XS_CTL_POLL) + error = hvs_poll(sc, sc->sc_chan, ccb); + else + error = hvs_start(sc, sc->sc_chan, ccb); + if (error) { + hvs_put_ccb(sc, ccb); + hvs_scsi_done(xs, (error == ENOMEM || error == EAGAIN) ? + XS_RESOURCE_SHORTAGE : XS_DRIVER_STUFFUP); + } +} + +static int +hvs_start(struct hvs_softc *sc, struct vmbus_channel *chan, struct hvs_ccb *ccb) +{ + union hvs_cmd *cmd = ccb->ccb_cmd; + int error; + + ccb->ccb_cmd = NULL; + + if (ccb->ccb_nsge > 0) { + error = vmbus_channel_send_prpl(chan, ccb->ccb_sgl, + ccb->ccb_nsge, cmd, HVS_CMD_SIZE, ccb->ccb_rid); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to submit operation %x via prpl\n", + cmd->cmd_op); + bus_dmamap_unload(sc->sc_dmat, ccb->ccb_dmap); + } + } else { + error = vmbus_channel_send(chan, cmd, HVS_CMD_SIZE, + ccb->ccb_rid, VMBUS_CHANPKT_TYPE_INBAND, + VMBUS_CHANPKT_FLAG_RC); + if (error) + aprint_error_dev(sc->sc_dev, + "failed to submit operation %x\n", cmd->cmd_op); + } + + return error; +} + +static void +hvs_poll_done(struct hvs_ccb *ccb) +{ + int *rv = ccb->ccb_cookie; + + if (ccb->ccb_cmd) { + memcpy(&ccb->ccb_rsp, ccb->ccb_cmd, HVS_CMD_SIZE); + ccb->ccb_cmd = &ccb->ccb_rsp; + } else + memset(&ccb->ccb_rsp, 0, HVS_CMD_SIZE); + + *rv = 0; +} + +static int +hvs_poll(struct hvs_softc *sc, struct vmbus_channel *chan, struct hvs_ccb *ccb) +{ + void (*done)(struct hvs_ccb *); + void *cookie; + int s, rv = 1; + + done = ccb->ccb_done; + cookie = ccb->ccb_cookie; + + ccb->ccb_done = hvs_poll_done; + ccb->ccb_cookie = &rv; + + if (hvs_start(sc, chan, ccb)) { + ccb->ccb_cookie = cookie; + ccb->ccb_done = done; + return -1; + } + + while (rv == 1) { + delay(10); + s = splbio(); + hvs_intr(sc); + splx(s); + } + + ccb->ccb_cookie = cookie; + ccb->ccb_done = done; + ccb->ccb_done(ccb); + + return 0; +} + +static void +hvs_intr(void *xsc) +{ + struct hvs_softc *sc = xsc; + struct hvs_ccb *ccb; + union hvs_cmd cmd; + uint64_t rid; + uint32_t rlen; + int error; + + for (;;) { + error = vmbus_channel_recv(sc->sc_chan, &cmd, sizeof(cmd), + &rlen, &rid, 0); + switch (error) { + case 0: + break; + case EAGAIN: + /* No more messages to process */ + return; + default: + aprint_error_dev(sc->sc_dev, + "error %d while receiving a reply\n", error); + return; + } + if (rlen != sizeof(cmd)) { + aprint_error_dev(sc->sc_dev, "short read: %u\n", rlen); + return; + } + +#ifdef HVS_DEBUG_IO + printf("%s: rid %"PRIu64" operation %u flags %#x status %#x\n", + device_xname(sc->sc_dev), rid, cmd.cmd_op, cmd.cmd_flags, + cmd.cmd_status); +#endif + + switch (cmd.cmd_op) { + case HVS_MSG_IODONE: + if (rid >= sc->sc_nccb) { + aprint_error_dev(sc->sc_dev, + "invalid response %#"PRIx64"\n", rid); + continue; + } + ccb = &sc->sc_ccbs[rid]; + ccb->ccb_cmd = &cmd; + ccb->ccb_done(ccb); + break; + case HVS_MSG_ENUMERATE: + hvs_scsi_probe(sc); + break; + default: + aprint_error_dev(sc->sc_dev, + "operation %u is not implemented\n", cmd.cmd_op); + break; + } + } +} + +static int +is_inquiry_valid(struct scsipi_inquiry_data *inq) +{ + + if ((inq->device & SID_TYPE) == T_NODEVICE) + return 0; + if ((inq->device & SID_QUAL) == SID_QUAL_LU_NOT_SUPP) + return 0; + return 1; +} + +static void +fixup_inquiry(struct scsipi_xfer *xs, struct hvs_srb *srb) +{ + struct scsipi_periph *periph = xs->xs_periph; + struct scsipi_channel *chan = periph->periph_channel; + struct scsipi_adapter *adapt = chan->chan_adapter; + struct hvs_softc *sc = device_private(adapt->adapt_dev); + struct scsipi_inquiry_data *inq = (void *)xs->data; + int datalen, resplen; + char vendor[8]; + + resplen = srb->srb_datalen >= 5 ? inq->additional_length + 5 : 0; + datalen = MIN(resplen, srb->srb_datalen); + + /* Fixup wrong response from WS2012 */ + if (sc->sc_config->fixup_wrong_response && + !is_inquiry_valid(inq) && datalen >= 4 && + (inq->version == 0 || inq->response_format == 0)) { + inq->version = 0x05; /* SPC-3 */ + inq->response_format = SID_FORMAT_ISO; + } else if (datalen >= SCSIPI_INQUIRY_LENGTH_SCSI2) { + /* + * Upgrade SPC2 to SPC3 if host is Win8 or WS2012 R2 + * to support UNMAP feature. + */ + strnvisx(vendor, sizeof(vendor), inq->vendor, sizeof(vendor), + VIS_TRIM|VIS_SAFE|VIS_OCTAL); + if (sc->sc_config->upgrade_spc2_to_spc3 && + (inq->version & SID_ANSII) == 0x04 /* SPC-2 */ && + !strncmp(vendor, "Msft", 4)) + inq->version = 0x05; /* SPC-3 */ + } +} + +static void +hvs_scsi_cmd_done(struct hvs_ccb *ccb) +{ + struct scsipi_xfer *xs = ccb->ccb_xfer; + struct scsipi_periph *periph = xs->xs_periph; + struct scsipi_channel *chan = periph->periph_channel; + struct scsipi_adapter *adapt = chan->chan_adapter; + struct hvs_softc *sc = device_private(adapt->adapt_dev); + union hvs_cmd *cmd = ccb->ccb_cmd; + struct hvs_srb *srb; + bus_dmamap_t map; + int error; + + map = ccb->ccb_dmap; + bus_dmamap_sync(sc->sc_dmat, map, 0, map->dm_mapsize, XS2DMAPOST(xs)); + bus_dmamap_unload(sc->sc_dmat, map); + + xs = ccb->ccb_xfer; + srb = &cmd->io.cmd_srb; + + xs->status = srb->srb_scsistatus & 0xff; + + switch (xs->status) { + case SCSI_OK: + if ((srb->srb_iostatus & ~(SRB_STATUS_AUTOSENSE_VALID | + SRB_STATUS_QUEUE_FROZEN)) != SRB_STATUS_SUCCESS) + error = XS_SELTIMEOUT; + else + error = XS_NOERROR; + break; + case SCSI_BUSY: + case SCSI_QUEUE_FULL: + aprint_error_dev(sc->sc_dev, "status %#x iostatus %#x (busy)\n", + srb->srb_scsistatus, srb->srb_iostatus); + error = XS_BUSY; + break; + case SCSI_CHECK: + if (srb->srb_iostatus & SRB_STATUS_AUTOSENSE_VALID) { + memcpy(&xs->sense, srb->srb_data, + MIN(sizeof(xs->sense), srb->srb_senselen)); + error = XS_SENSE; + break; + } + /* FALLTHROUGH */ + default: + error = XS_DRIVER_STUFFUP; + break; + } + + if (error == XS_NOERROR) { + if (xs->cmd->opcode == INQUIRY) + fixup_inquiry(xs, srb); + else if (srb->srb_direction != SRB_DATA_NONE) + xs->resid = xs->datalen - srb->srb_datalen; + } + + hvs_put_ccb(sc, ccb); + hvs_scsi_done(xs, error); +} + +static void +hvs_scsi_probe(void *arg) +{ + struct hvs_softc *sc = arg; + + if (sc->sc_scsibus != NULL) + scsi_probe_bus((void *)sc->sc_scsibus, -1, -1); +} + +static void +hvs_scsi_done(struct scsipi_xfer *xs, int error) +{ + + xs->error = error; + scsipi_done(xs); +} + +static int +hvs_connect(struct hvs_softc *sc) +{ + union hvs_cmd ucmd; + struct hvs_cmd_ver *cmd; + struct hvs_chp *chp; + struct hvs_ccb *ccb; +#if notyet /* XXX subchannel */ + struct vmbus_softc *vsc; + struct vmbus_channel **subchan; + uint32_t version; + uint16_t max_subch, req_subch; + bool support_multichannel = false; +#endif + int i; + + ccb = hvs_get_ccb(sc); + if (ccb == NULL) { + aprint_error_dev(sc->sc_dev, "failed to allocate ccb\n"); + return -1; + } + + ccb->ccb_done = hvs_empty_done; + + 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; + + ccb->ccb_cmd = &ucmd; + if (hvs_poll(sc, sc->sc_chan, ccb)) { + aprint_error_dev(sc->sc_dev, + "failed to send initialization command\n"); + goto error; + } + if (ccb->ccb_rsp.cmd_status != 0) { + aprint_error_dev(sc->sc_dev, + "failed to initialize, status %#x\n", + ccb->ccb_rsp.cmd_status); + goto error; + } + + /* + * 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 < __arraycount(hvs_config_list); i++) { + cmd->cmd_ver = hvs_config_list[i].proto_version; + + ccb->ccb_cmd = &ucmd; + if (hvs_poll(sc, sc->sc_chan, ccb)) { + aprint_error_dev(sc->sc_dev, + "failed to send protocol query\n"); + goto error; + } + if (ccb->ccb_rsp.cmd_status == 0) { + sc->sc_config = &hvs_config_list[i]; + break; + } + } + if (sc->sc_config == NULL) { + aprint_error_dev(sc->sc_dev, + "failed to negotiate protocol version\n"); + goto error; + } + + /* + * Query channel properties + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_QUERYPROPS; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + ccb->ccb_cmd = &ucmd; + if (hvs_poll(sc, sc->sc_chan, ccb)) { + aprint_error_dev(sc->sc_dev, + "failed to send channel properties query\n"); + goto error; + } + if (ccb->ccb_rsp.cmd_op != HVS_MSG_IODONE || + ccb->ccb_rsp.cmd_status != 0) { + aprint_error_dev(sc->sc_dev, + "failed to obtain channel properties, status %#x\n", + ccb->ccb_rsp.cmd_status); + goto error; + } + chp = &ccb->ccb_rsp.chp.cmd_chp; + + DPRINTF("%s: proto %#x path %u target %u maxchan %u port %u " + "chflags %#x maxfer %u chanid %#"PRIx64"\n", + device_xname(sc->sc_dev), chp->chp_proto, chp->chp_path, + chp->chp_target, chp->chp_maxchan, chp->chp_port, + chp->chp_chflags, chp->chp_maxfer, chp->chp_chanid); + +#if notyet /* XXX subchannel */ + max_subch = chp->chp_maxchan; + if (hvs_chan_cnt > 0 && hvs_chan_cnt < (max_subch + 1)) + max_subch = hvs_chan_cnt - 1; + + /* multi-channels feature is supported by WIN8 and above version */ + version = sc->sc_chan->ch_sc->sc_proto; + if (version != VMBUS_VERSION_WIN7 && version != VMBUS_VERSION_WS2008 && + ISSET(chp->chp_chflags, CHP_CHFLAGS_MULTI_CHANNEL)) + support_multichannel = true; +#endif + + /* XXX */ + sc->sc_bus = chp->chp_path; + sc->sc_channel.chan_id = chp->chp_target; + + /* + * Finish initialization + */ + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_FINISHINIT; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + + ccb->ccb_cmd = &ucmd; + if (hvs_poll(sc, sc->sc_chan, ccb)) { + aprint_error_dev(sc->sc_dev, + "failed to send initialization finish\n"); + goto error; + } + if (ccb->ccb_rsp.cmd_op != HVS_MSG_IODONE || + ccb->ccb_rsp.cmd_status != 0) { + aprint_error_dev(sc->sc_dev, + "failed to finish initialization, status %#x\n", + ccb->ccb_rsp.cmd_status); + goto error; + } + +#if notyet /* XXX subchannel */ + if (support_multichannel && max_subch > 0 && ncpu > 1) { + req_subch = MIN(max_subch, ncpu - 1); + + memset(&ucmd, 0, sizeof(ucmd)); + + cmd->cmd_op = HVS_REQ_CREATEMULTICHANNELS; + cmd->cmd_flags = VMBUS_CHANPKT_FLAG_RC; + cmd->u.multi_chans_cnt = req_subch; + + ccb->ccb_cmd = &ucmd; + if (hvs_poll(sc, sc->sc_chan, ccb)) { + aprint_error_dev(sc->sc_dev, + "failed to send create multi-channel\n"); + goto out; + } + if (ccb->ccb_rsp.cmd_op != HVS_MSG_IODONE || + ccb->ccb_rsp.cmd_status != 0) { + aprint_error_dev(sc->sc_dev, + "failed to create multi-channel, status %#x\n", + ccb->ccb_rsp.cmd_status); + goto out; + } + + sc->sc_nchan = req_subch + 1; + subchan = vmbus_subchan_get(sc->sc_chan, req_subch); + for (i = 0; i < req_subch; i++) + hsv_subchan_attach(sc, subchan[i]); + vmbus_subchan_rel(subchan, req_subch); + aprint_normal_dev(sc->sc_dev, "using %u channels\n", + sc->sc_nchan); + } +out: +#endif + hvs_put_ccb(sc, ccb); + return 0; + +error: + hvs_put_ccb(sc, ccb); + return -1; +} + +static void +hvs_empty_done(struct hvs_ccb *ccb) +{ + /* nothing */ +} + +static int +hvs_alloc_ccbs(struct hvs_softc *sc) +{ + int i, error; + + SIMPLEQ_INIT(&sc->sc_ccb_fq); + mutex_init(&sc->sc_ccb_fqlck, MUTEX_DEFAULT, IPL_BIO); + + sc->sc_nccb = HVS_MAX_CCB; + sc->sc_ccbs = kmem_zalloc(sc->sc_nccb * sizeof(struct hvs_ccb), + KM_SLEEP); + if (sc->sc_ccbs == NULL) { + aprint_error_dev(sc->sc_dev, "failed to allocate CCBs\n"); + 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_WAITOK, + &sc->sc_ccbs[i].ccb_dmap); + if (error) { + aprint_error_dev(sc->sc_dev, + "failed to create a CCB memory map (%d)\n", error); + goto errout; + } + + sc->sc_ccbs[i].ccb_sgl = kmem_zalloc( + sizeof(struct vmbus_gpa_range) * (HVS_MAX_SGE + 1), + KM_SLEEP); + if (sc->sc_ccbs[i].ccb_sgl == NULL) { + aprint_error_dev(sc->sc_dev, + "failed to allocate SGL array\n"); + goto errout; + } + + sc->sc_ccbs[i].ccb_rid = i; + hvs_put_ccb(sc, &sc->sc_ccbs[i]); + } + + return 0; + + errout: + hvs_free_ccbs(sc); + return -1; +} + +static 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); + + kmem_free(ccb->ccb_sgl, + sizeof(struct vmbus_gpa_range) * (HVS_MAX_SGE + 1)); + } + + kmem_free(sc->sc_ccbs, sc->sc_nccb * sizeof(struct hvs_ccb)); + sc->sc_ccbs = NULL; + sc->sc_nccb = 0; +} + +static struct hvs_ccb * +hvs_get_ccb(struct hvs_softc *sc) +{ + struct hvs_ccb *ccb; + + mutex_enter(&sc->sc_ccb_fqlck); + ccb = SIMPLEQ_FIRST(&sc->sc_ccb_fq); + if (ccb != NULL) + SIMPLEQ_REMOVE_HEAD(&sc->sc_ccb_fq, ccb_link); + mutex_exit(&sc->sc_ccb_fqlck); + + return ccb; +} + +static void +hvs_put_ccb(struct hvs_softc *sc, struct hvs_ccb *ccb) +{ + + ccb->ccb_cmd = NULL; + ccb->ccb_xfer = NULL; + ccb->ccb_done = NULL; + ccb->ccb_cookie = NULL; + ccb->ccb_nsge = 0; + + mutex_enter(&sc->sc_ccb_fqlck); + SIMPLEQ_INSERT_HEAD(&sc->sc_ccb_fq, ccb, ccb_link); + mutex_exit(&sc->sc_ccb_fqlck); +} -- 2.17.0