diff options
| author | nonaka <nonaka@NetBSD.org> | 2019-02-15 08:54:01 +0000 |
|---|---|---|
| committer | nonaka <nonaka@NetBSD.org> | 2019-02-15 08:54:01 +0000 |
| commit | afdb7e40e269f2837fdd19c847e29c394199cd11 (patch) | |
| tree | 6d8a68607667098c5299591fa91e81eefb53fb49 /sys/dev/hyperv | |
| parent | c8caf74aa9cfde718d636a3806ea428be4fb1391 (diff) | |
Added Microsoft Hyper-V support. It ported from OpenBSD and FreeBSD.
graphical console is not work on Gen.2 VM yet. To use the serial console,
enter "consdev com,0x3f8,115200" on efiboot.
Diffstat (limited to 'sys/dev/hyperv')
| -rw-r--r-- | sys/dev/hyperv/files.hyperv | 34 | ||||
| -rw-r--r-- | sys/dev/hyperv/hvheartbeat.c | 198 | ||||
| -rw-r--r-- | sys/dev/hyperv/hvs.c | 1139 | ||||
| -rw-r--r-- | sys/dev/hyperv/hvshutdown.c | 233 | ||||
| -rw-r--r-- | sys/dev/hyperv/hvtimesync.c | 338 | ||||
| -rw-r--r-- | sys/dev/hyperv/hyperv_common.c | 183 | ||||
| -rw-r--r-- | sys/dev/hyperv/hypervreg.h | 453 | ||||
| -rw-r--r-- | sys/dev/hyperv/hypervvar.h | 115 | ||||
| -rw-r--r-- | sys/dev/hyperv/if_hvn.c | 1849 | ||||
| -rw-r--r-- | sys/dev/hyperv/if_hvnreg.h | 204 | ||||
| -rw-r--r-- | sys/dev/hyperv/vmbus.c | 2112 | ||||
| -rw-r--r-- | sys/dev/hyperv/vmbusic.c | 271 | ||||
| -rw-r--r-- | sys/dev/hyperv/vmbusicreg.h | 248 | ||||
| -rw-r--r-- | sys/dev/hyperv/vmbusicvar.h | 68 | ||||
| -rw-r--r-- | sys/dev/hyperv/vmbusvar.h | 273 |
15 files changed, 7718 insertions, 0 deletions
diff --git a/sys/dev/hyperv/files.hyperv b/sys/dev/hyperv/files.hyperv new file mode 100644 index 00000000000..db3c8f96544 --- /dev/null +++ b/sys/dev/hyperv/files.hyperv @@ -0,0 +1,34 @@ +# $NetBSD: files.hyperv,v 1.1 2019/02/15 08:54:01 nonaka Exp $ + +define hypervvmbus {} +device vmbus: hypervvmbus +file dev/hyperv/vmbus.c vmbus needs-flag + +device hvn: ether, ifnet, arp +attach hvn at hypervvmbus +file dev/hyperv/if_hvn.c hvn + +device hvs: scsi +attach hvs at hypervvmbus +file dev/hyperv/hvs.c hvs + +device hvheartbeat +attach hvheartbeat at hypervvmbus +file dev/hyperv/hvheartbeat.c hvheartbeat + +device hvkvp +attach hvkvp at hypervvmbus +file dev/hyperv/hvkvp.c hvkvp + +device hvshutdown: sysmon_power, sysmon_taskq +attach hvshutdown at hypervvmbus +file dev/hyperv/hvshutdown.c hvshutdown + +device hvtimesync +attach hvtimesync at hypervvmbus +file dev/hyperv/hvtimesync.c hvtimesync + +file dev/hyperv/vmbusic.c hvheartbeat | hvkvp | hvshutdown | + hvtimesync + +file dev/hyperv/hyperv_common.c hyperv | vmbus diff --git a/sys/dev/hyperv/hvheartbeat.c b/sys/dev/hyperv/hvheartbeat.c new file mode 100644 index 00000000000..3b9c2620190 --- /dev/null +++ b/sys/dev/hyperv/hvheartbeat.c @@ -0,0 +1,198 @@ +/* $NetBSD: hvheartbeat.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ + +/*- + * Copyright (c) 2014,2016 Microsoft Corp. + * 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. + */ + +#include <sys/cdefs.h> +#ifdef __KERNEL_RCSID +__KERNEL_RCSID(0, "$NetBSD: hvheartbeat.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); +#endif +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_heartbeat.c 310324 2016-12-20 09:46:14Z sephe $"); +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/module.h> +#include <sys/pmf.h> + +#include <dev/hyperv/vmbusvar.h> +#include <dev/hyperv/vmbusicreg.h> +#include <dev/hyperv/vmbusicvar.h> + +#define VMBUS_HEARTBEAT_FWVER_MAJOR 3 +#define VMBUS_HEARTBEAT_FWVER \ + VMBUS_IC_VERSION(VMBUS_HEARTBEAT_FWVER_MAJOR, 0) + +#define VMBUS_HEARTBEAT_MSGVER_MAJOR 3 +#define VMBUS_HEARTBEAT_MSGVER \ + VMBUS_IC_VERSION(VMBUS_HEARTBEAT_MSGVER_MAJOR, 0) + +static int hvheartbeat_match(device_t, cfdata_t, void *); +static void hvheartbeat_attach(device_t, device_t, void *); +static int hvheartbeat_detach(device_t, int); + +static void hvheartbeat_channel_cb(void *); + +struct hvheartbeat_softc { + struct vmbusic_softc sc_vmbusic; +}; + +CFATTACH_DECL_NEW(hvheartbeat, sizeof(struct hvheartbeat_softc), + hvheartbeat_match, hvheartbeat_attach, hvheartbeat_detach, NULL); + +static int +hvheartbeat_match(device_t parent, cfdata_t cf, void *aux) +{ + struct vmbus_attach_args *aa = aux; + + return vmbusic_probe(aa, &hyperv_guid_heartbeat); +} + +static void +hvheartbeat_attach(device_t parent, device_t self, void *aux) +{ + struct vmbus_attach_args *aa = aux; + int error; + + aprint_naive("\n"); + aprint_normal(": Hyper-V Heartbeat\n"); + + error = vmbusic_attach(self, aa, hvheartbeat_channel_cb); + if (error) + return; + + (void) pmf_device_register(self, NULL, NULL); +} + +static int +hvheartbeat_detach(device_t self, int flags) +{ + int error; + + error = vmbusic_detach(self, flags); + if (error) + return error; + + pmf_device_deregister(self); + + return 0; +} + +static void +hvheartbeat_channel_cb(void *arg) +{ + struct hvheartbeat_softc *sc = arg; + struct vmbusic_softc *vsc = &sc->sc_vmbusic; + struct vmbus_channel *ch = vsc->sc_chan; + struct vmbus_icmsg_hdr *hdr; + struct vmbus_icmsg_heartbeat *msg; + uint64_t rid; + uint32_t rlen; + int error; + + error = vmbus_channel_recv(ch, vsc->sc_buf, vsc->sc_buflen, + &rlen, &rid, 0); + if (error || rlen == 0) { + if (error != EAGAIN) { + DPRINTF("%s: heartbeat error=%d len=%u\n", + device_xname(vsc->sc_dev), error, rlen); + } + return; + } + if (rlen < sizeof(*hdr)) { + DPRINTF("%s: heartbeat short read len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + hdr = (struct vmbus_icmsg_hdr *)vsc->sc_buf; + switch (hdr->ic_type) { + case VMBUS_ICMSG_TYPE_NEGOTIATE: + error = vmbusic_negotiate(vsc, hdr, &rlen, + VMBUS_HEARTBEAT_FWVER, VMBUS_HEARTBEAT_MSGVER); + if (error) + return; + break; + + case VMBUS_ICMSG_TYPE_HEARTBEAT: + if (rlen < VMBUS_ICMSG_HEARTBEAT_SIZE_MIN) { + DPRINTF("%s: invalid heartbeat len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + msg = (struct vmbus_icmsg_heartbeat *)hdr; + msg->ic_seq++; + break; + + default: + device_printf(vsc->sc_dev, + "unhandled heartbeat message type %u\n", hdr->ic_type); + return; + } + + (void) vmbusic_sendresp(vsc, ch, vsc->sc_buf, rlen, rid); +} + +MODULE(MODULE_CLASS_DRIVER, hvheartbeat, "vmbus"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +hvheartbeat_modcmd(modcmd_t cmd, void *aux) +{ + int error = 0; + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + error = config_init_component(cfdriver_ioconf_hvheartbeat, + cfattach_ioconf_hvheartbeat, cfdata_ioconf_hvheartbeat); +#endif + break; + + case MODULE_CMD_FINI: +#ifdef _MODULE + error = config_fini_component(cfdriver_ioconf_hvheartbeat, + cfattach_ioconf_hvheartbeat, cfdata_ioconf_hvheartbeat); +#endif + break; + + case MODULE_CMD_AUTOUNLOAD: + error = EBUSY; + break; + + default: + error = ENOTTY; + break; + } + + return error; +} diff --git a/sys/dev/hyperv/hvs.c b/sys/dev/hyperv/hvs.c new file mode 100644 index 00000000000..2bce33458ec --- /dev/null +++ b/sys/dev/hyperv/hvs.c @@ -0,0 +1,1139 @@ +/* $NetBSD: hvs.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ +/* $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 <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. + */ + +/* #define HVS_DEBUG_IO */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: hvs.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/buf.h> +#include <sys/bus.h> +#include <sys/kmem.h> +#include <sys/mutex.h> + +#include <uvm/uvm_extern.h> + +#include <dev/hyperv/vmbusvar.h> + +#include <dev/scsipi/scsi_all.h> +#include <dev/scsipi/scsiconf.h> +#include <dev/scsipi/scsipi_all.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 +#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) +{ + extern struct cfdata cfdata[]; + 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; + + 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) or piixide(4) causing DUID conflicts + * and preventing system from booting. + */ + if (!is_scsi && sc->sc_scsibus != NULL) { + static const char *disable_devices[] = { + "wd", + }; + size_t j; + + for (j = 0; j < __arraycount(disable_devices); j++) { + const char *dev = disable_devices[j]; + size_t len = strlen(dev); + int devno; + + for (devno = 0; cfdata[devno].cf_name != NULL; devno++) { + cfdata_t cf = &cfdata[devno]; + + if (strlen(cf->cf_name) != len || + strncasecmp(dev, cf->cf_name, len) != 0 || + cf->cf_fstate != FSTATE_STAR) + continue; + + cf->cf_fstate = FSTATE_DSTAR; + } + } + } +} + +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: + device_printf(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) { + device_printf(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) { + device_printf(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) { + device_printf(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) { + device_printf(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) + device_printf(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: + device_printf(sc->sc_dev, + "error %d while receiving a reply\n", error); + return; + } + if (rlen != sizeof(cmd)) { + device_printf(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) { + device_printf(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: + device_printf(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: + device_printf(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) +{ + const int kmemflags = cold ? KM_NOSLEEP : KM_SLEEP; + const int dmaflags = cold ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK; + 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), + kmemflags); + 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, dmaflags, &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), + kmemflags); + 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); +} diff --git a/sys/dev/hyperv/hvshutdown.c b/sys/dev/hyperv/hvshutdown.c new file mode 100644 index 00000000000..3f05d5aea20 --- /dev/null +++ b/sys/dev/hyperv/hvshutdown.c @@ -0,0 +1,233 @@ +/* $NetBSD: hvshutdown.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ + +/*- + * Copyright (c) 2014,2016 Microsoft Corp. + * 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. + */ + +#include <sys/cdefs.h> +#ifdef __KERNEL_RCSID +__KERNEL_RCSID(0, "$NetBSD: hvshutdown.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); +#endif +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_shutdown.c 310324 2016-12-20 09:46:14Z sephe $"); +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/module.h> +#include <sys/pmf.h> + +#include <dev/sysmon/sysmonvar.h> +#include <dev/sysmon/sysmon_taskq.h> + +#include <dev/hyperv/vmbusvar.h> +#include <dev/hyperv/vmbusicreg.h> +#include <dev/hyperv/vmbusicvar.h> + +#define VMBUS_SHUTDOWN_FWVER_MAJOR 3 +#define VMBUS_SHUTDOWN_FWVER \ + VMBUS_IC_VERSION(VMBUS_SHUTDOWN_FWVER_MAJOR, 0) + +#define VMBUS_SHUTDOWN_MSGVER_MAJOR 3 +#define VMBUS_SHUTDOWN_MSGVER \ + VMBUS_IC_VERSION(VMBUS_SHUTDOWN_MSGVER_MAJOR, 0) + +static int hvshutdown_match(device_t, cfdata_t, void *); +static void hvshutdown_attach(device_t, device_t, void *); +static int hvshutdown_detach(device_t, int); + +static void hvshutdown_channel_cb(void *); + +struct hvshutdown_softc { + struct vmbusic_softc sc_vmbusic; + + struct sysmon_pswitch sc_smpsw; +}; + +CFATTACH_DECL_NEW(hvshutdown, sizeof(struct hvshutdown_softc), + hvshutdown_match, hvshutdown_attach, hvshutdown_detach, NULL); + +static int +hvshutdown_match(device_t parent, cfdata_t cf, void *aux) +{ + struct vmbus_attach_args *aa = aux; + + return vmbusic_probe(aa, &hyperv_guid_shutdown); +} + +static void +hvshutdown_attach(device_t parent, device_t self, void *aux) +{ + struct hvshutdown_softc *sc = device_private(self); + struct vmbus_attach_args *aa = aux; + int error; + + aprint_naive("\n"); + aprint_normal(": Hyper-V Shutdown\n"); + + error = vmbusic_attach(self, aa, hvshutdown_channel_cb); + if (error) + return; + + (void) pmf_device_register(self, NULL, NULL); + + sysmon_task_queue_init(); + + sc->sc_smpsw.smpsw_name = device_xname(self); + sc->sc_smpsw.smpsw_type = PSWITCH_TYPE_POWER; + (void) sysmon_pswitch_register(&sc->sc_smpsw); +} + +static int +hvshutdown_detach(device_t self, int flags) +{ + struct hvshutdown_softc *sc = device_private(self); + int error; + + error = vmbusic_detach(self, flags); + if (error) + return error; + + pmf_device_deregister(self); + sysmon_pswitch_unregister(&sc->sc_smpsw); + + return 0; +} + +static void +hvshutdown_do_shutdown(void *arg) +{ + struct hvshutdown_softc *sc = arg; + + sysmon_pswitch_event(&sc->sc_smpsw, PSWITCH_EVENT_PRESSED); +} + +static void +hvshutdown_channel_cb(void *arg) +{ + struct hvshutdown_softc *sc = arg; + struct vmbusic_softc *vsc = &sc->sc_vmbusic; + struct vmbus_channel *ch = vsc->sc_chan; + struct vmbus_icmsg_hdr *hdr; + struct vmbus_icmsg_shutdown *msg; + uint64_t rid; + uint32_t rlen; + int error; + bool do_shutdown = false; + + error = vmbus_channel_recv(ch, vsc->sc_buf, vsc->sc_buflen, + &rlen, &rid, 0); + if (error || rlen == 0) { + if (error != EAGAIN) { + DPRINTF("%s: shutdown error=%d len=%u\n", + device_xname(vsc->sc_dev), error, rlen); + } + return; + } + if (rlen < sizeof(*hdr)) { + DPRINTF("%s: shutdown short read len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + hdr = (struct vmbus_icmsg_hdr *)vsc->sc_buf; + switch (hdr->ic_type) { + case VMBUS_ICMSG_TYPE_NEGOTIATE: + error = vmbusic_negotiate(vsc, hdr, &rlen, VMBUS_SHUTDOWN_FWVER, + VMBUS_SHUTDOWN_MSGVER); + if (error) + return; + break; + + case VMBUS_ICMSG_TYPE_SHUTDOWN: + if (rlen < VMBUS_ICMSG_SHUTDOWN_SIZE_MIN) { + DPRINTF("%s: invalid shutdown len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + msg = (struct vmbus_icmsg_shutdown *)hdr; + if (msg->ic_haltflags == 0 || msg->ic_haltflags == 1) { + device_printf(vsc->sc_dev, "shutdown requested\n"); + hdr->ic_status = VMBUS_ICMSG_STATUS_OK; + do_shutdown = true; + } else { + device_printf(vsc->sc_dev, + "unknown shutdown flags 0x%08x\n", + msg->ic_haltflags); + hdr->ic_status = VMBUS_ICMSG_STATUS_FAIL; + } + break; + + default: + device_printf(vsc->sc_dev, + "unhandled shutdown message type %u\n", hdr->ic_type); + return; + } + + (void) vmbusic_sendresp(vsc, ch, vsc->sc_buf, rlen, rid); + + if (do_shutdown) + sysmon_task_queue_sched(0, hvshutdown_do_shutdown, sc); +} + +MODULE(MODULE_CLASS_DRIVER, hvshutdown, "vmbus"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +hvshutdown_modcmd(modcmd_t cmd, void *aux) +{ + int error = 0; + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + error = config_init_component(cfdriver_ioconf_hvshutdown, + cfattach_ioconf_hvshutdown, cfdata_ioconf_hvshutdown); +#endif + break; + + case MODULE_CMD_FINI: +#ifdef _MODULE + error = config_fini_component(cfdriver_ioconf_hvshutdown, + cfattach_ioconf_hvshutdown, cfdata_ioconf_hvshutdown); +#endif + break; + + case MODULE_CMD_AUTOUNLOAD: + error = EBUSY; + break; + + default: + error = ENOTTY; + break; + } + + return error; +} diff --git a/sys/dev/hyperv/hvtimesync.c b/sys/dev/hyperv/hvtimesync.c new file mode 100644 index 00000000000..ca3dc7c6470 --- /dev/null +++ b/sys/dev/hyperv/hvtimesync.c @@ -0,0 +1,338 @@ +/* $NetBSD: hvtimesync.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ + +/*- + * Copyright (c) 2014,2016-2017 Microsoft Corp. + * 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. + */ + +#include <sys/cdefs.h> +#ifdef __KERNEL_RCSID +__KERNEL_RCSID(0, "$NetBSD: hvtimesync.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); +#endif +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_timesync.c 322488 2017-08-14 06:00:50Z sephe $"); +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/module.h> +#include <sys/pmf.h> +#include <sys/sysctl.h> +#include <sys/timetc.h> + +#include <dev/hyperv/vmbusvar.h> +#include <dev/hyperv/vmbusicreg.h> +#include <dev/hyperv/vmbusicvar.h> + +#define VMBUS_TIMESYNC_FWVER_MAJOR 3 +#define VMBUS_TIMESYNC_FWVER \ + VMBUS_IC_VERSION(VMBUS_TIMESYNC_FWVER_MAJOR, 0) + +#define VMBUS_TIMESYNC_MSGVER_MAJOR 4 +#define VMBUS_TIMESYNC_MSGVER \ + VMBUS_IC_VERSION(VMBUS_TIMESYNC_MSGVER_MAJOR, 0) + +#define VMBUS_TIMESYNC_MSGVER4(sc) \ + VMBUS_ICVER_LE(VMBUS_IC_VERSION(4, 0), (sc)->sc_vmbusic.sc_msgver) + +#define VMBUS_TIMESYNC_DORTT(sc) \ + (VMBUS_TIMESYNC_MSGVER4((sc)) && (hyperv_tc64 != NULL)) + +static int hvtimesync_match(device_t, cfdata_t, void *); +static void hvtimesync_attach(device_t, device_t, void *); +static int hvtimesync_detach(device_t, int); + +static void hvtimesync_channel_cb(void *); +static int hvtimesync_sysctl_setup(device_t); + +struct hvtimesync_softc { + struct vmbusic_softc sc_vmbusic; +}; + +CFATTACH_DECL_NEW(hvtimesync, sizeof(struct hvtimesync_softc), + hvtimesync_match, hvtimesync_attach, hvtimesync_detach, NULL); + +static int hvtimesync_ignore_sync; +static int hvtimesnyc_sample_verbose; +static int hvtimesync_sample_thresh = -1; + +static int +hvtimesync_match(device_t parent, cfdata_t cf, void *aux) +{ + struct vmbus_attach_args *aa = aux; + + return vmbusic_probe(aa, &hyperv_guid_timesync); +} + +static void +hvtimesync_attach(device_t parent, device_t self, void *aux) +{ + struct vmbus_attach_args *aa = aux; + int error; + + aprint_naive("\n"); + aprint_normal(": Hyper-V Timesync\n"); + + error = vmbusic_attach(self, aa, hvtimesync_channel_cb); + if (error) + return; + + (void) pmf_device_register(self, NULL, NULL); + + (void) hvtimesync_sysctl_setup(self); +} + +static int +hvtimesync_detach(device_t self, int flags) +{ + int error; + + error = vmbusic_detach(self, flags); + if (error) + return error; + + pmf_device_deregister(self); + + return 0; +} + +static void +do_timesync(struct hvtimesync_softc *sc, uint64_t hvtime, uint64_t sent_tc, + uint8_t tsflags) +{ + struct vmbusic_softc *vsc = &sc->sc_vmbusic; + struct timespec vm_ts, hv_ts; + uint64_t hv_ns, vm_ns, rtt = 0; + int64_t diff; + + if (VMBUS_TIMESYNC_DORTT(sc)) + rtt = hyperv_tc64() - sent_tc; + + hv_ns = (hvtime - VMBUS_ICMSG_TS_BASE + rtt) * HYPERV_TIMER_NS_FACTOR; + nanotime(&vm_ts); + vm_ns = (vm_ts.tv_sec * NANOSECOND) + vm_ts.tv_nsec; + + if ((tsflags & VMBUS_ICMSG_TS_FLAG_SYNC) && !hvtimesync_ignore_sync) { +#if 0 + device_printf(vsc->sc_dev, + "apply sync request, hv: %ju, vm: %ju\n", + (uintmax_t)hv_ns, (uintmax_t)vm_ns); +#endif + hv_ts.tv_sec = hv_ns / NANOSECOND; + hv_ts.tv_nsec = hv_ns % NANOSECOND; + tc_setclock(&hv_ts); + /* Done! */ + return; + } + + if ((tsflags & VMBUS_ICMSG_TS_FLAG_SAMPLE) && + hvtimesync_sample_thresh >= 0) { + if (hvtimesnyc_sample_verbose) { + device_printf(vsc->sc_dev, + "sample request, hv: %ju, vm: %ju\n", + (uintmax_t)hv_ns, (uintmax_t)vm_ns); + } + + if (hv_ns > vm_ns) + diff = hv_ns - vm_ns; + else + diff = vm_ns - hv_ns; + /* nanosec -> millisec */ + diff /= 1000000; + + if (diff > hvtimesync_sample_thresh) { + device_printf(vsc->sc_dev, + "apply sample request, hv: %ju, vm: %ju\n", + (uintmax_t)hv_ns, (uintmax_t)vm_ns); + hv_ts.tv_sec = hv_ns / NANOSECOND; + hv_ts.tv_nsec = hv_ns % NANOSECOND; + tc_setclock(&hv_ts); + } + /* Done */ + return; + } +} + +static void +hvtimesync_channel_cb(void *arg) +{ + struct hvtimesync_softc *sc = arg; + struct vmbusic_softc *vsc = &sc->sc_vmbusic; + struct vmbus_channel *ch = vsc->sc_chan; + struct vmbus_icmsg_hdr *hdr; + uint64_t rid; + uint32_t rlen; + int error; + + error = vmbus_channel_recv(ch, vsc->sc_buf, vsc->sc_buflen, + &rlen, &rid, 0); + if (error || rlen == 0) { + if (error != EAGAIN) { + DPRINTF("%s: timesync error=%d len=%u\n", + device_xname(vsc->sc_dev), error, rlen); + } + return; + } + if (rlen < sizeof(*hdr)) { + DPRINTF("%s: hvtimesync short read len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + hdr = (struct vmbus_icmsg_hdr *)vsc->sc_buf; + switch (hdr->ic_type) { + case VMBUS_ICMSG_TYPE_NEGOTIATE: + error = vmbusic_negotiate(vsc, hdr, &rlen, VMBUS_TIMESYNC_FWVER, + VMBUS_TIMESYNC_MSGVER); + if (error) + return; + if (VMBUS_TIMESYNC_DORTT(sc)) { + DPRINTF("%s: RTT\n", device_xname(vsc->sc_dev)); + } + break; + + case VMBUS_ICMSG_TYPE_TIMESYNC: + if (VMBUS_TIMESYNC_MSGVER4(sc)) { + struct vmbus_icmsg_timesync4 *msg4; + + if (rlen < sizeof(*msg4)) { + DPRINTF("%s: invalid timesync4 len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + msg4 = (struct vmbus_icmsg_timesync4 *)hdr; + do_timesync(sc, msg4->ic_hvtime, msg4->ic_sent_tc, + msg4->ic_tsflags); + } else { + struct vmbus_icmsg_timesync *msg; + + if (rlen < sizeof(*msg)) { + DPRINTF("%s: invalid timesync len=%u\n", + device_xname(vsc->sc_dev), rlen); + return; + } + + msg = (struct vmbus_icmsg_timesync *)hdr; + do_timesync(sc, msg->ic_hvtime, 0, msg->ic_tsflags); + } + break; + + default: + device_printf(vsc->sc_dev, + "unhandled _timesync message type %u\n", hdr->ic_type); + return; + } + + (void) vmbusic_sendresp(vsc, ch, vsc->sc_buf, rlen, rid); +} + +static int +hvtimesync_sysctl_setup(device_t self) +{ + struct hvtimesync_softc *sc = device_private(self); + struct vmbusic_softc *vsc = &sc->sc_vmbusic; + const struct sysctlnode *node; + int error; + + error = sysctl_createv(NULL, 0, NULL, &node, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, + NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); + if (error) + return error; + error = sysctl_createv(NULL, 0, &node, &node, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "hyperv", NULL, + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (error) + return error; + + error = sysctl_createv(&vsc->sc_log, 0, &node, &node, + 0, CTLTYPE_NODE, "timesync", NULL, + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (error) + return error; + + error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, + CTLFLAG_READWRITE, + CTLTYPE_INT, "ignore_sync", NULL, + NULL, 0, &hvtimesync_ignore_sync, 0, + CTL_CREATE, CTL_EOL); + if (error) + return error; + error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, + CTLFLAG_READWRITE, + CTLTYPE_INT, "sample_verbose", NULL, + NULL, 0, &hvtimesnyc_sample_verbose, 0, + CTL_CREATE, CTL_EOL); + if (error) + return error; + error = sysctl_createv(&vsc->sc_log, 0, &node, NULL, + CTLFLAG_READWRITE, + CTLTYPE_INT, "sample_thresh", NULL, + NULL, 0, &hvtimesync_sample_thresh, 0, + CTL_CREATE, CTL_EOL); + if (error) + return error; + + return 0; +} + +MODULE(MODULE_CLASS_DRIVER, hvtimesync, "vmbus"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +hvtimesync_modcmd(modcmd_t cmd, void *aux) +{ + int error = 0; + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + error = config_init_component(cfdriver_ioconf_hvtimesync, + cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync); +#endif + break; + + case MODULE_CMD_FINI: +#ifdef _MODULE + error = config_fini_component(cfdriver_ioconf_hvtimesync, + cfattach_ioconf_hvtimesync, cfdata_ioconf_hvtimesync); +#endif + break; + + case MODULE_CMD_AUTOUNLOAD: + error = EBUSY; + break; + + default: + error = ENOTTY; + break; + } + + return error; +} diff --git a/sys/dev/hyperv/hyperv_common.c b/sys/dev/hyperv/hyperv_common.c new file mode 100644 index 00000000000..438f9d20abc --- /dev/null +++ b/sys/dev/hyperv/hyperv_common.c @@ -0,0 +1,183 @@ +/* $NetBSD: hyperv_common.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ + +/*- + * Copyright (c) 2009-2012,2016-2017 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * 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. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: hyperv_common.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); + +#include "hyperv.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/kmem.h> + +#include <dev/hyperv/hypervreg.h> +#include <dev/hyperv/hypervvar.h> + +hyperv_tc64_t hyperv_tc64; + +int hyperv_nullop(void); +void hyperv_voidop(void); +uint64_t hyperv_hypercall_error(uint64_t, paddr_t, paddr_t); + +__weak_alias(hyperv_hypercall, hyperv_hypercall_error); +__weak_alias(hyperv_hypercall_enabled, hyperv_nullop); +__weak_alias(hyperv_synic_supported, hyperv_nullop); +__weak_alias(hyperv_set_event_proc, hyperv_voidop); +__weak_alias(hyperv_set_message_proc, hyperv_voidop); +__weak_alias(hyperv_send_eom, hyperv_voidop); +__weak_alias(hyperv_intr, hyperv_voidop); +__weak_alias(vmbus_init_interrupts_md, hyperv_voidop); +__weak_alias(vmbus_deinit_interrupts_md, hyperv_voidop); +__weak_alias(vmbus_init_synic_md, hyperv_voidop); +__weak_alias(vmbus_deinit_synic_md, hyperv_voidop); + +int +hyperv_nullop(void) +{ + return 0; +} + +void +hyperv_voidop(void) +{ +} + +uint64_t +hyperv_hypercall_error(uint64_t control, paddr_t in_paddr, paddr_t out_paddr) +{ + return ~HYPERCALL_STATUS_SUCCESS; +} + +uint64_t +hyperv_hypercall_post_message(paddr_t msg) +{ + + return hyperv_hypercall(HYPERCALL_POST_MESSAGE, msg, 0); +} + +uint64_t +hyperv_hypercall_signal_event(paddr_t monprm) +{ + + return hyperv_hypercall(HYPERCALL_SIGNAL_EVENT, monprm, 0); +} + +int +hyperv_guid2str(const struct hyperv_guid *guid, char *buf, size_t sz) +{ + const uint8_t *d = guid->hv_guid; + + return snprintf(buf, sz, "%02x%02x%02x%02x-" + "%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x", + d[3], d[2], d[1], d[0], + d[5], d[4], d[7], d[6], d[8], d[9], + d[10], d[11], d[12], d[13], d[14], d[15]); +} + +/* + * Hyper-V bus_dma utilities. + */ +void * +hyperv_dma_alloc(bus_dma_tag_t dmat, struct hyperv_dma *dma, bus_size_t size, + bus_size_t alignment, bus_size_t boundary, int nsegs) +{ + const int kmemflags = cold ? KM_NOSLEEP : KM_SLEEP; + const int dmaflags = cold ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK; + int rseg, error; + + KASSERT(dma != NULL); + KASSERT(dma->segs == NULL); + KASSERT(nsegs > 0); + + dma->segs = kmem_zalloc(sizeof(*dma->segs) * nsegs, kmemflags); + if (dma->segs == NULL) + return NULL; + + dma->nsegs = nsegs; + + error = bus_dmamem_alloc(dmat, size, alignment, boundary, dma->segs, + nsegs, &rseg, dmaflags); + if (error) { + printf("%s: bus_dmamem_alloc failed: error=%d\n", + __func__, error); + goto fail1; + } + error = bus_dmamem_map(dmat, dma->segs, rseg, size, &dma->addr, + dmaflags); + if (error) { + printf("%s: bus_dmamem_map failed: error=%d\n", + __func__, error); + goto fail2; + } + error = bus_dmamap_create(dmat, size, rseg, size, boundary, dmaflags, + &dma->map); + if (error) { + printf("%s: bus_dmamap_create failed: error=%d\n", + __func__, error); + goto fail3; + } + error = bus_dmamap_load(dmat, dma->map, dma->addr, size, NULL, + BUS_DMA_READ | BUS_DMA_WRITE | dmaflags); + if (error) { + printf("%s: bus_dmamap_load failed: error=%d\n", + __func__, error); + goto fail4; + } + + return dma->addr; + +fail4: bus_dmamap_destroy(dmat, dma->map); +fail3: bus_dmamem_unmap(dmat, dma->addr, size); + dma->addr = NULL; +fail2: bus_dmamem_free(dmat, dma->segs, rseg); +fail1: kmem_free(dma->segs, sizeof(*dma->segs) * nsegs); + dma->segs = NULL; + dma->nsegs = 0; + return NULL; +} + +void +hyperv_dma_free(bus_dma_tag_t dmat, struct hyperv_dma *dma) +{ + bus_size_t size = dma->map->dm_mapsize; + int rsegs = dma->map->dm_nsegs; + + bus_dmamap_unload(dmat, dma->map); + bus_dmamap_destroy(dmat, dma->map); + bus_dmamem_unmap(dmat, dma->addr, size); + dma->addr = NULL; + bus_dmamem_free(dmat, dma->segs, rsegs); + kmem_free(dma->segs, sizeof(*dma->segs) * dma->nsegs); + dma->segs = NULL; + dma->nsegs = 0; +} diff --git a/sys/dev/hyperv/hypervreg.h b/sys/dev/hyperv/hypervreg.h new file mode 100644 index 00000000000..68d10160239 --- /dev/null +++ b/sys/dev/hyperv/hypervreg.h @@ -0,0 +1,453 @@ +/* $NetBSD: hypervreg.h,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ +/* $OpenBSD: hypervreg.h,v 1.10 2017/01/05 13:17:22 mikeb Exp $ */ + +/*- + * Copyright (c) 2009-2012,2016 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * 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. + */ + +#ifndef _HYPERVREG_H_ +#define _HYPERVREG_H_ + +#if defined(_KERNEL) + +#define VMBUS_CONNID_MESSAGE 1 +#define VMBUS_CONNID_EVENT 2 +#define VMBUS_SINT_MESSAGE 2 +#define VMBUS_SINT_TIMER 4 + +struct hyperv_guid { + uint8_t hv_guid[16]; +} __packed; + +/* + * $FreeBSD: head/sys/dev/hyperv/vmbus/hyperv_reg.h 303283 2016-07-25 03:12:40Z sephe $ + */ + +/* + * Hyper-V Monitor Notification Facility + */ +struct hyperv_mon_param { + uint32_t mp_connid; + uint16_t mp_evtflag_ofs; + uint16_t mp_rsvd; +} __packed; + +/* + * Hyper-V message types + */ +#define HYPERV_MSGTYPE_NONE 0 +#define HYPERV_MSGTYPE_CHANNEL 1 +#define HYPERV_MSGTYPE_TIMER_EXPIRED 0x80000010 + +/* + * Hypercall status codes + */ +#define HYPERCALL_STATUS_SUCCESS 0x0000 + +/* + * Hypercall input values + */ +#define HYPERCALL_POST_MESSAGE 0x005c +#define HYPERCALL_SIGNAL_EVENT 0x005d + +/* + * Hypercall input parameters + */ +#define HYPERCALL_PARAM_ALIGN 8 +#if 0 +/* + * XXX + * <<Hypervisor Top Level Functional Specification 4.0b>> requires + * input parameters size to be multiple of 8, however, many post + * message input parameters do _not_ meet this requirement. + */ +#define HYPERCALL_PARAM_SIZE_ALIGN 8 +#endif + +/* + * HYPERCALL_POST_MESSAGE + */ +#define HYPERCALL_POSTMSGIN_DSIZE_MAX 240 +#define HYPERCALL_POSTMSGIN_SIZE 256 + +struct hyperv_hypercall_postmsg_in { + uint32_t hc_connid; + uint32_t hc_rsvd; + uint32_t hc_msgtype; /* VMBUS_MSGTYPE_ */ + uint32_t hc_dsize; + uint8_t hc_data[HYPERCALL_POSTMSGIN_DSIZE_MAX]; +} __packed; +__CTASSERT(sizeof(struct hyperv_hypercall_postmsg_in) == HYPERCALL_POSTMSGIN_SIZE); + +/* + * $FreeBSD: head/sys/dev/hyperv/include/vmbus.h 306389 2016-09-28 04:25:25Z sephe $ + */ + +/* + * VMBUS version is 32 bit, upper 16 bit for major_number and lower + * 16 bit for minor_number. + * + * 0.13 -- Windows Server 2008 + * 1.1 -- Windows 7 + * 2.4 -- Windows 8 + * 3.0 -- Windows 8.1 + * 4.0 -- Windows 10 + */ +#define VMBUS_VERSION_WS2008 ((0 << 16) | (13)) +#define VMBUS_VERSION_WIN7 ((1 << 16) | (1)) +#define VMBUS_VERSION_WIN8 ((2 << 16) | (4)) +#define VMBUS_VERSION_WIN8_1 ((3 << 16) | (0)) +#define VMBUS_VERSION_WIN10 ((4 << 16) | (0)) + +#define VMBUS_VERSION_MAJOR(ver) (((uint32_t)(ver)) >> 16) +#define VMBUS_VERSION_MINOR(ver) (((uint32_t)(ver)) & 0xffff) + +/* + * GPA stuffs. + */ +struct vmbus_gpa_range { + uint32_t gpa_len; + uint32_t gpa_ofs; + uint64_t gpa_page[0]; +} __packed; + +/* This is actually vmbus_gpa_range.gpa_page[1] */ +struct vmbus_gpa { + uint32_t gpa_len; + uint32_t gpa_ofs; + uint64_t gpa_page; +} __packed; + +#define VMBUS_CHANPKT_SIZE_SHIFT 3 + +#define VMBUS_CHANPKT_GETLEN(pktlen) \ + (((int)(pktlen)) << VMBUS_CHANPKT_SIZE_SHIFT) + +struct vmbus_chanpkt_hdr { + uint16_t cph_type; /* VMBUS_CHANPKT_TYPE_ */ + uint16_t cph_hlen; /* header len, in 8 bytes */ + uint16_t cph_tlen; /* total len, in 8 bytes */ + uint16_t cph_flags; /* VMBUS_CHANPKT_FLAG_ */ + uint64_t cph_tid; +} __packed; + +#define VMBUS_CHANPKT_TYPE_INBAND 0x0006 +#define VMBUS_CHANPKT_TYPE_RXBUF 0x0007 +#define VMBUS_CHANPKT_TYPE_GPA 0x0009 +#define VMBUS_CHANPKT_TYPE_COMP 0x000b + +#define VMBUS_CHANPKT_FLAG_RC 0x0001 /* report completion */ + +#define VMBUS_CHANPKT_CONST_DATA(pkt) \ + ((const void *)((const uint8_t *)(pkt) + \ + VMBUS_CHANPKT_GETLEN((pkt)->cph_hlen))) + +/* + * $FreeBSD: head/sys/dev/hyperv/vmbus/vmbus_reg.h 305405 2016-09-05 03:21:31Z sephe $ + */ + +/* + * Hyper-V SynIC message format. + */ + +#define VMBUS_MSG_DSIZE_MAX 240 +#define VMBUS_MSG_SIZE 256 + +struct vmbus_message { + uint32_t msg_type; /* VMBUS_MSGTYPE_ */ + uint8_t msg_dsize; /* data size */ + uint8_t msg_flags; /* VMBUS_MSGFLAG_ */ + uint16_t msg_rsvd; + uint64_t msg_id; + uint8_t msg_data[VMBUS_MSG_DSIZE_MAX]; +} __packed; + +#define VMBUS_MSGFLAG_PENDING 0x01 + +/* + * Hyper-V SynIC event flags + */ + +#define VMBUS_EVTFLAGS_SIZE 256 +#define VMBUS_EVTFLAGS_MAX ((VMBUS_EVTFLAGS_SIZE / LONG_BIT) * 8) +#define VMBUS_EVTFLAG_LEN LONG_BIT +#define VMBUS_EVTFLAG_MASK (LONG_BIT - 1) + +struct vmbus_evtflags { + ulong evt_flags[VMBUS_EVTFLAGS_MAX]; +} __packed; + +/* + * Hyper-V Monitor Notification Facility + */ + +struct vmbus_mon_trig { + uint32_t mt_pending; + uint32_t mt_armed; +} __packed; + +#define VMBUS_MONTRIGS_MAX 4 +#define VMBUS_MONTRIG_LEN 32 + +struct vmbus_mnf { + uint32_t mnf_state; + uint32_t mnf_rsvd1; + + struct vmbus_mon_trig + mnf_trigs[VMBUS_MONTRIGS_MAX]; + uint8_t mnf_rsvd2[536]; + + uint16_t mnf_lat[VMBUS_MONTRIGS_MAX][VMBUS_MONTRIG_LEN]; + uint8_t mnf_rsvd3[256]; + + struct hyperv_mon_param + mnf_param[VMBUS_MONTRIGS_MAX][VMBUS_MONTRIG_LEN]; + uint8_t mnf_rsvd4[1984]; +} __packed; + +/* + * Buffer ring + */ +struct vmbus_bufring { + /* + * If br_windex == br_rindex, this bufring is empty; this + * means we can _not_ write data to the bufring, if the + * write is going to make br_windex same as br_rindex. + */ + volatile uint32_t br_windex; + volatile uint32_t br_rindex; + + /* + * Interrupt mask {0,1} + * + * For TX bufring, host set this to 1, when it is processing + * the TX bufring, so that we can safely skip the TX event + * notification to host. + * + * For RX bufring, once this is set to 1 by us, host will not + * further dispatch interrupts to us, even if there are data + * pending on the RX bufring. This effectively disables the + * interrupt of the channel to which this RX bufring is attached. + */ + volatile uint32_t br_imask; + + uint8_t br_rsvd[4084]; + uint8_t br_data[0]; +} __packed; + +/* + * Channel + */ + +#define VMBUS_CHAN_MAX_COMPAT 256 +#define VMBUS_CHAN_MAX (VMBUS_EVTFLAG_LEN * VMBUS_EVTFLAGS_MAX) + +/* + * Channel packets + */ + +#define VMBUS_CHANPKT_SIZE_ALIGN (1 << VMBUS_CHANPKT_SIZE_SHIFT) + +#define VMBUS_CHANPKT_SETLEN(pktlen, len) \ +do { \ + (pktlen) = (len) >> VMBUS_CHANPKT_SIZE_SHIFT; \ +} while (0) + +struct vmbus_chanpkt { + struct vmbus_chanpkt_hdr cp_hdr; +} __packed; + +struct vmbus_chanpkt_sglist { + struct vmbus_chanpkt_hdr cp_hdr; + uint32_t cp_rsvd; + uint32_t cp_gpa_cnt; + struct vmbus_gpa cp_gpa[0]; +} __packed; + +struct vmbus_chanpkt_prplist { + struct vmbus_chanpkt_hdr cp_hdr; + uint32_t cp_rsvd; + uint32_t cp_range_cnt; + struct vmbus_gpa_range cp_range[0]; +} __packed; + +/* + * Channel messages + * - Embedded in vmbus_message.msg_data, e.g. response and notification. + * - Embedded in hyperv_hypercall_postmsg_in.hc_data, e.g. request. + */ + +#define VMBUS_CHANMSG_CHOFFER 1 /* NOTE */ +#define VMBUS_CHANMSG_CHRESCIND 2 /* NOTE */ +#define VMBUS_CHANMSG_CHREQUEST 3 /* REQ */ +#define VMBUS_CHANMSG_CHOFFER_DONE 4 /* NOTE */ +#define VMBUS_CHANMSG_CHOPEN 5 /* REQ */ +#define VMBUS_CHANMSG_CHOPEN_RESP 6 /* RESP */ +#define VMBUS_CHANMSG_CHCLOSE 7 /* REQ */ +#define VMBUS_CHANMSG_GPADL_CONN 8 /* REQ */ +#define VMBUS_CHANMSG_GPADL_SUBCONN 9 /* REQ */ +#define VMBUS_CHANMSG_GPADL_CONNRESP 10 /* RESP */ +#define VMBUS_CHANMSG_GPADL_DISCONN 11 /* REQ */ +#define VMBUS_CHANMSG_GPADL_DISCONNRESP 12 /* RESP */ +#define VMBUS_CHANMSG_CHFREE 13 /* REQ */ +#define VMBUS_CHANMSG_CONNECT 14 /* REQ */ +#define VMBUS_CHANMSG_CONNECT_RESP 15 /* RESP */ +#define VMBUS_CHANMSG_DISCONNECT 16 /* REQ */ +#define VMBUS_CHANMSG_COUNT 17 +#define VMBUS_CHANMSG_MAX 22 + +struct vmbus_chanmsg_hdr { + uint32_t chm_type; /* VMBUS_CHANMSG_* */ + uint32_t chm_rsvd; +} __packed; + +/* VMBUS_CHANMSG_CONNECT */ +struct vmbus_chanmsg_connect { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_ver; + uint32_t chm_rsvd; + uint64_t chm_evtflags; + uint64_t chm_mnf1; + uint64_t chm_mnf2; +} __packed; + +/* VMBUS_CHANMSG_CONNECT_RESP */ +struct vmbus_chanmsg_connect_resp { + struct vmbus_chanmsg_hdr chm_hdr; + uint8_t chm_done; +} __packed; + +/* VMBUS_CHANMSG_CHREQUEST */ +struct vmbus_chanmsg_chrequest { + struct vmbus_chanmsg_hdr chm_hdr; +} __packed; + +/* VMBUS_CHANMSG_DISCONNECT */ +struct vmbus_chanmsg_disconnect { + struct vmbus_chanmsg_hdr chm_hdr; +} __packed; + +/* VMBUS_CHANMSG_CHOPEN */ +struct vmbus_chanmsg_chopen { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; + uint32_t chm_openid; + uint32_t chm_gpadl; + uint32_t chm_vcpuid; + uint32_t chm_txbr_pgcnt; +#define VMBUS_CHANMSG_CHOPEN_UDATA_SIZE 120 + uint8_t chm_udata[VMBUS_CHANMSG_CHOPEN_UDATA_SIZE]; +} __packed; + +/* VMBUS_CHANMSG_CHOPEN_RESP */ +struct vmbus_chanmsg_chopen_resp { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; + uint32_t chm_openid; + uint32_t chm_status; +} __packed; + +/* VMBUS_CHANMSG_GPADL_CONN */ +struct vmbus_chanmsg_gpadl_conn { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; + uint32_t chm_gpadl; + uint16_t chm_range_len; + uint16_t chm_range_cnt; + struct vmbus_gpa_range chm_range; +} __packed; + +#define VMBUS_CHANMSG_GPADL_CONN_PGMAX 26 + +/* VMBUS_CHANMSG_GPADL_SUBCONN */ +struct vmbus_chanmsg_gpadl_subconn { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_msgno; + uint32_t chm_gpadl; + uint64_t chm_gpa_page[0]; +} __packed; + +#define VMBUS_CHANMSG_GPADL_SUBCONN_PGMAX 28 + +/* VMBUS_CHANMSG_GPADL_CONNRESP */ +struct vmbus_chanmsg_gpadl_connresp { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; + uint32_t chm_gpadl; + uint32_t chm_status; +} __packed; + +/* VMBUS_CHANMSG_CHCLOSE */ +struct vmbus_chanmsg_chclose { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; +} __packed; + +/* VMBUS_CHANMSG_GPADL_DISCONN */ +struct vmbus_chanmsg_gpadl_disconn { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; + uint32_t chm_gpadl; +} __packed; + +/* VMBUS_CHANMSG_CHFREE */ +struct vmbus_chanmsg_chfree { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; +} __packed; + +/* VMBUS_CHANMSG_CHRESCIND */ +struct vmbus_chanmsg_chrescind { + struct vmbus_chanmsg_hdr chm_hdr; + uint32_t chm_chanid; +} __packed; + +/* VMBUS_CHANMSG_CHOFFER */ +struct vmbus_chanmsg_choffer { + struct vmbus_chanmsg_hdr chm_hdr; + struct hyperv_guid chm_chtype; + struct hyperv_guid chm_chinst; + uint64_t chm_chlat; /* unit: 100ns */ + uint32_t chm_chrev; + uint32_t chm_svrctx_sz; + uint16_t chm_chflags; + uint16_t chm_mmio_sz; /* unit: MB */ + uint8_t chm_udata[120]; + uint16_t chm_subidx; + uint16_t chm_rsvd; + uint32_t chm_chanid; + uint8_t chm_montrig; + uint8_t chm_flags1; /* VMBUS_CHOFFER_FLAG1_ */ + uint16_t chm_flags2; + uint32_t chm_connid; +} __packed; + +#define VMBUS_CHOFFER_FLAG1_HASMNF 0x01 + +#endif /* _KERNEL */ + +#endif /* _HYPERVREG_H_ */ diff --git a/sys/dev/hyperv/hypervvar.h b/sys/dev/hyperv/hypervvar.h new file mode 100644 index 00000000000..b5657dda91e --- /dev/null +++ b/sys/dev/hyperv/hypervvar.h @@ -0,0 +1,115 @@ +/* $NetBSD: hypervvar.h,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2009-2012,2016-2017 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * 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. + * + * $FreeBSD: head/sys/dev/hyperv/include/hyperv.h 326255 2017-11-27 14:52:40Z pfg $ + */ + +#ifndef _HYPERVVAR_H_ +#define _HYPERVVAR_H_ + +#if defined(_KERNEL) + +#include <sys/bus.h> +#include <sys/timex.h> + +#define HYPERV_TIMER_NS_FACTOR 100ULL +#define HYPERV_TIMER_FREQ (NANOSECOND / HYPERV_TIMER_NS_FACTOR) + +#endif /* _KERNEL */ + +/* + * Hyper-V Reference TSC + */ +struct hyperv_reftsc { + volatile uint32_t tsc_seq; + volatile uint32_t tsc_rsvd1; + volatile uint64_t tsc_scale; + volatile int64_t tsc_ofs; +} __packed __aligned(PAGE_SIZE); +#ifdef __CTASSERT +__CTASSERT(sizeof(struct hyperv_reftsc) == PAGE_SIZE); +#endif + +#if defined(_KERNEL) + +int hyperv_hypercall_enabled(void); +int hyperv_synic_supported(void); +void hyperv_send_eom(void); +void hyperv_intr(void); + +struct vmbus_softc; +void vmbus_init_interrupts_md(struct vmbus_softc *); +void vmbus_deinit_interrupts_md(struct vmbus_softc *); +void vmbus_init_synic_md(struct vmbus_softc *, cpuid_t); +void vmbus_deinit_synic_md(struct vmbus_softc *, cpuid_t); + +#define HYPERV_GUID_STRLEN 40 +struct hyperv_guid; +int hyperv_guid2str(const struct hyperv_guid *, char *, size_t); + +/* + * hyperv_tc64 could be NULL, if there were no suitable Hyper-V + * specific timecounter. + */ +typedef uint64_t (*hyperv_tc64_t)(void); +extern hyperv_tc64_t hyperv_tc64; + +uint64_t hyperv_hypercall(uint64_t, paddr_t, paddr_t); +uint64_t hyperv_hypercall_post_message(paddr_t); +uint64_t hyperv_hypercall_signal_event(paddr_t); + +typedef void (*hyperv_proc_t)(void *, struct cpu_info *); +void hyperv_set_event_proc(hyperv_proc_t, void *); +void hyperv_set_message_proc(hyperv_proc_t, void *); + +/* + * Hyper-V bus_dma utilities. + */ +struct hyperv_dma { + bus_dmamap_t map; + bus_dma_segment_t *segs; + void *addr; + int nsegs; +}; + +static __inline bus_addr_t +hyperv_dma_get_paddr(struct hyperv_dma *dma) +{ + return dma->map->dm_segs[0].ds_addr; +} + +void *hyperv_dma_alloc(bus_dma_tag_t, struct hyperv_dma *, bus_size_t, + bus_size_t, bus_size_t, int); +void hyperv_dma_free(bus_dma_tag_t, struct hyperv_dma *); + +#endif /* _KERNEL */ + +#endif /* _HYPERVVAR_H_ */ diff --git a/sys/dev/hyperv/if_hvn.c b/sys/dev/hyperv/if_hvn.c new file mode 100644 index 00000000000..b1778ff93de --- /dev/null +++ b/sys/dev/hyperv/if_hvn.c @@ -0,0 +1,1849 @@ +/* $NetBSD: if_hvn.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ +/* $OpenBSD: if_hvn.c,v 1.39 2018/03/11 14:31:34 mikeb Exp $ */ + +/*- + * Copyright (c) 2009-2012,2016 Microsoft Corp. + * Copyright (c) 2010-2012 Citrix Inc. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2016 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: if_hvn.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); + +#ifdef _KERNEL_OPT +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_net_mpsafe.h" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/atomic.h> +#include <sys/bus.h> +#include <sys/intr.h> +#include <sys/kmem.h> + +#include <net/if.h> +#include <net/if_ether.h> +#include <net/if_media.h> + +#include <net/bpf.h> + +#include <dev/ic/ndisreg.h> +#include <dev/ic/rndisreg.h> + +#include <dev/hyperv/vmbusvar.h> +#include <dev/hyperv/if_hvnreg.h> + +#ifndef EVL_PRIO_BITS +#define EVL_PRIO_BITS 13 +#endif + +#ifndef ETHER_ALIGN +#define ETHER_ALIGN 2 +#endif + +#define HVN_NVS_MSGSIZE 32 +#define HVN_NVS_BUFSIZE PAGE_SIZE + +/* + * RNDIS control interface + */ +#define HVN_RNDIS_CTLREQS 4 +#define HVN_RNDIS_BUFSIZE 512 + +struct rndis_cmd { + uint32_t rc_id; + struct hvn_nvs_rndis rc_msg; + void *rc_req; + bus_dmamap_t rc_dmap; + bus_dma_segment_t rc_segs; + int rc_nsegs; + uint64_t rc_gpa; + struct rndis_packet_msg rc_cmp; + uint32_t rc_cmplen; + uint8_t rc_cmpbuf[HVN_RNDIS_BUFSIZE]; + int rc_done; + TAILQ_ENTRY(rndis_cmd) rc_entry; +}; +TAILQ_HEAD(rndis_queue, rndis_cmd); + +#define HVN_MAXMTU (9 * 1024) + +#define HVN_RNDIS_XFER_SIZE 2048 + +/* + * Tx ring + */ +#define HVN_TX_DESC 256 +#define HVN_TX_FRAGS 15 /* 31 is the max */ +#define HVN_TX_FRAG_SIZE PAGE_SIZE +#define HVN_TX_PKT_SIZE 16384 + +#define HVN_RNDIS_PKT_LEN \ + (sizeof(struct rndis_packet_msg) + \ + sizeof(struct rndis_pktinfo) + NDIS_VLAN_INFO_SIZE + \ + sizeof(struct rndis_pktinfo) + NDIS_TXCSUM_INFO_SIZE) + +struct hvn_tx_desc { + uint32_t txd_id; + int txd_ready; + struct vmbus_gpa txd_sgl[HVN_TX_FRAGS + 1]; + int txd_nsge; + struct mbuf *txd_buf; + bus_dmamap_t txd_dmap; + struct vmbus_gpa txd_gpa; + struct rndis_packet_msg *txd_req; +}; + +struct hvn_softc { + device_t sc_dev; + + struct vmbus_softc *sc_vmbus; + struct vmbus_channel *sc_chan; + bus_dma_tag_t sc_dmat; + + struct ethercom sc_ec; + struct ifmedia sc_media; + struct if_percpuq *sc_ipq; + int sc_link_state; + int sc_promisc; + + uint32_t sc_flags; +#define HVN_SCF_ATTACHED __BIT(0) + + /* NVS protocol */ + int sc_proto; + uint32_t sc_nvstid; + uint8_t sc_nvsrsp[HVN_NVS_MSGSIZE]; + uint8_t *sc_nvsbuf; + int sc_nvsdone; + + /* RNDIS protocol */ + int sc_ndisver; + uint32_t sc_rndisrid; + struct rndis_queue sc_cntl_sq; /* submission queue */ + kmutex_t sc_cntl_sqlck; + struct rndis_queue sc_cntl_cq; /* completion queue */ + kmutex_t sc_cntl_cqlck; + struct rndis_queue sc_cntl_fq; /* free queue */ + kmutex_t sc_cntl_fqlck; + struct rndis_cmd sc_cntl_msgs[HVN_RNDIS_CTLREQS]; + struct hvn_nvs_rndis sc_data_msg; + + /* Rx ring */ + uint8_t *sc_rx_ring; + int sc_rx_size; + uint32_t sc_rx_hndl; + struct hyperv_dma sc_rx_dma; + + /* Tx ring */ + uint32_t sc_tx_next; + uint32_t sc_tx_avail; + struct hvn_tx_desc sc_tx_desc[HVN_TX_DESC]; + bus_dmamap_t sc_tx_rmap; + uint8_t *sc_tx_msgs; + bus_dma_segment_t sc_tx_mseg; +}; + +#define SC2IFP(_sc_) (&(_sc_)->sc_ec.ec_if) +#define IFP2SC(_ifp_) ((_ifp_)->if_softc) + + +static int hvn_match(device_t, cfdata_t, void *); +static void hvn_attach(device_t, device_t, void *); +static int hvn_detach(device_t, int); + +CFATTACH_DECL_NEW(hvn, sizeof(struct hvn_softc), + hvn_match, hvn_attach, hvn_detach, NULL); + +static int hvn_ioctl(struct ifnet *, u_long, void *); +static int hvn_media_change(struct ifnet *); +static void hvn_media_status(struct ifnet *, struct ifmediareq *); +static int hvn_iff(struct hvn_softc *); +static int hvn_init(struct ifnet *); +static void hvn_stop(struct ifnet *, int); +static void hvn_start(struct ifnet *); +static int hvn_encap(struct hvn_softc *, struct mbuf *, + struct hvn_tx_desc **); +static void hvn_decap(struct hvn_softc *, struct hvn_tx_desc *); +static void hvn_txeof(struct hvn_softc *, uint64_t); +static int hvn_rx_ring_create(struct hvn_softc *); +static int hvn_rx_ring_destroy(struct hvn_softc *); +static int hvn_tx_ring_create(struct hvn_softc *); +static void hvn_tx_ring_destroy(struct hvn_softc *); +static int hvn_set_capabilities(struct hvn_softc *); +static int hvn_get_lladdr(struct hvn_softc *, uint8_t *); +static void hvn_get_link_status(struct hvn_softc *); + +/* NSVP */ +static int hvn_nvs_attach(struct hvn_softc *); +static void hvn_nvs_intr(void *); +static int hvn_nvs_cmd(struct hvn_softc *, void *, size_t, uint64_t, int); +static int hvn_nvs_ack(struct hvn_softc *, uint64_t); +static void hvn_nvs_detach(struct hvn_softc *); + +/* RNDIS */ +static int hvn_rndis_attach(struct hvn_softc *); +static int hvn_rndis_cmd(struct hvn_softc *, struct rndis_cmd *, int); +static void hvn_rndis_input(struct hvn_softc *, uint64_t, void *); +static void hvn_rxeof(struct hvn_softc *, uint8_t *, uint32_t); +static void hvn_rndis_complete(struct hvn_softc *, uint8_t *, uint32_t); +static int hvn_rndis_output(struct hvn_softc *, struct hvn_tx_desc *); +static void hvn_rndis_status(struct hvn_softc *, uint8_t *, uint32_t); +static int hvn_rndis_query(struct hvn_softc *, uint32_t, void *, size_t *); +static int hvn_rndis_set(struct hvn_softc *, uint32_t, void *, size_t); +static int hvn_rndis_open(struct hvn_softc *); +static int hvn_rndis_close(struct hvn_softc *); +static void hvn_rndis_detach(struct hvn_softc *); + +static int +hvn_match(device_t parent, cfdata_t match, void *aux) +{ + struct vmbus_attach_args *aa = aux; + + if (memcmp(aa->aa_type, &hyperv_guid_network, sizeof(*aa->aa_type))) + return 0; + return 1; +} + +static void +hvn_attach(device_t parent, device_t self, void *aux) +{ + struct hvn_softc *sc = device_private(self); + struct vmbus_attach_args *aa = aux; + struct ifnet *ifp = SC2IFP(sc); + uint8_t enaddr[ETHER_ADDR_LEN]; + int error; + + sc->sc_dev = self; + sc->sc_vmbus = (struct vmbus_softc *)device_private(parent); + sc->sc_chan = aa->aa_chan; + sc->sc_dmat = sc->sc_vmbus->sc_dmat; + + aprint_naive("\n"); + aprint_normal(": Hyper-V NetVSC\n"); + + strlcpy(ifp->if_xname, device_xname(sc->sc_dev), IFNAMSIZ); + + if (hvn_nvs_attach(sc)) { + aprint_error_dev(self, "failed to init NVSP\n"); + return; + } + + if (hvn_rx_ring_create(sc)) { + aprint_error_dev(self, "failed to create Rx ring\n"); + goto fail1; + } + + if (hvn_tx_ring_create(sc)) { + aprint_error_dev(self, "failed to create Tx ring\n"); + goto fail1; + } + + ifp->if_softc = sc; + ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; + ifp->if_ioctl = hvn_ioctl; + ifp->if_start = hvn_start; + ifp->if_init = hvn_init; + ifp->if_stop = hvn_stop; + ifp->if_capabilities = IFCAP_CSUM_IPv4_Tx | IFCAP_CSUM_IPv4_Rx; + ifp->if_capabilities |= IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv4_Rx; + ifp->if_capabilities |= IFCAP_CSUM_TCPv6_Tx | IFCAP_CSUM_TCPv6_Rx; + if (sc->sc_ndisver > NDIS_VERSION_6_30) { + ifp->if_capabilities |= IFCAP_CSUM_UDPv4_Tx; + ifp->if_capabilities |= IFCAP_CSUM_UDPv4_Rx; + ifp->if_capabilities |= IFCAP_CSUM_UDPv6_Tx; + ifp->if_capabilities |= IFCAP_CSUM_UDPv6_Rx; + } + if (sc->sc_proto >= HVN_NVS_PROTO_VERSION_2) { + sc->sc_ec.ec_capabilities |= ETHERCAP_VLAN_HWTAGGING; + sc->sc_ec.ec_capabilities |= ETHERCAP_VLAN_MTU; + } + + IFQ_SET_MAXLEN(&ifp->if_snd, HVN_TX_DESC - 1); + IFQ_SET_READY(&ifp->if_snd); + + ifmedia_init(&sc->sc_media, IFM_IMASK, hvn_media_change, + hvn_media_status); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_MANUAL, 0, NULL); + ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_MANUAL); + + error = if_initialize(ifp); + if (error) { + aprint_error_dev(self, "if_initialize failed(%d)\n", error); + goto fail2; + } + sc->sc_ipq = if_percpuq_create(ifp); + if_deferred_start_init(ifp, NULL); + + if (hvn_rndis_attach(sc)) { + aprint_error_dev(self, "failed to init RNDIS\n"); + goto fail1; + } + + aprint_normal_dev(self, "NVS %d.%d NDIS %d.%d\n", + sc->sc_proto >> 16, sc->sc_proto & 0xffff, + sc->sc_ndisver >> 16 , sc->sc_ndisver & 0xffff); + + if (hvn_set_capabilities(sc)) { + aprint_error_dev(self, "failed to setup offloading\n"); + goto fail2; + } + + if (hvn_get_lladdr(sc, enaddr)) { + aprint_error_dev(self, + "failed to obtain an ethernet address\n"); + goto fail2; + } + aprint_normal_dev(self, "Ethernet address %s\n", ether_sprintf(enaddr)); + + ether_ifattach(ifp, enaddr); + if_register(ifp); + + if (pmf_device_register(self, NULL, NULL)) + pmf_class_network_register(self, ifp); + else + aprint_error_dev(self, "couldn't establish power handler\n"); + + SET(sc->sc_flags, HVN_SCF_ATTACHED); + return; + +fail2: hvn_rndis_detach(sc); +fail1: hvn_rx_ring_destroy(sc); + hvn_tx_ring_destroy(sc); + hvn_nvs_detach(sc); +} + +static int +hvn_detach(device_t self, int flags) +{ + struct hvn_softc *sc = device_private(self); + struct ifnet *ifp = SC2IFP(sc); + + if (!ISSET(sc->sc_flags, HVN_SCF_ATTACHED)) + return 0; + + hvn_stop(ifp, 1); + + pmf_device_deregister(self); + + ether_ifdetach(ifp); + if_detach(ifp); + if_percpuq_destroy(sc->sc_ipq); + + hvn_rndis_detach(sc); + hvn_rx_ring_destroy(sc); + hvn_tx_ring_destroy(sc); + hvn_nvs_detach(sc); + + return 0; +} + +static int +hvn_ioctl(struct ifnet *ifp, u_long command, void * data) +{ + struct hvn_softc *sc = IFP2SC(ifp); + struct ifreq *ifr = (struct ifreq *)data; + int s, error = 0; + + s = splnet(); + + switch (command) { + case SIOCSIFFLAGS: + if (ifp->if_flags & IFF_UP) { + if (ifp->if_flags & IFF_RUNNING) + error = ENETRESET; + else { + error = hvn_init(ifp); + if (error) + ifp->if_flags &= ~IFF_UP; + } + } else { + if (ifp->if_flags & IFF_RUNNING) + hvn_stop(ifp, 1); + } + break; + case SIOCGIFMEDIA: + case SIOCSIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, command); + break; + default: + error = ether_ioctl(ifp, command, data); + break; + } + + if (error == ENETRESET) { + if (ifp->if_flags & IFF_RUNNING) + hvn_iff(sc); + error = 0; + } + + splx(s); + + return error; +} + +static int +hvn_media_change(struct ifnet *ifp) +{ + + return 0; +} + +static void +hvn_media_status(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct hvn_softc *sc = IFP2SC(ifp); + int link_state; + + link_state = sc->sc_link_state; + hvn_get_link_status(sc); + if (link_state != sc->sc_link_state) + if_link_state_change(ifp, sc->sc_link_state); + + ifmr->ifm_status = IFM_AVALID; + ifmr->ifm_active = IFM_ETHER | IFM_MANUAL; + if (sc->sc_link_state == LINK_STATE_UP) + ifmr->ifm_status |= IFM_ACTIVE; +} + +static int +hvn_iff(struct hvn_softc *sc) +{ + + /* XXX */ + sc->sc_promisc = 0; + + return 0; +} + +static int +hvn_init(struct ifnet *ifp) +{ + struct hvn_softc *sc = IFP2SC(ifp); + int error; + + hvn_stop(ifp, 0); + + error = hvn_iff(sc); + if (error) + return error; + + error = hvn_rndis_open(sc); + if (error == 0) { + ifp->if_flags |= IFF_RUNNING; + ifp->if_flags &= ~IFF_OACTIVE; + } + return error; +} + +static void +hvn_stop(struct ifnet *ifp, int disable) +{ + struct hvn_softc *sc = IFP2SC(ifp); + + hvn_rndis_close(sc); + + ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); +} + +static void +hvn_start(struct ifnet *ifp) +{ + struct hvn_softc *sc = IFP2SC(ifp); + struct hvn_tx_desc *txd; + struct mbuf *m; + + if ((ifp->if_flags & (IFF_RUNNING | IFF_OACTIVE)) != IFF_RUNNING) + return; + + for (;;) { + if (!sc->sc_tx_avail) { + /* transient */ + ifp->if_flags |= IFF_OACTIVE; + break; + } + + IFQ_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + if (hvn_encap(sc, m, &txd)) { + /* the chain is too large */ + ifp->if_oerrors++; + m_freem(m); + continue; + } + + bpf_mtap(ifp, m, BPF_D_OUT); + + if (hvn_rndis_output(sc, txd)) { + hvn_decap(sc, txd); + ifp->if_oerrors++; + m_freem(m); + continue; + } + + sc->sc_tx_next++; + } +} + +static inline char * +hvn_rndis_pktinfo_append(struct rndis_packet_msg *pkt, size_t pktsize, + size_t datalen, uint32_t type) +{ + struct rndis_pktinfo *pi; + size_t pi_size = sizeof(*pi) + datalen; + char *cp; + + KASSERT(pkt->rm_pktinfooffset + pkt->rm_pktinfolen + pi_size <= + pktsize); + + cp = (char *)pkt + pkt->rm_pktinfooffset + pkt->rm_pktinfolen; + pi = (struct rndis_pktinfo *)cp; + pi->rm_size = pi_size; + pi->rm_type = type; + pi->rm_pktinfooffset = sizeof(*pi); + pkt->rm_pktinfolen += pi_size; + pkt->rm_dataoffset += pi_size; + pkt->rm_len += pi_size; + + return (char *)pi->rm_data; +} + +static int +hvn_encap(struct hvn_softc *sc, struct mbuf *m, struct hvn_tx_desc **txd0) +{ + struct hvn_tx_desc *txd; + struct rndis_packet_msg *pkt; + bus_dma_segment_t *seg; + size_t pktlen; + int i, rv; + + do { + txd = &sc->sc_tx_desc[sc->sc_tx_next % HVN_TX_DESC]; + sc->sc_tx_next++; + } while (!txd->txd_ready); + txd->txd_ready = 0; + + pkt = txd->txd_req; + memset(pkt, 0, HVN_RNDIS_PKT_LEN); + pkt->rm_type = REMOTE_NDIS_PACKET_MSG; + pkt->rm_len = sizeof(*pkt) + m->m_pkthdr.len; + pkt->rm_dataoffset = RNDIS_DATA_OFFSET; + pkt->rm_datalen = m->m_pkthdr.len; + pkt->rm_pktinfooffset = sizeof(*pkt); /* adjusted below */ + pkt->rm_pktinfolen = 0; + + rv = bus_dmamap_load_mbuf(sc->sc_dmat, txd->txd_dmap, m, BUS_DMA_READ | + BUS_DMA_NOWAIT); + switch (rv) { + case 0: + break; + case EFBIG: + if (m_defrag(m, M_NOWAIT) == 0 && + bus_dmamap_load_mbuf(sc->sc_dmat, txd->txd_dmap, m, + BUS_DMA_READ | BUS_DMA_NOWAIT) == 0) + break; + /* FALLTHROUGH */ + default: + DPRINTF("%s: failed to load mbuf\n", device_xname(sc->sc_dev)); + return -1; + } + txd->txd_buf = m; + + if (m->m_flags & M_VLANTAG) { + uint32_t vlan; + char *cp; + + vlan = NDIS_VLAN_INFO_MAKE( + EVL_VLANOFTAG(m->m_pkthdr.ether_vtag), + EVL_PRIOFTAG(m->m_pkthdr.ether_vtag), 0); + cp = hvn_rndis_pktinfo_append(pkt, HVN_RNDIS_PKT_LEN, + NDIS_VLAN_INFO_SIZE, NDIS_PKTINFO_TYPE_VLAN); + memcpy(cp, &vlan, NDIS_VLAN_INFO_SIZE); + } + + if (m->m_pkthdr.csum_flags & (M_CSUM_IPv4 | M_CSUM_UDPv4 | + M_CSUM_TCPv4)) { + uint32_t csum = NDIS_TXCSUM_INFO_IPV4; + char *cp; + + if (m->m_pkthdr.csum_flags & M_CSUM_IPv4) + csum |= NDIS_TXCSUM_INFO_IPCS; + if (m->m_pkthdr.csum_flags & M_CSUM_TCPv4) + csum |= NDIS_TXCSUM_INFO_TCPCS; + if (m->m_pkthdr.csum_flags & M_CSUM_UDPv4) + csum |= NDIS_TXCSUM_INFO_UDPCS; + cp = hvn_rndis_pktinfo_append(pkt, HVN_RNDIS_PKT_LEN, + NDIS_TXCSUM_INFO_SIZE, NDIS_PKTINFO_TYPE_CSUM); + memcpy(cp, &csum, NDIS_TXCSUM_INFO_SIZE); + } + + pktlen = pkt->rm_pktinfooffset + pkt->rm_pktinfolen; + pkt->rm_pktinfooffset -= RNDIS_HEADER_OFFSET; + + /* Attach an RNDIS message to the first slot */ + txd->txd_sgl[0].gpa_page = txd->txd_gpa.gpa_page; + txd->txd_sgl[0].gpa_ofs = txd->txd_gpa.gpa_ofs; + txd->txd_sgl[0].gpa_len = pktlen; + txd->txd_nsge = txd->txd_dmap->dm_nsegs + 1; + + for (i = 0; i < txd->txd_dmap->dm_nsegs; i++) { + seg = &txd->txd_dmap->dm_segs[i]; + txd->txd_sgl[1 + i].gpa_page = atop(seg->ds_addr); + txd->txd_sgl[1 + i].gpa_ofs = seg->ds_addr & PAGE_MASK; + txd->txd_sgl[1 + i].gpa_len = seg->ds_len; + } + + *txd0 = txd; + + atomic_dec_uint(&sc->sc_tx_avail); + + return 0; +} + +static void +hvn_decap(struct hvn_softc *sc, struct hvn_tx_desc *txd) +{ + struct ifnet *ifp = SC2IFP(sc); + + bus_dmamap_sync(sc->sc_dmat, txd->txd_dmap, 0, 0, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, txd->txd_dmap); + txd->txd_buf = NULL; + txd->txd_nsge = 0; + txd->txd_ready = 1; + atomic_inc_uint(&sc->sc_tx_avail); + ifp->if_flags &= ~IFF_OACTIVE; +} + +static void +hvn_txeof(struct hvn_softc *sc, uint64_t tid) +{ + struct ifnet *ifp = SC2IFP(sc); + struct hvn_tx_desc *txd; + struct mbuf *m; + uint32_t id = tid >> 32; + + if ((tid & 0xffffffffU) != 0) + return; + + id -= HVN_NVS_CHIM_SIG; + if (id >= HVN_TX_DESC) { + device_printf(sc->sc_dev, "tx packet index too large: %u", id); + return; + } + + txd = &sc->sc_tx_desc[id]; + + if ((m = txd->txd_buf) == NULL) { + device_printf(sc->sc_dev, "no mbuf @%u\n", id); + return; + } + txd->txd_buf = NULL; + + bus_dmamap_sync(sc->sc_dmat, txd->txd_dmap, 0, 0, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, txd->txd_dmap); + m_freem(m); + ifp->if_opackets++; + + txd->txd_ready = 1; + + atomic_inc_uint(&sc->sc_tx_avail); + ifp->if_flags &= ~IFF_OACTIVE; +} + +static int +hvn_rx_ring_create(struct hvn_softc *sc) +{ + struct hvn_nvs_rxbuf_conn cmd; + struct hvn_nvs_rxbuf_conn_resp *rsp; + uint64_t tid; + + if (sc->sc_proto <= HVN_NVS_PROTO_VERSION_2) + sc->sc_rx_size = 15 * 1024 * 1024; /* 15MB */ + else + sc->sc_rx_size = 16 * 1024 * 1024; /* 16MB */ + sc->sc_rx_ring = hyperv_dma_alloc(sc->sc_dmat, &sc->sc_rx_dma, + sc->sc_rx_size, PAGE_SIZE, PAGE_SIZE, sc->sc_rx_size / PAGE_SIZE); + if (sc->sc_rx_ring == NULL) { + DPRINTF("%s: failed to allocate Rx ring buffer\n", + device_xname(sc->sc_dev)); + return -1; + } + if (vmbus_handle_alloc(sc->sc_chan, &sc->sc_rx_dma, sc->sc_rx_size, + &sc->sc_rx_hndl)) { + DPRINTF("%s: failed to obtain a PA handle\n", + device_xname(sc->sc_dev)); + goto errout; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.nvs_type = HVN_NVS_TYPE_RXBUF_CONN; + cmd.nvs_gpadl = sc->sc_rx_hndl; + cmd.nvs_sig = HVN_NVS_RXBUF_SIG; + + tid = atomic_inc_uint_nv(&sc->sc_nvstid); + if (hvn_nvs_cmd(sc, &cmd, sizeof(cmd), tid, 100)) + goto errout; + + rsp = (struct hvn_nvs_rxbuf_conn_resp *)&sc->sc_nvsrsp; + if (rsp->nvs_status != HVN_NVS_STATUS_OK) { + DPRINTF("%s: failed to set up the Rx ring\n", + device_xname(sc->sc_dev)); + goto errout; + } + if (rsp->nvs_nsect > 1) { + DPRINTF("%s: invalid number of Rx ring sections: %u\n", + device_xname(sc->sc_dev), rsp->nvs_nsect); + hvn_rx_ring_destroy(sc); + return -1; + } + return 0; + + errout: + if (sc->sc_rx_hndl) { + vmbus_handle_free(sc->sc_chan, sc->sc_rx_hndl); + sc->sc_rx_hndl = 0; + } + if (sc->sc_rx_ring) { + kmem_free(sc->sc_rx_ring, sc->sc_rx_size); + sc->sc_rx_ring = NULL; + } + return -1; +} + +static int +hvn_rx_ring_destroy(struct hvn_softc *sc) +{ + struct hvn_nvs_rxbuf_disconn cmd; + uint64_t tid; + + if (sc->sc_rx_ring == NULL) + return 0; + + memset(&cmd, 0, sizeof(cmd)); + cmd.nvs_type = HVN_NVS_TYPE_RXBUF_DISCONN; + cmd.nvs_sig = HVN_NVS_RXBUF_SIG; + + tid = atomic_inc_uint_nv(&sc->sc_nvstid); + if (hvn_nvs_cmd(sc, &cmd, sizeof(cmd), tid, 0)) + return -1; + + delay(100); + + vmbus_handle_free(sc->sc_chan, sc->sc_rx_hndl); + + sc->sc_rx_hndl = 0; + + kmem_free(sc->sc_rx_ring, sc->sc_rx_size); + sc->sc_rx_ring = NULL; + + return 0; +} + +static int +hvn_tx_ring_create(struct hvn_softc *sc) +{ + const int dmaflags = cold ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK; + struct hvn_tx_desc *txd; + bus_dma_segment_t *seg; + size_t msgsize; + int i, rsegs; + paddr_t pa; + + msgsize = roundup(HVN_RNDIS_PKT_LEN, 128); + + /* Allocate memory to store RNDIS messages */ + if (bus_dmamem_alloc(sc->sc_dmat, msgsize * HVN_TX_DESC, PAGE_SIZE, 0, + &sc->sc_tx_mseg, 1, &rsegs, dmaflags)) { + DPRINTF("%s: failed to allocate memory for RDNIS messages\n", + device_xname(sc->sc_dev)); + goto errout; + } + if (bus_dmamem_map(sc->sc_dmat, &sc->sc_tx_mseg, 1, msgsize * + HVN_TX_DESC, (void **)&sc->sc_tx_msgs, dmaflags)) { + DPRINTF("%s: failed to establish mapping for RDNIS messages\n", + device_xname(sc->sc_dev)); + goto errout; + } + memset(sc->sc_tx_msgs, 0, msgsize * HVN_TX_DESC); + if (bus_dmamap_create(sc->sc_dmat, msgsize * HVN_TX_DESC, 1, + msgsize * HVN_TX_DESC, 0, dmaflags, &sc->sc_tx_rmap)) { + DPRINTF("%s: failed to create map for RDNIS messages\n", + device_xname(sc->sc_dev)); + goto errout; + } + if (bus_dmamap_load(sc->sc_dmat, sc->sc_tx_rmap, sc->sc_tx_msgs, + msgsize * HVN_TX_DESC, NULL, dmaflags)) { + DPRINTF("%s: failed to create map for RDNIS messages\n", + device_xname(sc->sc_dev)); + goto errout; + } + + for (i = 0; i < HVN_TX_DESC; i++) { + txd = &sc->sc_tx_desc[i]; + if (bus_dmamap_create(sc->sc_dmat, HVN_TX_PKT_SIZE, + HVN_TX_FRAGS, HVN_TX_FRAG_SIZE, PAGE_SIZE, dmaflags, + &txd->txd_dmap)) { + DPRINTF("%s: failed to create map for TX descriptors\n", + device_xname(sc->sc_dev)); + goto errout; + } + seg = &sc->sc_tx_rmap->dm_segs[0]; + pa = seg->ds_addr + (msgsize * i); + txd->txd_gpa.gpa_page = atop(pa); + txd->txd_gpa.gpa_ofs = pa & PAGE_MASK; + txd->txd_gpa.gpa_len = msgsize; + txd->txd_req = (void *)(sc->sc_tx_msgs + (msgsize * i)); + txd->txd_id = i + HVN_NVS_CHIM_SIG; + txd->txd_ready = 1; + } + sc->sc_tx_avail = HVN_TX_DESC; + + return 0; + + errout: + hvn_tx_ring_destroy(sc); + return -1; +} + +static void +hvn_tx_ring_destroy(struct hvn_softc *sc) +{ + struct hvn_tx_desc *txd; + int i; + + for (i = 0; i < HVN_TX_DESC; i++) { + txd = &sc->sc_tx_desc[i]; + if (txd->txd_dmap == NULL) + continue; + bus_dmamap_sync(sc->sc_dmat, txd->txd_dmap, 0, 0, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, txd->txd_dmap); + bus_dmamap_destroy(sc->sc_dmat, txd->txd_dmap); + txd->txd_dmap = NULL; + if (txd->txd_buf == NULL) + continue; + m_free(txd->txd_buf); + txd->txd_buf = NULL; + } + if (sc->sc_tx_rmap) { + bus_dmamap_sync(sc->sc_dmat, sc->sc_tx_rmap, 0, 0, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, sc->sc_tx_rmap); + bus_dmamap_destroy(sc->sc_dmat, sc->sc_tx_rmap); + } + if (sc->sc_tx_msgs) { + size_t msgsize = roundup(HVN_RNDIS_PKT_LEN, 128); + + bus_dmamem_unmap(sc->sc_dmat, sc->sc_tx_msgs, + msgsize * HVN_TX_DESC); + bus_dmamem_free(sc->sc_dmat, &sc->sc_tx_mseg, 1); + } + sc->sc_tx_rmap = NULL; + sc->sc_tx_msgs = NULL; +} + +static int +hvn_get_lladdr(struct hvn_softc *sc, uint8_t *enaddr) +{ + size_t addrlen = ETHER_ADDR_LEN; + int rv; + + rv = hvn_rndis_query(sc, OID_802_3_PERMANENT_ADDRESS, enaddr, &addrlen); + if (rv == 0 && addrlen != ETHER_ADDR_LEN) + rv = -1; + return rv; +} + +static void +hvn_get_link_status(struct hvn_softc *sc) +{ + uint32_t state; + size_t len = sizeof(state); + + if (hvn_rndis_query(sc, OID_GEN_MEDIA_CONNECT_STATUS, + &state, &len) == 0) + sc->sc_link_state = (state == NDIS_MEDIA_STATE_CONNECTED) ? + LINK_STATE_UP : LINK_STATE_DOWN; +} + +static int +hvn_nvs_attach(struct hvn_softc *sc) +{ + static const uint32_t protos[] = { + HVN_NVS_PROTO_VERSION_5, + HVN_NVS_PROTO_VERSION_4, + HVN_NVS_PROTO_VERSION_2, + HVN_NVS_PROTO_VERSION_1 + }; + const int kmemflags = cold ? KM_NOSLEEP : KM_SLEEP; + struct hvn_nvs_init cmd; + struct hvn_nvs_init_resp *rsp; + struct hvn_nvs_ndis_init ncmd; + struct hvn_nvs_ndis_conf ccmd; + uint32_t ndisver, ringsize; + uint64_t tid; + int i; + + sc->sc_nvsbuf = kmem_zalloc(HVN_NVS_BUFSIZE, kmemflags); + if (sc->sc_nvsbuf == NULL) { + DPRINTF("%s: failed to allocate channel data buffer\n", + device_xname(sc->sc_dev)); + return -1; + } + + /* We need to be able to fit all RNDIS control and data messages */ + ringsize = HVN_RNDIS_CTLREQS * + (sizeof(struct hvn_nvs_rndis) + sizeof(struct vmbus_gpa)) + + HVN_TX_DESC * (sizeof(struct hvn_nvs_rndis) + + (HVN_TX_FRAGS + 1) * sizeof(struct vmbus_gpa)); + + sc->sc_chan->ch_flags &= ~CHF_BATCHED; + + if (vmbus_channel_setdeferred(sc->sc_chan, device_xname(sc->sc_dev))) { + aprint_error_dev(sc->sc_dev, + "failed to create the interrupt thread\n"); + return -1; + } + + /* Associate our interrupt handler with the channel */ + if (vmbus_channel_open(sc->sc_chan, ringsize, NULL, 0, + hvn_nvs_intr, sc)) { + DPRINTF("%s: failed to open channel\n", + device_xname(sc->sc_dev)); + kmem_free(sc->sc_nvsbuf, HVN_NVS_BUFSIZE); + return -1; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.nvs_type = HVN_NVS_TYPE_INIT; + for (i = 0; i < __arraycount(protos); i++) { + cmd.nvs_ver_min = cmd.nvs_ver_max = protos[i]; + tid = atomic_inc_uint_nv(&sc->sc_nvstid); + if (hvn_nvs_cmd(sc, &cmd, sizeof(cmd), tid, 100)) + return -1; + + rsp = (struct hvn_nvs_init_resp *)&sc->sc_nvsrsp; + if (rsp->nvs_status == HVN_NVS_STATUS_OK) { + sc->sc_proto = protos[i]; + break; + } + } + if (i == __arraycount(protos)) { + DPRINTF("%s: failed to negotiate NVSP version\n", + device_xname(sc->sc_dev)); + return -1; + } + + if (sc->sc_proto >= HVN_NVS_PROTO_VERSION_2) { + memset(&ccmd, 0, sizeof(ccmd)); + ccmd.nvs_type = HVN_NVS_TYPE_NDIS_CONF; + ccmd.nvs_mtu = HVN_MAXMTU; + ccmd.nvs_caps = HVN_NVS_NDIS_CONF_VLAN; + + tid = atomic_inc_uint_nv(&sc->sc_nvstid); + if (hvn_nvs_cmd(sc, &ccmd, sizeof(ccmd), tid, 100)) + return -1; + } + + memset(&ncmd, 0, sizeof(ncmd)); + ncmd.nvs_type = HVN_NVS_TYPE_NDIS_INIT; + if (sc->sc_proto <= HVN_NVS_PROTO_VERSION_4) + ndisver = NDIS_VERSION_6_1; + else + ndisver = NDIS_VERSION_6_30; + ncmd.nvs_ndis_major = (ndisver & 0xffff0000) >> 16; + ncmd.nvs_ndis_minor = ndisver & 0x0000ffff; + + tid = atomic_inc_uint_nv(&sc->sc_nvstid); + if (hvn_nvs_cmd(sc, &ncmd, sizeof(ncmd), tid, 100)) + return -1; + + sc->sc_ndisver = ndisver; + + return 0; +} + +static void +hvn_nvs_intr(void *arg) +{ + struct hvn_softc *sc = arg; + struct ifnet *ifp = SC2IFP(sc); + struct vmbus_chanpkt_hdr *cph; + const struct hvn_nvs_hdr *nvs; + uint64_t rid; + uint32_t rlen; + int rv; + bool dotx = false; + + for (;;) { + rv = vmbus_channel_recv(sc->sc_chan, sc->sc_nvsbuf, + HVN_NVS_BUFSIZE, &rlen, &rid, 1); + if (rv != 0 || rlen == 0) { + if (rv != EAGAIN) + device_printf(sc->sc_dev, + "failed to receive an NVSP packet\n"); + break; + } + cph = (struct vmbus_chanpkt_hdr *)sc->sc_nvsbuf; + nvs = (const struct hvn_nvs_hdr *)VMBUS_CHANPKT_CONST_DATA(cph); + + if (cph->cph_type == VMBUS_CHANPKT_TYPE_COMP) { + switch (nvs->nvs_type) { + case HVN_NVS_TYPE_INIT_RESP: + case HVN_NVS_TYPE_RXBUF_CONNRESP: + case HVN_NVS_TYPE_CHIM_CONNRESP: + case HVN_NVS_TYPE_SUBCH_RESP: + /* copy the response back */ + memcpy(&sc->sc_nvsrsp, nvs, HVN_NVS_MSGSIZE); + sc->sc_nvsdone = 1; + wakeup(&sc->sc_nvsrsp); + break; + case HVN_NVS_TYPE_RNDIS_ACK: + dotx = true; + hvn_txeof(sc, cph->cph_tid); + break; + default: + device_printf(sc->sc_dev, + "unhandled NVSP packet type %u " + "on completion\n", nvs->nvs_type); + break; + } + } else if (cph->cph_type == VMBUS_CHANPKT_TYPE_RXBUF) { + switch (nvs->nvs_type) { + case HVN_NVS_TYPE_RNDIS: + hvn_rndis_input(sc, cph->cph_tid, cph); + break; + default: + device_printf(sc->sc_dev, + "unhandled NVSP packet type %u " + "on receive\n", nvs->nvs_type); + break; + } + } else + device_printf(sc->sc_dev, + "unknown NVSP packet type %u\n", cph->cph_type); + } + + if (dotx) + if_schedule_deferred_start(ifp); +} + +static int +hvn_nvs_cmd(struct hvn_softc *sc, void *cmd, size_t cmdsize, uint64_t tid, + int timo) +{ + struct hvn_nvs_hdr *hdr = cmd; + int tries = 10; + int rv, s; + + sc->sc_nvsdone = 0; + + do { + rv = vmbus_channel_send(sc->sc_chan, cmd, cmdsize, + tid, VMBUS_CHANPKT_TYPE_INBAND, + timo ? VMBUS_CHANPKT_FLAG_RC : 0); + if (rv == EAGAIN) { + if (cold) + delay(1000); + else + tsleep(cmd, PRIBIO, "nvsout", 1); + } else if (rv) { + DPRINTF("%s: NVSP operation %u send error %d\n", + device_xname(sc->sc_dev), hdr->nvs_type, rv); + return rv; + } + } while (rv != 0 && --tries > 0); + + if (tries == 0 && rv != 0) { + device_printf(sc->sc_dev, + "NVSP operation %u send error %d\n", hdr->nvs_type, rv); + return rv; + } + + if (timo == 0) + return 0; + + do { + if (cold) + delay(1000); + else + tsleep(sc, PRIBIO | PCATCH, "nvscmd", 1); + s = splnet(); + hvn_nvs_intr(sc); + splx(s); + } while (--timo > 0 && sc->sc_nvsdone != 1); + + if (timo == 0 && sc->sc_nvsdone != 1) { + device_printf(sc->sc_dev, "NVSP operation %u timed out\n", + hdr->nvs_type); + return ETIMEDOUT; + } + return 0; +} + +static int +hvn_nvs_ack(struct hvn_softc *sc, uint64_t tid) +{ + struct hvn_nvs_rndis_ack cmd; + int tries = 5; + int rv; + + cmd.nvs_type = HVN_NVS_TYPE_RNDIS_ACK; + cmd.nvs_status = HVN_NVS_STATUS_OK; + do { + rv = vmbus_channel_send(sc->sc_chan, &cmd, sizeof(cmd), + tid, VMBUS_CHANPKT_TYPE_COMP, 0); + if (rv == EAGAIN) + delay(10); + else if (rv) { + DPRINTF("%s: NVSP acknowledgement error %d\n", + device_xname(sc->sc_dev), rv); + return rv; + } + } while (rv != 0 && --tries > 0); + return rv; +} + +static void +hvn_nvs_detach(struct hvn_softc *sc) +{ + + if (vmbus_channel_close(sc->sc_chan) == 0) { + kmem_free(sc->sc_nvsbuf, HVN_NVS_BUFSIZE); + sc->sc_nvsbuf = NULL; + } +} + +static inline struct rndis_cmd * +hvn_alloc_cmd(struct hvn_softc *sc) +{ + struct rndis_cmd *rc; + + mutex_enter(&sc->sc_cntl_fqlck); + while ((rc = TAILQ_FIRST(&sc->sc_cntl_fq)) == NULL) + /* XXX use condvar(9) instead of mtsleep */ + mtsleep(&sc->sc_cntl_fq, PRIBIO, "nvsalloc", 1, + &sc->sc_cntl_fqlck); + TAILQ_REMOVE(&sc->sc_cntl_fq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_fqlck); + return rc; +} + +static inline void +hvn_submit_cmd(struct hvn_softc *sc, struct rndis_cmd *rc) +{ + + mutex_enter(&sc->sc_cntl_sqlck); + TAILQ_INSERT_TAIL(&sc->sc_cntl_sq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_sqlck); +} + +static inline struct rndis_cmd * +hvn_complete_cmd(struct hvn_softc *sc, uint32_t id) +{ + struct rndis_cmd *rc; + + mutex_enter(&sc->sc_cntl_sqlck); + TAILQ_FOREACH(rc, &sc->sc_cntl_sq, rc_entry) { + if (rc->rc_id == id) { + TAILQ_REMOVE(&sc->sc_cntl_sq, rc, rc_entry); + break; + } + } + mutex_exit(&sc->sc_cntl_sqlck); + if (rc != NULL) { + mutex_enter(&sc->sc_cntl_cqlck); + TAILQ_INSERT_TAIL(&sc->sc_cntl_cq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_cqlck); + } + return rc; +} + +static inline void +hvn_release_cmd(struct hvn_softc *sc, struct rndis_cmd *rc) +{ + + mutex_enter(&sc->sc_cntl_cqlck); + TAILQ_REMOVE(&sc->sc_cntl_cq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_cqlck); +} + +static inline int +hvn_rollback_cmd(struct hvn_softc *sc, struct rndis_cmd *rc) +{ + struct rndis_cmd *rn; + + mutex_enter(&sc->sc_cntl_sqlck); + TAILQ_FOREACH(rn, &sc->sc_cntl_sq, rc_entry) { + if (rn == rc) { + TAILQ_REMOVE(&sc->sc_cntl_sq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_sqlck); + return 0; + } + } + mutex_exit(&sc->sc_cntl_sqlck); + return -1; +} + +static inline void +hvn_free_cmd(struct hvn_softc *sc, struct rndis_cmd *rc) +{ + + memset(rc->rc_req, 0, sizeof(struct rndis_packet_msg)); + memset(&rc->rc_cmp, 0, sizeof(rc->rc_cmp)); + memset(&rc->rc_msg, 0, sizeof(rc->rc_msg)); + mutex_enter(&sc->sc_cntl_fqlck); + TAILQ_INSERT_TAIL(&sc->sc_cntl_fq, rc, rc_entry); + mutex_exit(&sc->sc_cntl_fqlck); + wakeup(&sc->sc_cntl_fq); +} + +static int +hvn_rndis_attach(struct hvn_softc *sc) +{ + const int dmaflags = cold ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK; + struct rndis_init_req *req; + struct rndis_init_comp *cmp; + struct rndis_cmd *rc; + int i, rv; + + /* RNDIS control message queues */ + TAILQ_INIT(&sc->sc_cntl_sq); + TAILQ_INIT(&sc->sc_cntl_cq); + TAILQ_INIT(&sc->sc_cntl_fq); + mutex_init(&sc->sc_cntl_sqlck, MUTEX_DEFAULT, IPL_NET); + mutex_init(&sc->sc_cntl_cqlck, MUTEX_DEFAULT, IPL_NET); + mutex_init(&sc->sc_cntl_fqlck, MUTEX_DEFAULT, IPL_NET); + + for (i = 0; i < HVN_RNDIS_CTLREQS; i++) { + rc = &sc->sc_cntl_msgs[i]; + if (bus_dmamap_create(sc->sc_dmat, PAGE_SIZE, 1, PAGE_SIZE, 0, + dmaflags, &rc->rc_dmap)) { + DPRINTF("%s: failed to create RNDIS command map\n", + device_xname(sc->sc_dev)); + goto errout; + } + if (bus_dmamem_alloc(sc->sc_dmat, PAGE_SIZE, PAGE_SIZE, + 0, &rc->rc_segs, 1, &rc->rc_nsegs, dmaflags)) { + DPRINTF("%s: failed to allocate RNDIS command\n", + device_xname(sc->sc_dev)); + bus_dmamap_destroy(sc->sc_dmat, rc->rc_dmap); + goto errout; + } + if (bus_dmamem_map(sc->sc_dmat, &rc->rc_segs, rc->rc_nsegs, + PAGE_SIZE, (void **)&rc->rc_req, dmaflags)) { + DPRINTF("%s: failed to allocate RNDIS command\n", + device_xname(sc->sc_dev)); + bus_dmamem_free(sc->sc_dmat, &rc->rc_segs, + rc->rc_nsegs); + bus_dmamap_destroy(sc->sc_dmat, rc->rc_dmap); + goto errout; + } + memset(rc->rc_req, 0, PAGE_SIZE); + if (bus_dmamap_load(sc->sc_dmat, rc->rc_dmap, rc->rc_req, + PAGE_SIZE, NULL, dmaflags)) { + DPRINTF("%s: failed to load RNDIS command map\n", + device_xname(sc->sc_dev)); + bus_dmamem_free(sc->sc_dmat, &rc->rc_segs, + rc->rc_nsegs); + bus_dmamap_destroy(sc->sc_dmat, rc->rc_dmap); + goto errout; + } + rc->rc_gpa = atop(rc->rc_dmap->dm_segs[0].ds_addr); + TAILQ_INSERT_TAIL(&sc->sc_cntl_fq, rc, rc_entry); + } + + rc = hvn_alloc_cmd(sc); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREREAD); + + rc->rc_id = atomic_inc_uint_nv(&sc->sc_rndisrid); + + req = rc->rc_req; + req->rm_type = REMOTE_NDIS_INITIALIZE_MSG; + req->rm_len = sizeof(*req); + req->rm_rid = rc->rc_id; + req->rm_ver_major = RNDIS_VERSION_MAJOR; + req->rm_ver_minor = RNDIS_VERSION_MINOR; + req->rm_max_xfersz = HVN_RNDIS_XFER_SIZE; + + rc->rc_cmplen = sizeof(*cmp); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREWRITE); + + if ((rv = hvn_rndis_cmd(sc, rc, 500)) != 0) { + DPRINTF("%s: INITIALIZE_MSG failed, error %d\n", + device_xname(sc->sc_dev), rv); + hvn_free_cmd(sc, rc); + goto errout; + } + cmp = (struct rndis_init_comp *)&rc->rc_cmp; + if (cmp->rm_status != RNDIS_STATUS_SUCCESS) { + DPRINTF("%s: failed to init RNDIS, error %#x\n", + device_xname(sc->sc_dev), cmp->rm_status); + hvn_free_cmd(sc, rc); + goto errout; + } + + hvn_free_cmd(sc, rc); + + /* Initialize RNDIS Data command */ + memset(&sc->sc_data_msg, 0, sizeof(sc->sc_data_msg)); + sc->sc_data_msg.nvs_type = HVN_NVS_TYPE_RNDIS; + sc->sc_data_msg.nvs_rndis_mtype = HVN_NVS_RNDIS_MTYPE_DATA; + sc->sc_data_msg.nvs_chim_idx = HVN_NVS_CHIM_IDX_INVALID; + + return 0; + +errout: + for (i = 0; i < HVN_RNDIS_CTLREQS; i++) { + rc = &sc->sc_cntl_msgs[i]; + if (rc->rc_req == NULL) + continue; + TAILQ_REMOVE(&sc->sc_cntl_fq, rc, rc_entry); + bus_dmamem_free(sc->sc_dmat, &rc->rc_segs, rc->rc_nsegs); + rc->rc_req = NULL; + bus_dmamap_destroy(sc->sc_dmat, rc->rc_dmap); + } + return -1; +} + +static int +hvn_set_capabilities(struct hvn_softc *sc) +{ + struct ndis_offload_params params; + size_t len = sizeof(params); + + memset(¶ms, 0, sizeof(params)); + + params.ndis_hdr.ndis_type = NDIS_OBJTYPE_DEFAULT; + if (sc->sc_ndisver < NDIS_VERSION_6_30) { + params.ndis_hdr.ndis_rev = NDIS_OFFLOAD_PARAMS_REV_2; + len = params.ndis_hdr.ndis_size = NDIS_OFFLOAD_PARAMS_SIZE_6_1; + } else { + params.ndis_hdr.ndis_rev = NDIS_OFFLOAD_PARAMS_REV_3; + len = params.ndis_hdr.ndis_size = NDIS_OFFLOAD_PARAMS_SIZE; + } + + params.ndis_ip4csum = NDIS_OFFLOAD_PARAM_TXRX; + params.ndis_tcp4csum = NDIS_OFFLOAD_PARAM_TXRX; + params.ndis_tcp6csum = NDIS_OFFLOAD_PARAM_TXRX; + if (sc->sc_ndisver >= NDIS_VERSION_6_30) { + params.ndis_udp4csum = NDIS_OFFLOAD_PARAM_TXRX; + params.ndis_udp6csum = NDIS_OFFLOAD_PARAM_TXRX; + } + + return hvn_rndis_set(sc, OID_TCP_OFFLOAD_PARAMETERS, ¶ms, len); +} + +static int +hvn_rndis_cmd(struct hvn_softc *sc, struct rndis_cmd *rc, int timo) +{ + struct hvn_nvs_rndis *msg = &rc->rc_msg; + struct rndis_msghdr *hdr = rc->rc_req; + struct vmbus_gpa sgl[1]; + int tries = 10; + int rv, s; + + KASSERT(timo > 0); + + msg->nvs_type = HVN_NVS_TYPE_RNDIS; + msg->nvs_rndis_mtype = HVN_NVS_RNDIS_MTYPE_CTRL; + msg->nvs_chim_idx = HVN_NVS_CHIM_IDX_INVALID; + + sgl[0].gpa_page = rc->rc_gpa; + sgl[0].gpa_len = hdr->rm_len; + sgl[0].gpa_ofs = 0; + + rc->rc_done = 0; + + hvn_submit_cmd(sc, rc); + + do { + rv = vmbus_channel_send_sgl(sc->sc_chan, sgl, 1, &rc->rc_msg, + sizeof(*msg), rc->rc_id); + if (rv == EAGAIN) { + if (cold) + delay(1000); + else + tsleep(rc, PRIBIO, "rndisout", 1); + } else if (rv) { + DPRINTF("%s: RNDIS operation %u send error %d\n", + device_xname(sc->sc_dev), hdr->rm_type, rv); + hvn_rollback_cmd(sc, rc); + return rv; + } + } while (rv != 0 && --tries > 0); + + if (tries == 0 && rv != 0) { + device_printf(sc->sc_dev, + "RNDIS operation %u send error %d\n", hdr->rm_type, rv); + return rv; + } + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_POSTWRITE); + + do { + if (cold) + delay(1000); + else + tsleep(rc, PRIBIO | PCATCH, "rndiscmd", 1); + s = splnet(); + hvn_nvs_intr(sc); + splx(s); + } while (--timo > 0 && rc->rc_done != 1); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_POSTREAD); + + if (rc->rc_done != 1) { + rv = timo == 0 ? ETIMEDOUT : EINTR; + if (hvn_rollback_cmd(sc, rc)) { + hvn_release_cmd(sc, rc); + rv = 0; + } else if (rv == ETIMEDOUT) { + device_printf(sc->sc_dev, + "RNDIS operation %u timed out\n", hdr->rm_type); + } + return rv; + } + + hvn_release_cmd(sc, rc); + return 0; +} + +static void +hvn_rndis_input(struct hvn_softc *sc, uint64_t tid, void *arg) +{ + struct vmbus_chanpkt_prplist *cp = arg; + uint32_t off, len, type; + int i; + + if (sc->sc_rx_ring == NULL) { + DPRINTF("%s: invalid rx ring\n", device_xname(sc->sc_dev)); + return; + } + + for (i = 0; i < cp->cp_range_cnt; i++) { + off = cp->cp_range[i].gpa_ofs; + len = cp->cp_range[i].gpa_len; + + KASSERT(off + len <= sc->sc_rx_size); + KASSERT(len >= RNDIS_HEADER_OFFSET + 4); + + memcpy(&type, sc->sc_rx_ring + off, sizeof(type)); + switch (type) { + /* data message */ + case REMOTE_NDIS_PACKET_MSG: + hvn_rxeof(sc, sc->sc_rx_ring + off, len); + break; + /* completion messages */ + case REMOTE_NDIS_INITIALIZE_CMPLT: + case REMOTE_NDIS_QUERY_CMPLT: + case REMOTE_NDIS_SET_CMPLT: + case REMOTE_NDIS_RESET_CMPLT: + case REMOTE_NDIS_KEEPALIVE_CMPLT: + hvn_rndis_complete(sc, sc->sc_rx_ring + off, len); + break; + /* notification message */ + case REMOTE_NDIS_INDICATE_STATUS_MSG: + hvn_rndis_status(sc, sc->sc_rx_ring + off, len); + break; + default: + device_printf(sc->sc_dev, + "unhandled RNDIS message type %u\n", type); + break; + } + } + + hvn_nvs_ack(sc, tid); +} + +static inline struct mbuf * +hvn_devget(struct hvn_softc *sc, void *buf, uint32_t len) +{ + struct ifnet *ifp = SC2IFP(sc); + struct mbuf *m; + size_t size = len + ETHER_ALIGN; + + MGETHDR(m, M_NOWAIT, MT_DATA); + if (m == NULL) + return NULL; + + if (size > MHLEN) { + if (size <= MCLBYTES) + MCLGET(m, M_NOWAIT); + else + MEXTMALLOC(m, size, M_NOWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_freem(m); + return NULL; + } + } + + m->m_len = m->m_pkthdr.len = size; + m_adj(m, ETHER_ALIGN); + m_copyback(m, 0, len, buf); + m_set_rcvif(m, ifp); + return m; +} + +static void +hvn_rxeof(struct hvn_softc *sc, uint8_t *buf, uint32_t len) +{ + struct ifnet *ifp = SC2IFP(sc); + struct rndis_packet_msg *pkt; + struct rndis_pktinfo *pi; + uint32_t csum, vlan; + struct mbuf *m; + + if (!(ifp->if_flags & IFF_RUNNING)) + return; + + if (len < sizeof(*pkt)) { + device_printf(sc->sc_dev, "data packet too short: %u\n", + len); + return; + } + + pkt = (struct rndis_packet_msg *)buf; + if (pkt->rm_dataoffset + pkt->rm_datalen > len) { + device_printf(sc->sc_dev, + "data packet out of bounds: %u@%u\n", pkt->rm_dataoffset, + pkt->rm_datalen); + return; + } + + if ((m = hvn_devget(sc, buf + RNDIS_HEADER_OFFSET + pkt->rm_dataoffset, + pkt->rm_datalen)) == NULL) { + ifp->if_ierrors++; + return; + } + + if (pkt->rm_pktinfooffset + pkt->rm_pktinfolen > len) { + device_printf(sc->sc_dev, + "pktinfo is out of bounds: %u@%u vs %u\n", + pkt->rm_pktinfolen, pkt->rm_pktinfooffset, len); + goto done; + } + + pi = (struct rndis_pktinfo *)(buf + RNDIS_HEADER_OFFSET + + pkt->rm_pktinfooffset); + while (pkt->rm_pktinfolen > 0) { + if (pi->rm_size > pkt->rm_pktinfolen) { + device_printf(sc->sc_dev, + "invalid pktinfo size: %u/%u\n", pi->rm_size, + pkt->rm_pktinfolen); + break; + } + + switch (pi->rm_type) { + case NDIS_PKTINFO_TYPE_CSUM: + memcpy(&csum, pi->rm_data, sizeof(csum)); + if (csum & NDIS_RXCSUM_INFO_IPCS_OK) + m->m_pkthdr.csum_flags |= M_CSUM_IPv4; + if (csum & NDIS_RXCSUM_INFO_TCPCS_OK) + m->m_pkthdr.csum_flags |= M_CSUM_TCPv4; + if (csum & NDIS_RXCSUM_INFO_UDPCS_OK) + m->m_pkthdr.csum_flags |= M_CSUM_UDPv4; + break; + case NDIS_PKTINFO_TYPE_VLAN: + memcpy(&vlan, pi->rm_data, sizeof(vlan)); + if (vlan != 0xffffffff) { + m->m_pkthdr.ether_vtag = + NDIS_VLAN_INFO_ID(vlan) | + (NDIS_VLAN_INFO_PRI(vlan) << EVL_PRIO_BITS); + m->m_flags |= M_VLANTAG; + } + break; + default: + DPRINTF("%s: unhandled pktinfo type %u\n", + device_xname(sc->sc_dev), pi->rm_type); + break; + } + + pkt->rm_pktinfolen -= pi->rm_size; + pi = (struct rndis_pktinfo *)((char *)pi + pi->rm_size); + } + + done: + if_percpuq_enqueue(sc->sc_ipq, m); +} + +static void +hvn_rndis_complete(struct hvn_softc *sc, uint8_t *buf, uint32_t len) +{ + struct rndis_cmd *rc; + uint32_t id; + + memcpy(&id, buf + RNDIS_HEADER_OFFSET, sizeof(id)); + if ((rc = hvn_complete_cmd(sc, id)) != NULL) { + if (len < rc->rc_cmplen) + device_printf(sc->sc_dev, + "RNDIS response %u too short: %u\n", id, len); + else + memcpy(&rc->rc_cmp, buf, rc->rc_cmplen); + if (len > rc->rc_cmplen && + len - rc->rc_cmplen > HVN_RNDIS_BUFSIZE) + device_printf(sc->sc_dev, + "RNDIS response %u too large: %u\n", id, len); + else if (len > rc->rc_cmplen) + memcpy(&rc->rc_cmpbuf, buf + rc->rc_cmplen, + len - rc->rc_cmplen); + rc->rc_done = 1; + wakeup(rc); + } else { + DPRINTF("%s: failed to complete RNDIS request id %u\n", + device_xname(sc->sc_dev), id); + } +} + +static int +hvn_rndis_output(struct hvn_softc *sc, struct hvn_tx_desc *txd) +{ + uint64_t rid = (uint64_t)txd->txd_id << 32; + int rv; + + rv = vmbus_channel_send_sgl(sc->sc_chan, txd->txd_sgl, txd->txd_nsge, + &sc->sc_data_msg, sizeof(sc->sc_data_msg), rid); + if (rv) { + DPRINTF("%s: RNDIS data send error %d\n", + device_xname(sc->sc_dev), rv); + return rv; + } + return 0; +} + +static void +hvn_rndis_status(struct hvn_softc *sc, uint8_t *buf, uint32_t len) +{ + struct ifnet *ifp = SC2IFP(sc); + uint32_t status; + int link_state = sc->sc_link_state; + + memcpy(&status, buf + RNDIS_HEADER_OFFSET, sizeof(status)); + switch (status) { + case RNDIS_STATUS_MEDIA_CONNECT: + sc->sc_link_state = LINK_STATE_UP; + break; + case RNDIS_STATUS_MEDIA_DISCONNECT: + sc->sc_link_state = LINK_STATE_DOWN; + break; + /* Ignore these */ + case RNDIS_STATUS_OFFLOAD_CURRENT_CONFIG: + return; + default: + DPRINTF("%s: unhandled status %#x\n", device_xname(sc->sc_dev), + status); + return; + } + if (link_state != sc->sc_link_state) + if_link_state_change(ifp, sc->sc_link_state); +} + +static int +hvn_rndis_query(struct hvn_softc *sc, uint32_t oid, void *res, size_t *length) +{ + struct rndis_cmd *rc; + struct rndis_query_req *req; + struct rndis_query_comp *cmp; + size_t olength = *length; + int rv; + + rc = hvn_alloc_cmd(sc); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREREAD); + + rc->rc_id = atomic_inc_uint_nv(&sc->sc_rndisrid); + + req = rc->rc_req; + req->rm_type = REMOTE_NDIS_QUERY_MSG; + req->rm_len = sizeof(*req); + req->rm_rid = rc->rc_id; + req->rm_oid = oid; + req->rm_infobufoffset = sizeof(*req) - RNDIS_HEADER_OFFSET; + + rc->rc_cmplen = sizeof(*cmp); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREWRITE); + + if ((rv = hvn_rndis_cmd(sc, rc, 500)) != 0) { + DPRINTF("%s: QUERY_MSG failed, error %d\n", + device_xname(sc->sc_dev), rv); + hvn_free_cmd(sc, rc); + return rv; + } + + cmp = (struct rndis_query_comp *)&rc->rc_cmp; + switch (cmp->rm_status) { + case RNDIS_STATUS_SUCCESS: + if (cmp->rm_infobuflen > olength) { + rv = EINVAL; + break; + } + memcpy(res, rc->rc_cmpbuf, cmp->rm_infobuflen); + *length = cmp->rm_infobuflen; + break; + default: + *length = 0; + rv = EIO; + break; + } + + hvn_free_cmd(sc, rc); + return rv; +} + +static int +hvn_rndis_set(struct hvn_softc *sc, uint32_t oid, void *data, size_t length) +{ + struct rndis_cmd *rc; + struct rndis_set_req *req; + struct rndis_set_comp *cmp; + int rv; + + rc = hvn_alloc_cmd(sc); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREREAD); + + rc->rc_id = atomic_inc_uint_nv(&sc->sc_rndisrid); + + req = rc->rc_req; + req->rm_type = REMOTE_NDIS_SET_MSG; + req->rm_len = sizeof(*req) + length; + req->rm_rid = rc->rc_id; + req->rm_oid = oid; + req->rm_infobufoffset = sizeof(*req) - RNDIS_HEADER_OFFSET; + + rc->rc_cmplen = sizeof(*cmp); + + if (length > 0) { + KASSERT(sizeof(*req) + length < PAGE_SIZE); + req->rm_infobuflen = length; + memcpy(req + 1, data, length); + } + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREWRITE); + + if ((rv = hvn_rndis_cmd(sc, rc, 500)) != 0) { + DPRINTF("%s: SET_MSG failed, error %d\n", + device_xname(sc->sc_dev), rv); + hvn_free_cmd(sc, rc); + return rv; + } + + cmp = (struct rndis_set_comp *)&rc->rc_cmp; + if (cmp->rm_status != RNDIS_STATUS_SUCCESS) + rv = EIO; + + hvn_free_cmd(sc, rc); + return rv; +} + +static int +hvn_rndis_open(struct hvn_softc *sc) +{ + uint32_t filter; + int rv; + + if (sc->sc_promisc) + filter = RNDIS_PACKET_TYPE_PROMISCUOUS; + else + filter = RNDIS_PACKET_TYPE_BROADCAST | + RNDIS_PACKET_TYPE_ALL_MULTICAST | + RNDIS_PACKET_TYPE_DIRECTED; + + rv = hvn_rndis_set(sc, OID_GEN_CURRENT_PACKET_FILTER, + &filter, sizeof(filter)); + if (rv) { + DPRINTF("%s: failed to set RNDIS filter to %#x\n", + device_xname(sc->sc_dev), filter); + } + return rv; +} + +static int +hvn_rndis_close(struct hvn_softc *sc) +{ + uint32_t filter = 0; + int rv; + + rv = hvn_rndis_set(sc, OID_GEN_CURRENT_PACKET_FILTER, + &filter, sizeof(filter)); + if (rv) { + DPRINTF("%s: failed to clear RNDIS filter\n", + device_xname(sc->sc_dev)); + } + return rv; +} + +static void +hvn_rndis_detach(struct hvn_softc *sc) +{ + struct rndis_cmd *rc; + struct rndis_halt_req *req; + int rv; + + rc = hvn_alloc_cmd(sc); + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREREAD); + + rc->rc_id = atomic_inc_uint_nv(&sc->sc_rndisrid); + + req = rc->rc_req; + req->rm_type = REMOTE_NDIS_HALT_MSG; + req->rm_len = sizeof(*req); + req->rm_rid = rc->rc_id; + + bus_dmamap_sync(sc->sc_dmat, rc->rc_dmap, 0, PAGE_SIZE, + BUS_DMASYNC_PREWRITE); + + if ((rv = hvn_rndis_cmd(sc, rc, 500)) != 0) { + DPRINTF("%s: HALT_MSG failed, error %d\n", + device_xname(sc->sc_dev), rv); + } + hvn_free_cmd(sc, rc); +} diff --git a/sys/dev/hyperv/if_hvnreg.h b/sys/dev/hyperv/if_hvnreg.h new file mode 100644 index 00000000000..f43cb1b7a7c --- /dev/null +++ b/sys/dev/hyperv/if_hvnreg.h @@ -0,0 +1,204 @@ +/* $NetBSD: if_hvnreg.h,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ +/* $OpenBSD: if_hvnreg.h,v 1.3 2016/09/14 17:48:28 mikeb Exp $ */ + +/*- + * Copyright (c) 2016 Microsoft Corp. + * 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. + */ + +#ifndef _IF_HVNREG_H_ +#define _IF_HVNREG_H_ + +#define HVN_NVS_PROTO_VERSION_1 0x00002 +#define HVN_NVS_PROTO_VERSION_2 0x30002 +#define HVN_NVS_PROTO_VERSION_4 0x40000 +#define HVN_NVS_PROTO_VERSION_5 0x50000 + +#define HVN_NVS_RXBUF_SIG 0x2409 +#define HVN_NVS_CHIM_SIG 0x1984 + +#define HVN_NVS_CHIM_IDX_INVALID 0xffffffff + +#define HVN_NVS_RNDIS_MTYPE_DATA 0 +#define HVN_NVS_RNDIS_MTYPE_CTRL 1 + +/* + * NVS message transacion status codes. + */ +#define HVN_NVS_STATUS_OK 1 +#define HVN_NVS_STATUS_FAILED 2 + +/* + * NVS request/response message types. + */ +#define HVN_NVS_TYPE_INIT 1 +#define HVN_NVS_TYPE_INIT_RESP 2 +#define HVN_NVS_TYPE_NDIS_INIT 100 +#define HVN_NVS_TYPE_RXBUF_CONN 101 +#define HVN_NVS_TYPE_RXBUF_CONNRESP 102 +#define HVN_NVS_TYPE_RXBUF_DISCONN 103 +#define HVN_NVS_TYPE_CHIM_CONN 104 +#define HVN_NVS_TYPE_CHIM_CONNRESP 105 +#define HVN_NVS_TYPE_CHIM_DISCONN 106 +#define HVN_NVS_TYPE_RNDIS 107 +#define HVN_NVS_TYPE_RNDIS_ACK 108 +#define HVN_NVS_TYPE_NDIS_CONF 125 +#define HVN_NVS_TYPE_VFASSOC_NOTE 128 /* notification */ +#define HVN_NVS_TYPE_SET_DATAPATH 129 +#define HVN_NVS_TYPE_SUBCH_REQ 133 +#define HVN_NVS_TYPE_SUBCH_RESP 133 /* same as SUBCH_REQ */ +#define HVN_NVS_TYPE_TXTBL_NOTE 134 /* notification */ + +/* + * Any size less than this one will _not_ work, e.g. hn_nvs_init + * only has 12B valid data, however, if only 12B data were sent, + * Hypervisor would never reply. + */ +#define HVN_NVS_REQSIZE_MIN 32 + +/* NVS message common header */ +struct hvn_nvs_hdr { + uint32_t nvs_type; +} __packed; + +struct hvn_nvs_init { + uint32_t nvs_type; /* HVN_NVS_TYPE_INIT */ + uint32_t nvs_ver_min; + uint32_t nvs_ver_max; + uint8_t nvs_rsvd[20]; +} __packed; + +struct hvn_nvs_init_resp { + uint32_t nvs_type; /* HVN_NVS_TYPE_INIT_RESP */ + uint32_t nvs_ver; /* deprecated */ + uint32_t nvs_rsvd; + uint32_t nvs_status; /* HVN_NVS_STATUS_ */ +} __packed; + +/* No reponse */ +struct hvn_nvs_ndis_conf { + uint32_t nvs_type; /* HVN_NVS_TYPE_NDIS_CONF */ + uint32_t nvs_mtu; + uint32_t nvs_rsvd; + uint64_t nvs_caps; /* HVN_NVS_NDIS_CONF_ */ + uint8_t nvs_rsvd1[12]; +} __packed; + +#define HVN_NVS_NDIS_CONF_SRIOV 0x0004 +#define HVN_NVS_NDIS_CONF_VLAN 0x0008 + +/* No response */ +struct hvn_nvs_ndis_init { + uint32_t nvs_type; /* HVN_NVS_TYPE_NDIS_INIT */ + uint32_t nvs_ndis_major; /* NDIS_VERSION_MAJOR_ */ + uint32_t nvs_ndis_minor; /* NDIS_VERSION_MINOR_ */ + uint8_t nvs_rsvd[20]; +} __packed; + +struct hvn_nvs_rxbuf_conn { + uint32_t nvs_type; /* HVN_NVS_TYPE_RXBUF_CONN */ + uint32_t nvs_gpadl; /* RXBUF vmbus GPADL */ + uint16_t nvs_sig; /* HVN_NVS_RXBUF_SIG */ + uint8_t nvs_rsvd[22]; +} __packed; + +struct hvn_nvs_rxbuf_sect { + uint32_t nvs_start; + uint32_t nvs_slotsz; + uint32_t nvs_slotcnt; + uint32_t nvs_end; +} __packed; + +struct hvn_nvs_rxbuf_conn_resp { + uint32_t nvs_type; /* HVN_NVS_TYPE_RXBUF_CONNRESP */ + uint32_t nvs_status; /* HVN_NVS_STATUS_ */ + uint32_t nvs_nsect; /* # of elem in nvs_sect */ + struct hvn_nvs_rxbuf_sect nvs_sect[0]; +} __packed; + +/* No response */ +struct hvn_nvs_rxbuf_disconn { + uint32_t nvs_type; /* HVN_NVS_TYPE_RXBUF_DISCONN */ + uint16_t nvs_sig; /* HVN_NVS_RXBUF_SIG */ + uint8_t nvs_rsvd[26]; +} __packed; + +struct hvn_nvs_chim_conn { + uint32_t nvs_type; /* HVN_NVS_TYPE_CHIM_CONN */ + uint32_t nvs_gpadl; /* chimney buf vmbus GPADL */ + uint16_t nvs_sig; /* NDIS_NVS_CHIM_SIG */ + uint8_t nvs_rsvd[22]; +} __packed; + +struct hvn_nvs_chim_conn_resp { + uint32_t nvs_type; /* HVN_NVS_TYPE_CHIM_CONNRESP */ + uint32_t nvs_status; /* HVN_NVS_STATUS_ */ + uint32_t nvs_sectsz; /* section size */ +} __packed; + +/* No response */ +struct hvn_nvs_chim_disconn { + uint32_t nvs_type; /* HVN_NVS_TYPE_CHIM_DISCONN */ + uint16_t nvs_sig; /* HVN_NVS_CHIM_SIG */ + uint8_t nvs_rsvd[26]; +} __packed; + +#define HVN_NVS_SUBCH_OP_ALLOC 1 + +struct hvn_nvs_subch_req { + uint32_t nvs_type; /* HVN_NVS_TYPE_SUBCH_REQ */ + uint32_t nvs_op; /* HVN_NVS_SUBCH_OP_ */ + uint32_t nvs_nsubch; + uint8_t nvs_rsvd[20]; +} __packed; + +struct hvn_nvs_subch_resp { + uint32_t nvs_type; /* HVN_NVS_TYPE_SUBCH_RESP */ + uint32_t nvs_status; /* HVN_NVS_STATUS_ */ + uint32_t nvs_nsubch; +} __packed; + +struct hvn_nvs_rndis { + uint32_t nvs_type; /* HVN_NVS_TYPE_RNDIS */ + uint32_t nvs_rndis_mtype;/* HVN_NVS_RNDIS_MTYPE_ */ + /* + * Chimney sending buffer index and size. + * + * NOTE: + * If nvs_chim_idx is set to HVN_NVS_CHIM_IDX_INVALID + * and nvs_chim_sz is set to 0, then chimney sending + * buffer is _not_ used by this RNDIS message. + */ + uint32_t nvs_chim_idx; + uint32_t nvs_chim_sz; + uint8_t nvs_rsvd[16]; +} __packed; + +struct hvn_nvs_rndis_ack { + uint32_t nvs_type; /* HVN_NVS_TYPE_RNDIS_ACK */ + uint32_t nvs_status; /* HVN_NVS_STATUS_ */ + uint8_t nvs_rsvd[24]; +} __packed; + +#endif /* _IF_HVNREG_H_ */ diff --git a/sys/dev/hyperv/vmbus.c b/sys/dev/hyperv/vmbus.c new file mode 100644 index 00000000000..f7cf204512e --- /dev/null +++ b/sys/dev/hyperv/vmbus.c @@ -0,0 +1,2112 @@ +/* $NetBSD: vmbus.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $ */ +/* $OpenBSD: hyperv.c,v 1.43 2017/06/27 13:56:15 mikeb Exp $ */ + +/*- + * Copyright (c) 2009-2012 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2016 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: vmbus.c,v 1.1 2019/02/15 08:54:01 nonaka Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/atomic.h> +#include <sys/bitops.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/intr.h> +#include <sys/kmem.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/xcall.h> + +#include <uvm/uvm_extern.h> + +#include <dev/hyperv/vmbusvar.h> + +#define VMBUS_GPADL_START 0xffff /* 0x10000 effectively */ + +/* Command submission flags */ +#define HCF_SLEEPOK 0x0000 +#define HCF_NOSLEEP 0x0002 /* M_NOWAIT */ +#define HCF_NOREPLY 0x0004 + +static void vmbus_attach_deferred(device_t); +static int vmbus_alloc_dma(struct vmbus_softc *); +static void vmbus_free_dma(struct vmbus_softc *); +static int vmbus_init_interrupts(struct vmbus_softc *); +static void vmbus_deinit_interrupts(struct vmbus_softc *); +static void vmbus_init_synic(void *, void *); +static void vmbus_deinit_synic(void *, void *); + +static int vmbus_connect(struct vmbus_softc *); +static int vmbus_cmd(struct vmbus_softc *, void *, size_t, void *, size_t, + int); +static int vmbus_start(struct vmbus_softc *, struct vmbus_msg *, paddr_t); +static int vmbus_reply(struct vmbus_softc *, struct vmbus_msg *); +static void vmbus_wait(struct vmbus_softc *, + int (*done)(struct vmbus_softc *, struct vmbus_msg *), + struct vmbus_msg *, void *, const char *); +static uint16_t vmbus_intr_signal(struct vmbus_softc *, paddr_t); +static void vmbus_event_proc(void *, struct cpu_info *); +static void vmbus_event_proc_compat(void *, struct cpu_info *); +static void vmbus_message_proc(void *, struct cpu_info *); +static void vmbus_message_softintr(void *); +static void vmbus_channel_response(struct vmbus_softc *, + struct vmbus_chanmsg_hdr *); +static void vmbus_channel_offer(struct vmbus_softc *, + struct vmbus_chanmsg_hdr *); +static void vmbus_channel_rescind(struct vmbus_softc *, + struct vmbus_chanmsg_hdr *); +static void vmbus_channel_delivered(struct vmbus_softc *, + struct vmbus_chanmsg_hdr *); +static int vmbus_channel_scan(struct vmbus_softc *); +static void vmbus_channel_cpu_default(struct vmbus_channel *); +static void vmbus_process_offer(struct vmbus_softc *, struct vmbus_offer *); +static struct vmbus_channel * + vmbus_channel_lookup(struct vmbus_softc *, uint32_t); +static int vmbus_channel_ring_create(struct vmbus_channel *, uint32_t); +static void vmbus_channel_ring_destroy(struct vmbus_channel *); +static void vmbus_channel_pause(struct vmbus_channel *); +static uint32_t vmbus_channel_unpause(struct vmbus_channel *); +static uint32_t vmbus_channel_ready(struct vmbus_channel *); +static int vmbus_attach_icdevs(struct vmbus_softc *); +static int vmbus_attach_devices(struct vmbus_softc *); + +static struct vmbus_softc *vmbus_sc; + +static const struct { + int hmd_response; + int hmd_request; + void (*hmd_handler)(struct vmbus_softc *, + struct vmbus_chanmsg_hdr *); +} vmbus_msg_dispatch[] = { + { 0, 0, NULL }, + { VMBUS_CHANMSG_CHOFFER, 0, vmbus_channel_offer }, + { VMBUS_CHANMSG_CHRESCIND, 0, vmbus_channel_rescind }, + { VMBUS_CHANMSG_CHREQUEST, VMBUS_CHANMSG_CHOFFER, NULL }, + { VMBUS_CHANMSG_CHOFFER_DONE, 0, vmbus_channel_delivered }, + { VMBUS_CHANMSG_CHOPEN, 0, NULL }, + { VMBUS_CHANMSG_CHOPEN_RESP, VMBUS_CHANMSG_CHOPEN, + vmbus_channel_response }, + { VMBUS_CHANMSG_CHCLOSE, 0, NULL }, + { VMBUS_CHANMSG_GPADL_CONN, 0, NULL }, + { VMBUS_CHANMSG_GPADL_SUBCONN, 0, NULL }, + { VMBUS_CHANMSG_GPADL_CONNRESP, VMBUS_CHANMSG_GPADL_CONN, + vmbus_channel_response }, + { VMBUS_CHANMSG_GPADL_DISCONN, 0, NULL }, + { VMBUS_CHANMSG_GPADL_DISCONNRESP, VMBUS_CHANMSG_GPADL_DISCONN, + vmbus_channel_response }, + { VMBUS_CHANMSG_CHFREE, 0, NULL }, + { VMBUS_CHANMSG_CONNECT, 0, NULL }, + { VMBUS_CHANMSG_CONNECT_RESP, VMBUS_CHANMSG_CONNECT, + vmbus_channel_response }, + { VMBUS_CHANMSG_DISCONNECT, 0, NULL }, +}; + +const struct hyperv_guid hyperv_guid_network = { + { 0x63, 0x51, 0x61, 0xf8, 0x3e, 0xdf, 0xc5, 0x46, + 0x91, 0x3f, 0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e } +}; + +const struct hyperv_guid hyperv_guid_ide = { + { 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 } +}; + +const struct hyperv_guid hyperv_guid_scsi = { + { 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f } +}; + +const struct hyperv_guid hyperv_guid_shutdown = { + { 0x31, 0x60, 0x0b, 0x0e, 0x13, 0x52, 0x34, 0x49, + 0x81, 0x8b, 0x38, 0xd9, 0x0c, 0xed, 0x39, 0xdb } +}; + +const struct hyperv_guid hyperv_guid_timesync = { + { 0x30, 0xe6, 0x27, 0x95, 0xae, 0xd0, 0x7b, 0x49, + 0xad, 0xce, 0xe8, 0x0a, 0xb0, 0x17, 0x5c, 0xaf } +}; + +const struct hyperv_guid hyperv_guid_heartbeat = { + { 0x39, 0x4f, 0x16, 0x57, 0x15, 0x91, 0x78, 0x4e, + 0xab, 0x55, 0x38, 0x2f, 0x3b, 0xd5, 0x42, 0x2d } +}; + +const struct hyperv_guid hyperv_guid_kvp = { + { 0xe7, 0xf4, 0xa0, 0xa9, 0x45, 0x5a, 0x96, 0x4d, + 0xb8, 0x27, 0x8a, 0x84, 0x1e, 0x8c, 0x03, 0xe6 } +}; + +const struct hyperv_guid hyperv_guid_vss = { + { 0x29, 0x2e, 0xfa, 0x35, 0x23, 0xea, 0x36, 0x42, + 0x96, 0xae, 0x3a, 0x6e, 0xba, 0xcb, 0xa4, 0x40 } +}; + +const struct hyperv_guid hyperv_guid_dynmem = { + { 0xdc, 0x74, 0x50, 0x52, 0x85, 0x89, 0xe2, 0x46, + 0x80, 0x57, 0xa3, 0x07, 0xdc, 0x18, 0xa5, 0x02 } +}; + +const struct hyperv_guid hyperv_guid_mouse = { + { 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, + 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a } +}; + +const struct hyperv_guid hyperv_guid_kbd = { + { 0x6d, 0xad, 0x12, 0xf9, 0x17, 0x2b, 0xea, 0x48, + 0xbd, 0x65, 0xf9, 0x27, 0xa6, 0x1c, 0x76, 0x84 } +}; + +const struct hyperv_guid hyperv_guid_video = { + { 0x02, 0x78, 0x0a, 0xda, 0x77, 0xe3, 0xac, 0x4a, + 0x8e, 0x77, 0x05, 0x58, 0xeb, 0x10, 0x73, 0xf8 } +}; + +const struct hyperv_guid hyperv_guid_fc = { + { 0x4a, 0xcc, 0x9b, 0x2f, 0x69, 0x00, 0xf3, 0x4a, + 0xb7, 0x6b, 0x6f, 0xd0, 0xbe, 0x52, 0x8c, 0xda } +}; + +const struct hyperv_guid hyperv_guid_fcopy = { + { 0xe3, 0x4b, 0xd1, 0x34, 0xe4, 0xde, 0xc8, 0x41, + 0x9a, 0xe7, 0x6b, 0x17, 0x49, 0x77, 0xc1, 0x92 } +}; + +const struct hyperv_guid hyperv_guid_pcie = { + { 0x1d, 0xf6, 0xc4, 0x44, 0x44, 0x44, 0x00, 0x44, + 0x9d, 0x52, 0x80, 0x2e, 0x27, 0xed, 0xe1, 0x9f } +}; + +const struct hyperv_guid hyperv_guid_netdir = { + { 0x3d, 0xaf, 0x2e, 0x8c, 0xa7, 0x32, 0x09, 0x4b, + 0xab, 0x99, 0xbd, 0x1f, 0x1c, 0x86, 0xb5, 0x01 } +}; + +const struct hyperv_guid hyperv_guid_rdesktop = { + { 0xf4, 0xac, 0x6a, 0x27, 0x15, 0xac, 0x6c, 0x42, + 0x98, 0xdd, 0x75, 0x21, 0xad, 0x3f, 0x01, 0xfe } +}; + +/* Automatic Virtual Machine Activation (AVMA) Services */ +const struct hyperv_guid hyperv_guid_avma1 = { + { 0x55, 0xb2, 0x87, 0x44, 0x8c, 0xb8, 0x3f, 0x40, + 0xbb, 0x51, 0xd1, 0xf6, 0x9c, 0xf1, 0x7f, 0x87 } +}; + +const struct hyperv_guid hyperv_guid_avma2 = { + { 0xf4, 0xba, 0x75, 0x33, 0x15, 0x9e, 0x30, 0x4b, + 0xb7, 0x65, 0x67, 0xac, 0xb1, 0x0d, 0x60, 0x7b } +}; + +const struct hyperv_guid hyperv_guid_avma3 = { + { 0xa0, 0x1f, 0x22, 0x99, 0xad, 0x24, 0xe2, 0x11, + 0xbe, 0x98, 0x00, 0x1a, 0xa0, 0x1b, 0xbf, 0x6e } +}; + +const struct hyperv_guid hyperv_guid_avma4 = { + { 0x16, 0x57, 0xe6, 0xf8, 0xb3, 0x3c, 0x06, 0x4a, + 0x9a, 0x60, 0x18, 0x89, 0xc5, 0xcc, 0xca, 0xb5 } +}; + +int +vmbus_match(device_t parent, cfdata_t cf, void *aux) +{ + + if (cf->cf_unit != 0 || + !hyperv_hypercall_enabled() || + !hyperv_synic_supported()) + return 0; + + return 1; +} + +int +vmbus_attach(struct vmbus_softc *sc) +{ + + aprint_naive("\n"); + aprint_normal(": Hyper-V VMBus\n"); + + vmbus_sc = sc; + + sc->sc_msgpool = pool_cache_init(sizeof(struct vmbus_msg), 8, 0, 0, + "hvmsg", NULL, IPL_NET, NULL, NULL, NULL); + hyperv_set_message_proc(vmbus_message_proc, sc); + + if (vmbus_alloc_dma(sc)) + goto cleanup; + + if (vmbus_init_interrupts(sc)) + goto cleanup; + + if (vmbus_connect(sc)) + goto cleanup; + + aprint_normal_dev(sc->sc_dev, "protocol %d.%d\n", + VMBUS_VERSION_MAJOR(sc->sc_proto), + VMBUS_VERSION_MINOR(sc->sc_proto)); + + if (sc->sc_proto == VMBUS_VERSION_WS2008 || + sc->sc_proto == VMBUS_VERSION_WIN7) { + hyperv_set_event_proc(vmbus_event_proc_compat, sc); + sc->sc_channel_max = VMBUS_CHAN_MAX_COMPAT; + } else { + hyperv_set_event_proc(vmbus_event_proc, sc); + sc->sc_channel_max = VMBUS_CHAN_MAX; + } + + if (vmbus_channel_scan(sc)) + goto cleanup; + + /* Attach heartbeat, KVP and other "internal" services */ + vmbus_attach_icdevs(sc); + + /* Attach devices with external drivers */ + vmbus_attach_devices(sc); + + config_interrupts(sc->sc_dev, vmbus_attach_deferred); + + return 0; + +cleanup: + vmbus_deinit_interrupts(sc); + vmbus_free_dma(sc); + return -1; +} + +static void +vmbus_attach_deferred(device_t self) +{ + struct vmbus_softc *sc = device_private(self); + + xc_wait(xc_broadcast(0, vmbus_init_synic, sc, NULL)); +} + +int +vmbus_detach(struct vmbus_softc *sc, int flags) +{ + + vmbus_deinit_interrupts(sc); + vmbus_free_dma(sc); + + return 0; +} + +static int +vmbus_alloc_dma(struct vmbus_softc *sc) +{ + CPU_INFO_ITERATOR cii; + struct cpu_info *ci; + struct vmbus_percpu_data *pd; + int i; + + /* + * Per-CPU messages and event flags. + */ + for (CPU_INFO_FOREACH(cii, ci)) { + pd = &sc->sc_percpu[cpu_index(ci)]; + + pd->simp = hyperv_dma_alloc(sc->sc_dmat, &pd->simp_dma, + PAGE_SIZE, PAGE_SIZE, 0, 1); + if (pd->simp == NULL) + return ENOMEM; + + pd->siep = hyperv_dma_alloc(sc->sc_dmat, &pd->siep_dma, + PAGE_SIZE, PAGE_SIZE, 0, 1); + if (pd->siep == NULL) + return ENOMEM; + } + + sc->sc_events = hyperv_dma_alloc(sc->sc_dmat, &sc->sc_events_dma, + PAGE_SIZE, PAGE_SIZE, 0, 1); + if (sc->sc_events == NULL) + return ENOMEM; + sc->sc_wevents = (u_long *)sc->sc_events; + sc->sc_revents = (u_long *)((uint8_t *)sc->sc_events + (PAGE_SIZE / 2)); + + for (i = 0; i < __arraycount(sc->sc_monitor); i++) { + sc->sc_monitor[i] = hyperv_dma_alloc(sc->sc_dmat, + &sc->sc_monitor_dma[i], PAGE_SIZE, PAGE_SIZE, 0, 1); + if (sc->sc_monitor[i] == NULL) + return ENOMEM; + } + + return 0; +} + +static void +vmbus_free_dma(struct vmbus_softc *sc) +{ + CPU_INFO_ITERATOR cii; + struct cpu_info *ci; + int i; + + if (sc->sc_events != NULL) { + sc->sc_events = sc->sc_wevents = sc->sc_revents = NULL; + hyperv_dma_free(sc->sc_dmat, &sc->sc_events_dma); + } + + for (i = 0; i < __arraycount(sc->sc_monitor); i++) { + sc->sc_monitor[i] = NULL; + hyperv_dma_free(sc->sc_dmat, &sc->sc_monitor_dma[i]); + } + + for (CPU_INFO_FOREACH(cii, ci)) { + struct vmbus_percpu_data *pd = &sc->sc_percpu[cpu_index(ci)]; + + if (pd->simp != NULL) { + pd->simp = NULL; + hyperv_dma_free(sc->sc_dmat, &pd->simp_dma); + } + if (pd->siep != NULL) { + pd->siep = NULL; + hyperv_dma_free(sc->sc_dmat, &pd->siep_dma); + } + } +} + +static int +vmbus_init_interrupts(struct vmbus_softc *sc) +{ + + TAILQ_INIT(&sc->sc_reqs); + mutex_init(&sc->sc_req_lock, MUTEX_DEFAULT, IPL_NET); + + TAILQ_INIT(&sc->sc_rsps); + mutex_init(&sc->sc_rsp_lock, MUTEX_DEFAULT, IPL_NET); + + sc->sc_proto = VMBUS_VERSION_WS2008; + + /* XXX event_tq */ + + sc->sc_msg_sih = softint_establish(SOFTINT_NET | SOFTINT_MPSAFE, + vmbus_message_softintr, sc); + if (sc->sc_msg_sih == NULL) + return -1; + + vmbus_init_interrupts_md(sc); + + kcpuset_create(&sc->sc_intr_cpuset, true); + if (cold) { + /* Initialize other CPUs later. */ + vmbus_init_synic(sc, NULL); + } else + xc_wait(xc_broadcast(0, vmbus_init_synic, sc, NULL)); + atomic_or_32(&sc->sc_flags, VMBUS_SCFLAG_SYNIC); + + return 0; +} + +static void +vmbus_deinit_interrupts(struct vmbus_softc *sc) +{ + + if (ISSET(sc->sc_flags, VMBUS_SCFLAG_SYNIC)) { + if (cold) + vmbus_deinit_synic(sc, NULL); + else + xc_wait(xc_broadcast(0, vmbus_deinit_synic, sc, NULL)); + atomic_and_32(&sc->sc_flags, (uint32_t)~VMBUS_SCFLAG_SYNIC); + } + + /* XXX event_tq */ + + if (sc->sc_msg_sih != NULL) { + softint_disestablish(sc->sc_msg_sih); + sc->sc_msg_sih = NULL; + } + + vmbus_deinit_interrupts_md(sc); +} + +static void +vmbus_init_synic(void *arg1, void *arg2) +{ + struct vmbus_softc *sc = arg1; + cpuid_t cpu; + int s; + + s = splhigh(); + + cpu = cpu_index(curcpu()); + if (!kcpuset_isset(sc->sc_intr_cpuset, cpu)) { + kcpuset_atomic_set(sc->sc_intr_cpuset, cpu); + vmbus_init_synic_md(sc, cpu); + } + + splx(s); +} + +static void +vmbus_deinit_synic(void *arg1, void *arg2) +{ + struct vmbus_softc *sc = arg1; + cpuid_t cpu; + int s; + + s = splhigh(); + + cpu = cpu_index(curcpu()); + if (kcpuset_isset(sc->sc_intr_cpuset, cpu)) { + vmbus_deinit_synic_md(sc, cpu); + kcpuset_atomic_clear(sc->sc_intr_cpuset, cpu); + } + + splx(s); +} + +static int +vmbus_connect(struct vmbus_softc *sc) +{ + static const uint32_t versions[] = { + VMBUS_VERSION_WIN8_1, + VMBUS_VERSION_WIN8, + VMBUS_VERSION_WIN7, + VMBUS_VERSION_WS2008 + }; + struct vmbus_chanmsg_connect cmd; + struct vmbus_chanmsg_connect_resp rsp; + int i, rv; + + memset(&cmd, 0, sizeof(cmd)); + cmd.chm_hdr.chm_type = VMBUS_CHANMSG_CONNECT; + cmd.chm_evtflags = hyperv_dma_get_paddr(&sc->sc_events_dma); + cmd.chm_mnf1 = hyperv_dma_get_paddr(&sc->sc_monitor_dma[0]); + cmd.chm_mnf2 = hyperv_dma_get_paddr(&sc->sc_monitor_dma[1]); + + memset(&rsp, 0, sizeof(rsp)); + + for (i = 0; i < __arraycount(versions); i++) { + cmd.chm_ver = versions[i]; + rv = vmbus_cmd(sc, &cmd, sizeof(cmd), &rsp, sizeof(rsp), + cold ? HCF_NOSLEEP : HCF_SLEEPOK); + if (rv) { + DPRINTF("%s: CONNECT failed\n", + device_xname(sc->sc_dev)); + return rv; + } + if (rsp.chm_done) { + atomic_or_32(&sc->sc_flags, VMBUS_SCFLAG_CONNECTED); + sc->sc_proto = versions[i]; + sc->sc_handle = VMBUS_GPADL_START; + break; + } + } + if (i == __arraycount(versions)) { + device_printf(sc->sc_dev, + "failed to negotiate protocol version\n"); + return ENXIO; + } + + return 0; +} + +static int +vmbus_cmd(struct vmbus_softc *sc, void *cmd, size_t cmdlen, void *rsp, + size_t rsplen, int flags) +{ + const int prflags = cold ? PR_NOWAIT : PR_WAITOK; + struct vmbus_msg *msg; + paddr_t pa; + int rv; + + if (cmdlen > VMBUS_MSG_DSIZE_MAX) { + device_printf(sc->sc_dev, "payload too large (%zu)\n", + cmdlen); + return EMSGSIZE; + } + + msg = pool_cache_get_paddr(sc->sc_msgpool, prflags, &pa); + if (msg == NULL) { + device_printf(sc->sc_dev, "couldn't get msgpool\n"); + return ENOMEM; + } + memset(msg, 0, sizeof(*msg)); + msg->msg_req.hc_dsize = cmdlen; + memcpy(msg->msg_req.hc_data, cmd, cmdlen); + + if (!(flags & HCF_NOREPLY)) { + msg->msg_rsp = rsp; + msg->msg_rsplen = rsplen; + } else + msg->msg_flags |= MSGF_NOQUEUE; + + if (flags & HCF_NOSLEEP) + msg->msg_flags |= MSGF_NOSLEEP; + + rv = vmbus_start(sc, msg, pa); + if (rv == 0) + rv = vmbus_reply(sc, msg); + pool_cache_put_paddr(sc->sc_msgpool, msg, pa); + return rv; +} + +static int +vmbus_start(struct vmbus_softc *sc, struct vmbus_msg *msg, paddr_t msg_pa) +{ + static const int delays[] = { + 100, 100, 100, 500, 500, 5000, 5000, 5000 + }; + const char *wchan = "hvstart"; + uint16_t status; + int i, s; + + msg->msg_req.hc_connid = VMBUS_CONNID_MESSAGE; + msg->msg_req.hc_msgtype = 1; + + if (!(msg->msg_flags & MSGF_NOQUEUE)) { + mutex_enter(&sc->sc_req_lock); + TAILQ_INSERT_TAIL(&sc->sc_reqs, msg, msg_entry); + mutex_exit(&sc->sc_req_lock); + } + + for (i = 0; i < __arraycount(delays); i++) { + status = hyperv_hypercall_post_message( + msg_pa + offsetof(struct vmbus_msg, msg_req)); + if (status == HYPERCALL_STATUS_SUCCESS) + break; + + if (msg->msg_flags & MSGF_NOSLEEP) { + delay(delays[i]); + s = splnet(); + hyperv_intr(); + splx(s); + } else + tsleep(wchan, PRIBIO, wchan, 1); + } + if (status != HYPERCALL_STATUS_SUCCESS) { + device_printf(sc->sc_dev, + "posting vmbus message failed with %d\n", status); + if (!(msg->msg_flags & MSGF_NOQUEUE)) { + mutex_enter(&sc->sc_req_lock); + TAILQ_REMOVE(&sc->sc_reqs, msg, msg_entry); + mutex_exit(&sc->sc_req_lock); + } + return EIO; + } + + return 0; +} + +static int +vmbus_reply_done(struct vmbus_softc *sc, struct vmbus_msg *msg) +{ + struct vmbus_msg *m; + + mutex_enter(&sc->sc_rsp_lock); + TAILQ_FOREACH(m, &sc->sc_rsps, msg_entry) { + if (m == msg) { + mutex_exit(&sc->sc_rsp_lock); + return 1; + } + } + mutex_exit(&sc->sc_rsp_lock); + return 0; +} + +static int +vmbus_reply(struct vmbus_softc *sc, struct vmbus_msg *msg) +{ + + if (msg->msg_flags & MSGF_NOQUEUE) + return 0; + + vmbus_wait(sc, vmbus_reply_done, msg, msg, "hvreply"); + + mutex_enter(&sc->sc_rsp_lock); + TAILQ_REMOVE(&sc->sc_rsps, msg, msg_entry); + mutex_exit(&sc->sc_rsp_lock); + + return 0; +} + +static void +vmbus_wait(struct vmbus_softc *sc, + int (*cond)(struct vmbus_softc *, struct vmbus_msg *), + struct vmbus_msg *msg, void *wchan, const char *wmsg) +{ + int s; + + while (!cond(sc, msg)) { + if (msg->msg_flags & MSGF_NOSLEEP) { + delay(1000); + s = splnet(); + hyperv_intr(); + splx(s); + } else + tsleep(wchan, PRIBIO, wmsg ? wmsg : "hvwait", 1); + } +} + +static uint16_t +vmbus_intr_signal(struct vmbus_softc *sc, paddr_t con_pa) +{ + uint64_t status; + + status = hyperv_hypercall_signal_event(con_pa); + return (uint16_t)status; +} + +#if LONG_BIT == 64 +#define ffsl(v) ffs64(v) +#elif LONG_BIT == 32 +#define ffsl(v) ffs32(v) +#else +#error unsupport LONG_BIT +#endif /* LONG_BIT */ + +static void +vmbus_event_flags_proc(struct vmbus_softc *sc, volatile u_long *revents, + int maxrow) +{ + struct vmbus_channel *ch; + u_long pending; + uint32_t chanid, chanid_base; + int row, chanid_ofs; + + for (row = 0; row < maxrow; row++) { + if (revents[row] == 0) + continue; + + pending = atomic_swap_ulong(&revents[row], 0); + chanid_base = row * LONG_BIT; + + while ((chanid_ofs = ffsl(pending)) != 0) { + chanid_ofs--; /* NOTE: ffs is 1-based */ + pending &= ~(1UL << chanid_ofs); + + chanid = chanid_base + chanid_ofs; + /* vmbus channel protocol message */ + if (chanid == 0) + continue; + + ch = vmbus_channel_lookup(sc, chanid); + if (ch == NULL) { + device_printf(sc->sc_dev, + "unhandled event on %d\n", chanid); + continue; + } + if (ch->ch_state != VMBUS_CHANSTATE_OPENED) { + device_printf(sc->sc_dev, + "channel %d is not active\n", chanid); + continue; + } + ch->ch_evcnt.ev_count++; + vmbus_channel_schedule(ch); + } + } +} + +static void +vmbus_event_proc(void *arg, struct cpu_info *ci) +{ + struct vmbus_softc *sc = arg; + struct vmbus_evtflags *evt; + + /* + * On Host with Win8 or above, the event page can be + * checked directly to get the id of the channel + * that has the pending interrupt. + */ + evt = (struct vmbus_evtflags *)sc->sc_percpu[cpu_index(ci)].siep + + VMBUS_SINT_MESSAGE; + + vmbus_event_flags_proc(sc, evt->evt_flags, + __arraycount(evt->evt_flags)); +} + +static void +vmbus_event_proc_compat(void *arg, struct cpu_info *ci) +{ + struct vmbus_softc *sc = arg; + struct vmbus_evtflags *evt; + + evt = (struct vmbus_evtflags *)sc->sc_percpu[cpu_index(ci)].siep + + VMBUS_SINT_MESSAGE; + + if (test_bit(0, &evt->evt_flags[0])) { + clear_bit(0, &evt->evt_flags[0]); + /* + * receive size is 1/2 page and divide that by 4 bytes + */ + vmbus_event_flags_proc(sc, sc->sc_revents, + VMBUS_CHAN_MAX_COMPAT / VMBUS_EVTFLAG_LEN); + } +} + +static void +vmbus_message_proc(void *arg, struct cpu_info *ci) +{ + struct vmbus_softc *sc = arg; + struct vmbus_message *msg; + + msg = (struct vmbus_message *)sc->sc_percpu[cpu_index(ci)].simp + + VMBUS_SINT_MESSAGE; + if (__predict_false(msg->msg_type != HYPERV_MSGTYPE_NONE)) { + if (__predict_true(!cold)) + softint_schedule_cpu(sc->sc_msg_sih, ci); + else + vmbus_message_softintr(sc); + } +} + +static void +vmbus_message_softintr(void *arg) +{ + struct vmbus_softc *sc = arg; + struct vmbus_message *msg; + struct vmbus_chanmsg_hdr *hdr; + uint32_t type; + cpuid_t cpu; + + cpu = cpu_index(curcpu()); + + for (;;) { + msg = (struct vmbus_message *)sc->sc_percpu[cpu].simp + + VMBUS_SINT_MESSAGE; + if (msg->msg_type == HYPERV_MSGTYPE_NONE) + break; + + hdr = (struct vmbus_chanmsg_hdr *)msg->msg_data; + type = hdr->chm_type; + if (type >= VMBUS_CHANMSG_COUNT) { + device_printf(sc->sc_dev, + "unhandled message type %u flags %#x\n", type, + msg->msg_flags); + } else { + if (vmbus_msg_dispatch[type].hmd_handler) { + vmbus_msg_dispatch[type].hmd_handler(sc, hdr); + } else { + device_printf(sc->sc_dev, + "unhandled message type %u\n", type); + } + } + + msg->msg_type = HYPERV_MSGTYPE_NONE; + membar_sync(); + if (msg->msg_flags & VMBUS_MSGFLAG_PENDING) + hyperv_send_eom(); + } +} + +static void +vmbus_channel_response(struct vmbus_softc *sc, struct vmbus_chanmsg_hdr *rsphdr) +{ + struct vmbus_msg *msg; + struct vmbus_chanmsg_hdr *reqhdr; + int req; + + req = vmbus_msg_dispatch[rsphdr->chm_type].hmd_request; + mutex_enter(&sc->sc_req_lock); + TAILQ_FOREACH(msg, &sc->sc_reqs, msg_entry) { + reqhdr = (struct vmbus_chanmsg_hdr *)&msg->msg_req.hc_data; + if (reqhdr->chm_type == req) { + TAILQ_REMOVE(&sc->sc_reqs, msg, msg_entry); + break; + } + } + mutex_exit(&sc->sc_req_lock); + if (msg != NULL) { + memcpy(msg->msg_rsp, rsphdr, msg->msg_rsplen); + mutex_enter(&sc->sc_rsp_lock); + TAILQ_INSERT_TAIL(&sc->sc_rsps, msg, msg_entry); + mutex_exit(&sc->sc_rsp_lock); + wakeup(msg); + } +} + +static void +vmbus_channel_offer(struct vmbus_softc *sc, struct vmbus_chanmsg_hdr *hdr) +{ + struct vmbus_offer *co; + + co = kmem_intr_zalloc(sizeof(*co), KM_NOSLEEP); + if (co == NULL) { + device_printf(sc->sc_dev, "couldn't allocate offer\n"); + return; + } + + memcpy(&co->co_chan, hdr, sizeof(co->co_chan)); + + mutex_enter(&sc->sc_offer_lock); + SIMPLEQ_INSERT_TAIL(&sc->sc_offers, co, co_entry); + mutex_exit(&sc->sc_offer_lock); +} + +static void +vmbus_channel_rescind(struct vmbus_softc *sc, struct vmbus_chanmsg_hdr *hdr) +{ + const struct vmbus_chanmsg_chrescind *cmd; + + cmd = (const struct vmbus_chanmsg_chrescind *)hdr; + device_printf(sc->sc_dev, "revoking channel %u\n", cmd->chm_chanid); +} + +static void +vmbus_channel_delivered(struct vmbus_softc *sc, struct vmbus_chanmsg_hdr *hdr) +{ + + atomic_or_32(&sc->sc_flags, VMBUS_SCFLAG_OFFERS_DELIVERED); + wakeup(&sc->sc_offers); +} + +static void +hyperv_guid_sprint(struct hyperv_guid *guid, char *str, size_t size) +{ + static const struct { + const struct hyperv_guid *guid; + const char *ident; + } map[] = { + { &hyperv_guid_network, "network" }, + { &hyperv_guid_ide, "ide" }, + { &hyperv_guid_scsi, "scsi" }, + { &hyperv_guid_shutdown, "shutdown" }, + { &hyperv_guid_timesync, "timesync" }, + { &hyperv_guid_heartbeat, "heartbeat" }, + { &hyperv_guid_kvp, "kvp" }, + { &hyperv_guid_vss, "vss" }, + { &hyperv_guid_dynmem, "dynamic-memory" }, + { &hyperv_guid_mouse, "mouse" }, + { &hyperv_guid_kbd, "keyboard" }, + { &hyperv_guid_video, "video" }, + { &hyperv_guid_fc, "fiber-channel" }, + { &hyperv_guid_fcopy, "file-copy" }, + { &hyperv_guid_pcie, "pcie-passthrough" }, + { &hyperv_guid_netdir, "network-direct" }, + { &hyperv_guid_rdesktop, "remote-desktop" }, + { &hyperv_guid_avma1, "avma-1" }, + { &hyperv_guid_avma2, "avma-2" }, + { &hyperv_guid_avma3, "avma-3" }, + { &hyperv_guid_avma4, "avma-4" }, + }; + int i; + + for (i = 0; i < __arraycount(map); i++) { + if (memcmp(guid, map[i].guid, sizeof(*guid)) == 0) { + strlcpy(str, map[i].ident, size); + return; + } + } + hyperv_guid2str(guid, str, size); +} + +static int +vmbus_channel_scan_done(struct vmbus_softc *sc, struct vmbus_msg *msg __unused) +{ + + return ISSET(sc->sc_flags, VMBUS_SCFLAG_OFFERS_DELIVERED); +} + +static int +vmbus_channel_scan(struct vmbus_softc *sc) +{ + struct vmbus_chanmsg_hdr hdr; + struct vmbus_chanmsg_choffer rsp; + struct vmbus_offer *co; + + SIMPLEQ_INIT(&sc->sc_offers); + mutex_init(&sc->sc_offer_lock, MUTEX_DEFAULT, IPL_NET); + + memset(&hdr, 0, sizeof(hdr)); + hdr.chm_type = VMBUS_CHANMSG_CHREQUEST; + + if (vmbus_cmd(sc, &hdr, sizeof(hdr), &rsp, sizeof(rsp), + HCF_NOREPLY | (cold ? HCF_NOSLEEP : HCF_SLEEPOK))) { + DPRINTF("%s: CHREQUEST failed\n", device_xname(sc->sc_dev)); + return -1; + } + + vmbus_wait(sc, vmbus_channel_scan_done, (struct vmbus_msg *)&hdr, + &sc->sc_offers, "hvscan"); + + TAILQ_INIT(&sc->sc_channels); + mutex_init(&sc->sc_channel_lock, MUTEX_DEFAULT, IPL_NET); + + mutex_enter(&sc->sc_offer_lock); + while (!SIMPLEQ_EMPTY(&sc->sc_offers)) { + co = SIMPLEQ_FIRST(&sc->sc_offers); + SIMPLEQ_REMOVE_HEAD(&sc->sc_offers, co_entry); + mutex_exit(&sc->sc_offer_lock); + + vmbus_process_offer(sc, co); + kmem_free(co, sizeof(*co)); + + mutex_enter(&sc->sc_offer_lock); + } + mutex_exit(&sc->sc_offer_lock); + + return 0; +} + +static struct vmbus_channel * +vmbus_channel_alloc(struct vmbus_softc *sc) +{ + struct vmbus_channel *ch; + + ch = kmem_zalloc(sizeof(*ch), cold ? KM_NOSLEEP : KM_SLEEP); + + ch->ch_monprm = hyperv_dma_alloc(sc->sc_dmat, &ch->ch_monprm_dma, + sizeof(*ch->ch_monprm), 8, 0, 1); + if (ch->ch_monprm == NULL) { + device_printf(sc->sc_dev, "monprm alloc failed\n"); + kmem_free(ch, sizeof(*ch)); + return NULL; + } + memset(ch->ch_monprm, 0, sizeof(*ch->ch_monprm)); + + ch->ch_refs = 1; + ch->ch_sc = sc; + mutex_init(&ch->ch_subchannel_lock, MUTEX_DEFAULT, IPL_NET); + TAILQ_INIT(&ch->ch_subchannels); + + ch->ch_state = VMBUS_CHANSTATE_CLOSED; + + return ch; +} + +static void +vmbus_channel_free(struct vmbus_channel *ch) +{ + struct vmbus_softc *sc = ch->ch_sc; + + KASSERTMSG(TAILQ_EMPTY(&ch->ch_subchannels) && + ch->ch_subchannel_count == 0, "still owns sub-channels"); + KASSERTMSG(ch->ch_state == 0 || ch->ch_state == VMBUS_CHANSTATE_CLOSED, + "free busy channel"); + KASSERTMSG(ch->ch_refs == 0, "channel %u: invalid refcnt %d", + ch->ch_id, ch->ch_refs); + + hyperv_dma_free(sc->sc_dmat, &ch->ch_monprm_dma); + mutex_destroy(&ch->ch_subchannel_lock); + /* XXX ch_evcnt */ + softint_disestablish(ch->ch_taskq); + kmem_free(ch, sizeof(*ch)); +} + +static int +vmbus_channel_add(struct vmbus_channel *nch) +{ + struct vmbus_softc *sc = nch->ch_sc; + struct vmbus_channel *ch; + u_int refs; + + if (nch->ch_id == 0) { + device_printf(sc->sc_dev, "got channel 0 offer, discard\n"); + return EINVAL; + } else if (nch->ch_id >= sc->sc_channel_max) { + device_printf(sc->sc_dev, "invalid channel %u offer\n", + nch->ch_id); + return EINVAL; + } + + mutex_enter(&sc->sc_channel_lock); + TAILQ_FOREACH(ch, &sc->sc_channels, ch_entry) { + if (!memcmp(&ch->ch_type, &nch->ch_type, sizeof(ch->ch_type)) && + !memcmp(&ch->ch_inst, &nch->ch_inst, sizeof(ch->ch_inst))) + break; + } + if (VMBUS_CHAN_ISPRIMARY(nch)) { + if (ch == NULL) { + TAILQ_INSERT_TAIL(&sc->sc_channels, nch, ch_entry); + mutex_exit(&sc->sc_channel_lock); + goto done; + } else { + mutex_exit(&sc->sc_channel_lock); + device_printf(sc->sc_dev, + "duplicated primary channel%u\n", nch->ch_id); + return EINVAL; + } + } else { + if (ch == NULL) { + mutex_exit(&sc->sc_channel_lock); + device_printf(sc->sc_dev, "no primary channel%u\n", + nch->ch_id); + return EINVAL; + } + } + mutex_exit(&sc->sc_channel_lock); + + KASSERT(!VMBUS_CHAN_ISPRIMARY(nch)); + KASSERT(ch != NULL); + + refs = atomic_add_int_nv(&nch->ch_refs, 1); + KASSERT(refs == 1); + + nch->ch_primary_channel = ch; + nch->ch_dev = ch->ch_dev; + + mutex_enter(&ch->ch_subchannel_lock); + TAILQ_INSERT_TAIL(&ch->ch_subchannels, nch, ch_subentry); + ch->ch_subchannel_count++; + mutex_exit(&ch->ch_subchannel_lock); + wakeup(ch); + +done: + vmbus_channel_cpu_default(nch); + + return 0; +} + +void +vmbus_channel_cpu_set(struct vmbus_channel *ch, int cpu) +{ + struct vmbus_softc *sc = ch->ch_sc; + + KASSERTMSG(cpu >= 0 && cpu < ncpu, "invalid cpu %d", cpu); + + if (sc->sc_proto == VMBUS_VERSION_WS2008 || + sc->sc_proto == VMBUS_VERSION_WIN7) { + /* Only cpu0 is supported */ + cpu = 0; + } + + ch->ch_cpuid = cpu; + ch->ch_vcpu = sc->sc_percpu[cpu].vcpuid; +} + +void +vmbus_channel_cpu_rr(struct vmbus_channel *ch) +{ + static uint32_t vmbus_channel_nextcpu; + int cpu; + + cpu = atomic_add_32_nv(&vmbus_channel_nextcpu, 1) % ncpu; + vmbus_channel_cpu_set(ch, cpu); +} + +static void +vmbus_channel_cpu_default(struct vmbus_channel *ch) +{ + + /* + * By default, pin the channel to cpu0. Devices having + * special channel-cpu mapping requirement should call + * vmbus_channel_cpu_{set,rr}(). + */ + vmbus_channel_cpu_set(ch, 0); +} + +static void +vmbus_process_offer(struct vmbus_softc *sc, struct vmbus_offer *co) +{ + struct vmbus_channel *ch; + + ch = vmbus_channel_alloc(sc); + if (ch == NULL) { + device_printf(sc->sc_dev, "allocate channel %u failed\n", + co->co_chan.chm_chanid); + return; + } + + /* + * By default we setup state to enable batched reading. + * A specific service can choose to disable this prior + * to opening the channel. + */ + ch->ch_flags |= CHF_BATCHED; + + hyperv_guid_sprint(&co->co_chan.chm_chtype, ch->ch_ident, + sizeof(ch->ch_ident)); + + ch->ch_monprm->mp_connid = VMBUS_CONNID_EVENT; + if (sc->sc_proto > VMBUS_VERSION_WS2008) + ch->ch_monprm->mp_connid = co->co_chan.chm_connid; + + if (co->co_chan.chm_flags1 & VMBUS_CHOFFER_FLAG1_HASMNF) { + ch->ch_mgroup = co->co_chan.chm_montrig / VMBUS_MONTRIG_LEN; + ch->ch_mindex = co->co_chan.chm_montrig % VMBUS_MONTRIG_LEN; + ch->ch_flags |= CHF_MONITOR; + } + + ch->ch_id = co->co_chan.chm_chanid; + ch->ch_subidx = co->co_chan.chm_subidx; + + memcpy(&ch->ch_type, &co->co_chan.chm_chtype, sizeof(ch->ch_type)); + memcpy(&ch->ch_inst, &co->co_chan.chm_chinst, sizeof(ch->ch_inst)); + + if (VMBUS_CHAN_ISPRIMARY(ch)) { + /* set primary channel mgmt wq */ + } else { + /* set sub channel mgmt wq */ + } + + if (vmbus_channel_add(ch) != 0) { + vmbus_channel_free(ch); + return; + } + + ch->ch_state = VMBUS_CHANSTATE_OFFERED; + +#ifdef HYPERV_DEBUG + printf("%s: channel %u: \"%s\"", device_xname(sc->sc_dev), ch->ch_id, + ch->ch_ident); + if (ch->ch_flags & CHF_MONITOR) + printf(", monitor %u\n", co->co_chan.chm_montrig); + else + printf("\n"); +#endif +} + +static int +vmbus_channel_release(struct vmbus_channel *ch) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanmsg_chfree cmd; + int rv; + + memset(&cmd, 0, sizeof(cmd)); + cmd.chm_hdr.chm_type = VMBUS_CHANMSG_CHFREE; + cmd.chm_chanid = ch->ch_id; + + rv = vmbus_cmd(sc, &cmd, sizeof(cmd), NULL, 0, + HCF_NOREPLY | (cold ? HCF_NOSLEEP : HCF_SLEEPOK)); + if (rv) { + DPRINTF("%s: CHFREE failed with %d\n", device_xname(sc->sc_dev), + rv); + } + return rv; +} + +struct vmbus_channel ** +vmbus_subchannel_get(struct vmbus_channel *prich, int cnt) +{ + struct vmbus_channel **ret, *ch; + int i; + + KASSERT(cnt > 0); + + ret = kmem_alloc(sizeof(struct vmbus_channel *) * cnt, + cold ? KM_NOSLEEP : KM_SLEEP); + + mutex_enter(&prich->ch_subchannel_lock); + + while (prich->ch_subchannel_count < cnt) + /* XXX use condvar(9) instead of mtsleep */ + mtsleep(prich, PRIBIO, "hvvmsubch", 0, + &prich->ch_subchannel_lock); + + i = 0; + TAILQ_FOREACH(ch, &prich->ch_subchannels, ch_subentry) { + ret[i] = ch; /* XXX inc refs */ + + if (++i == cnt) + break; + } + + mutex_exit(&prich->ch_subchannel_lock); + + return ret; +} + +void +vmbus_subchannel_put(struct vmbus_channel **subch, int cnt) +{ + + kmem_free(subch, sizeof(struct vmbus_channel *) * cnt); +} + +static struct vmbus_channel * +vmbus_channel_lookup(struct vmbus_softc *sc, uint32_t relid) +{ + struct vmbus_channel *ch; + + TAILQ_FOREACH(ch, &sc->sc_channels, ch_entry) { + if (ch->ch_id == relid) + return ch; + } + return NULL; +} + +static int +vmbus_channel_ring_create(struct vmbus_channel *ch, uint32_t buflen) +{ + struct vmbus_softc *sc = ch->ch_sc; + + buflen = roundup(buflen, PAGE_SIZE) + sizeof(struct vmbus_bufring); + ch->ch_ring_size = 2 * buflen; + ch->ch_ring = hyperv_dma_alloc(sc->sc_dmat, &ch->ch_ring_dma, + ch->ch_ring_size, PAGE_SIZE, 0, 1); /* page aligned memory */ + if (ch->ch_ring == NULL) { + device_printf(sc->sc_dev, + "failed to allocate channel ring\n"); + return ENOMEM; + } + + memset(&ch->ch_wrd, 0, sizeof(ch->ch_wrd)); + ch->ch_wrd.rd_ring = (struct vmbus_bufring *)ch->ch_ring; + ch->ch_wrd.rd_size = buflen; + ch->ch_wrd.rd_dsize = buflen - sizeof(struct vmbus_bufring); + mutex_init(&ch->ch_wrd.rd_lock, MUTEX_DEFAULT, IPL_NET); + + memset(&ch->ch_rrd, 0, sizeof(ch->ch_rrd)); + ch->ch_rrd.rd_ring = (struct vmbus_bufring *)((uint8_t *)ch->ch_ring + + buflen); + ch->ch_rrd.rd_size = buflen; + ch->ch_rrd.rd_dsize = buflen - sizeof(struct vmbus_bufring); + mutex_init(&ch->ch_rrd.rd_lock, MUTEX_DEFAULT, IPL_NET); + + if (vmbus_handle_alloc(ch, &ch->ch_ring_dma, ch->ch_ring_size, + &ch->ch_ring_gpadl)) { + device_printf(sc->sc_dev, + "failed to obtain a PA handle for the ring\n"); + vmbus_channel_ring_destroy(ch); + return ENOMEM; + } + + return 0; +} + +static void +vmbus_channel_ring_destroy(struct vmbus_channel *ch) +{ + struct vmbus_softc *sc = ch->ch_sc; + + hyperv_dma_free(sc->sc_dmat, &ch->ch_ring_dma); + ch->ch_ring = NULL; + vmbus_handle_free(ch, ch->ch_ring_gpadl); + + mutex_destroy(&ch->ch_wrd.rd_lock); + memset(&ch->ch_wrd, 0, sizeof(ch->ch_wrd)); + mutex_destroy(&ch->ch_rrd.rd_lock); + memset(&ch->ch_rrd, 0, sizeof(ch->ch_rrd)); +} + +int +vmbus_channel_open(struct vmbus_channel *ch, size_t buflen, void *udata, + size_t udatalen, void (*handler)(void *), void *arg) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanmsg_chopen cmd; + struct vmbus_chanmsg_chopen_resp rsp; + int rv = EINVAL; + + if (ch->ch_ring == NULL && + (rv = vmbus_channel_ring_create(ch, buflen))) { + DPRINTF("%s: failed to create channel ring\n", + device_xname(sc->sc_dev)); + return rv; + } + + memset(&cmd, 0, sizeof(cmd)); + cmd.chm_hdr.chm_type = VMBUS_CHANMSG_CHOPEN; + cmd.chm_openid = ch->ch_id; + cmd.chm_chanid = ch->ch_id; + cmd.chm_gpadl = ch->ch_ring_gpadl; + cmd.chm_txbr_pgcnt = atop(ch->ch_wrd.rd_size); + cmd.chm_vcpuid = ch->ch_vcpu; + if (udata && udatalen > 0) + memcpy(cmd.chm_udata, udata, udatalen); + + memset(&rsp, 0, sizeof(rsp)); + + ch->ch_handler = handler; + ch->ch_ctx = arg; + ch->ch_state = VMBUS_CHANSTATE_OPENED; + + rv = vmbus_cmd(sc, &cmd, sizeof(cmd), &rsp, sizeof(rsp), + cold ? HCF_NOSLEEP : HCF_SLEEPOK); + if (rv) { + vmbus_channel_ring_destroy(ch); + DPRINTF("%s: CHOPEN failed with %d\n", device_xname(sc->sc_dev), + rv); + ch->ch_handler = NULL; + ch->ch_ctx = NULL; + ch->ch_state = VMBUS_CHANSTATE_OFFERED; + return rv; + } + return 0; +} + +static void +vmbus_channel_detach(struct vmbus_channel *ch) +{ + u_int refs; + + refs = atomic_add_int_nv(&ch->ch_refs, -1); + if (refs == 1) { + /* XXX on workqueue? */ + if (VMBUS_CHAN_ISPRIMARY(ch)) { + vmbus_channel_release(ch); + vmbus_channel_free(ch); + } else { + struct vmbus_channel *prich = ch->ch_primary_channel; + + vmbus_channel_release(ch); + + mutex_enter(&prich->ch_subchannel_lock); + TAILQ_REMOVE(&prich->ch_subchannels, ch, ch_subentry); + prich->ch_subchannel_count--; + mutex_exit(&prich->ch_subchannel_lock); + wakeup(prich); + + vmbus_channel_free(ch); + } + } +} + +static int +vmbus_channel_close_internal(struct vmbus_channel *ch) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanmsg_chclose cmd; + int rv; + + memset(&cmd, 0, sizeof(cmd)); + cmd.chm_hdr.chm_type = VMBUS_CHANMSG_CHCLOSE; + cmd.chm_chanid = ch->ch_id; + + ch->ch_state = VMBUS_CHANSTATE_CLOSING; + rv = vmbus_cmd(sc, &cmd, sizeof(cmd), NULL, 0, + HCF_NOREPLY | (cold ? HCF_NOSLEEP : HCF_SLEEPOK)); + if (rv) { + DPRINTF("%s: CHCLOSE failed with %d\n", + device_xname(sc->sc_dev), rv); + return rv; + } + ch->ch_state = VMBUS_CHANSTATE_CLOSED; + vmbus_channel_ring_destroy(ch); + return 0; +} + +int +vmbus_channel_close_direct(struct vmbus_channel *ch) +{ + int rv; + + rv = vmbus_channel_close_internal(ch); + if (!VMBUS_CHAN_ISPRIMARY(ch)) + vmbus_channel_detach(ch); + return rv; +} + +int +vmbus_channel_close(struct vmbus_channel *ch) +{ + struct vmbus_channel **subch; + int i, cnt, rv; + + if (!VMBUS_CHAN_ISPRIMARY(ch)) + return 0; + + cnt = ch->ch_subchannel_count; + if (cnt > 0) { + subch = vmbus_subchannel_get(ch, cnt); + for (i = 0; i < ch->ch_subchannel_count; i++) { + rv = vmbus_channel_close_internal(subch[i]); + (void) rv; /* XXX */ + vmbus_channel_detach(ch); + } + vmbus_subchannel_put(subch, cnt); + } + + return vmbus_channel_close_internal(ch); +} + +static inline void +vmbus_channel_setevent(struct vmbus_softc *sc, struct vmbus_channel *ch) +{ + struct vmbus_mon_trig *mtg; + + /* Each uint32_t represents 32 channels */ + set_bit(ch->ch_id, sc->sc_wevents); + if (ch->ch_flags & CHF_MONITOR) { + mtg = &sc->sc_monitor[1]->mnf_trigs[ch->ch_mgroup]; + set_bit(ch->ch_mindex, &mtg->mt_pending); + } else + vmbus_intr_signal(sc, hyperv_dma_get_paddr(&ch->ch_monprm_dma)); +} + +static void +vmbus_channel_intr(void *arg) +{ + struct vmbus_channel *ch = arg; + + if (vmbus_channel_ready(ch)) + ch->ch_handler(ch->ch_ctx); + + if (vmbus_channel_unpause(ch) == 0) + return; + + vmbus_channel_pause(ch); + vmbus_channel_schedule(ch); +} + +int +vmbus_channel_setdeferred(struct vmbus_channel *ch, const char *name) +{ + + ch->ch_taskq = softint_establish(SOFTINT_NET | SOFTINT_MPSAFE, + vmbus_channel_intr, ch); + if (ch->ch_taskq == NULL) + return -1; + return 0; +} + +void +vmbus_channel_schedule(struct vmbus_channel *ch) +{ + + if (ch->ch_handler) { + if (!cold && (ch->ch_flags & CHF_BATCHED)) { + vmbus_channel_pause(ch); + softint_schedule(ch->ch_taskq); + } else + ch->ch_handler(ch->ch_ctx); + } +} + +static __inline void +vmbus_ring_put(struct vmbus_ring_data *wrd, uint8_t *data, uint32_t datalen) +{ + int left = MIN(datalen, wrd->rd_dsize - wrd->rd_prod); + + memcpy(&wrd->rd_ring->br_data[wrd->rd_prod], data, left); + memcpy(&wrd->rd_ring->br_data[0], data + left, datalen - left); + wrd->rd_prod += datalen; + if (wrd->rd_prod >= wrd->rd_dsize) + wrd->rd_prod -= wrd->rd_dsize; +} + +static inline void +vmbus_ring_get(struct vmbus_ring_data *rrd, uint8_t *data, uint32_t datalen, + int peek) +{ + int left = MIN(datalen, rrd->rd_dsize - rrd->rd_cons); + + memcpy(data, &rrd->rd_ring->br_data[rrd->rd_cons], left); + memcpy(data + left, &rrd->rd_ring->br_data[0], datalen - left); + if (!peek) { + rrd->rd_cons += datalen; + if (rrd->rd_cons >= rrd->rd_dsize) + rrd->rd_cons -= rrd->rd_dsize; + } +} + +static __inline void +vmbus_ring_avail(struct vmbus_ring_data *rd, uint32_t *towrite, + uint32_t *toread) +{ + uint32_t ridx = rd->rd_ring->br_rindex; + uint32_t widx = rd->rd_ring->br_windex; + uint32_t r, w; + + if (widx >= ridx) + w = rd->rd_dsize - (widx - ridx); + else + w = ridx - widx; + r = rd->rd_dsize - w; + if (towrite) + *towrite = w; + if (toread) + *toread = r; +} + +static int +vmbus_ring_write(struct vmbus_ring_data *wrd, struct iovec *iov, int iov_cnt, + int *needsig) +{ + uint64_t indices = 0; + uint32_t avail, oprod, datalen = sizeof(indices); + int i; + + for (i = 0; i < iov_cnt; i++) + datalen += iov[i].iov_len; + + KASSERT(datalen <= wrd->rd_dsize); + + vmbus_ring_avail(wrd, &avail, NULL); + if (avail <= datalen) { + DPRINTF("%s: avail %u datalen %u\n", __func__, avail, datalen); + return EAGAIN; + } + + oprod = wrd->rd_prod; + + for (i = 0; i < iov_cnt; i++) + vmbus_ring_put(wrd, iov[i].iov_base, iov[i].iov_len); + + indices = (uint64_t)oprod << 32; + vmbus_ring_put(wrd, (uint8_t *)&indices, sizeof(indices)); + + membar_sync(); + wrd->rd_ring->br_windex = wrd->rd_prod; + membar_sync(); + + /* Signal when the ring transitions from being empty to non-empty */ + if (wrd->rd_ring->br_imask == 0 && + wrd->rd_ring->br_rindex == oprod) + *needsig = 1; + else + *needsig = 0; + + return 0; +} + +int +vmbus_channel_send(struct vmbus_channel *ch, void *data, uint32_t datalen, + uint64_t rid, int type, uint32_t flags) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanpkt cp; + struct iovec iov[3]; + uint32_t pktlen, pktlen_aligned; + uint64_t zeropad = 0; + int rv, needsig = 0; + + pktlen = sizeof(cp) + datalen; + pktlen_aligned = roundup(pktlen, sizeof(uint64_t)); + + cp.cp_hdr.cph_type = type; + cp.cp_hdr.cph_flags = flags; + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_hlen, sizeof(cp)); + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_tlen, pktlen_aligned); + cp.cp_hdr.cph_tid = rid; + + iov[0].iov_base = &cp; + iov[0].iov_len = sizeof(cp); + + iov[1].iov_base = data; + iov[1].iov_len = datalen; + + iov[2].iov_base = &zeropad; + iov[2].iov_len = pktlen_aligned - pktlen; + + mutex_enter(&ch->ch_wrd.rd_lock); + rv = vmbus_ring_write(&ch->ch_wrd, iov, 3, &needsig); + mutex_exit(&ch->ch_wrd.rd_lock); + if (rv == 0 && needsig) + vmbus_channel_setevent(sc, ch); + + return rv; +} + +int +vmbus_channel_send_sgl(struct vmbus_channel *ch, struct vmbus_gpa *sgl, + uint32_t nsge, void *data, uint32_t datalen, uint64_t rid) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanpkt_sglist cp; + struct iovec iov[4]; + uint32_t buflen, pktlen, pktlen_aligned; + uint64_t zeropad = 0; + int rv, needsig = 0; + + buflen = sizeof(struct vmbus_gpa) * nsge; + pktlen = sizeof(cp) + datalen + buflen; + pktlen_aligned = roundup(pktlen, sizeof(uint64_t)); + + cp.cp_hdr.cph_type = VMBUS_CHANPKT_TYPE_GPA; + cp.cp_hdr.cph_flags = VMBUS_CHANPKT_FLAG_RC; + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_hlen, sizeof(cp) + buflen); + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_tlen, pktlen_aligned); + cp.cp_hdr.cph_tid = rid; + cp.cp_gpa_cnt = nsge; + cp.cp_rsvd = 0; + + iov[0].iov_base = &cp; + iov[0].iov_len = sizeof(cp); + + iov[1].iov_base = sgl; + iov[1].iov_len = buflen; + + iov[2].iov_base = data; + iov[2].iov_len = datalen; + + iov[3].iov_base = &zeropad; + iov[3].iov_len = pktlen_aligned - pktlen; + + mutex_enter(&ch->ch_wrd.rd_lock); + rv = vmbus_ring_write(&ch->ch_wrd, iov, 4, &needsig); + mutex_exit(&ch->ch_wrd.rd_lock); + if (rv == 0 && needsig) + vmbus_channel_setevent(sc, ch); + + return rv; +} + +int +vmbus_channel_send_prpl(struct vmbus_channel *ch, struct vmbus_gpa_range *prpl, + uint32_t nprp, void *data, uint32_t datalen, uint64_t rid) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanpkt_prplist cp; + struct iovec iov[4]; + uint32_t buflen, pktlen, pktlen_aligned; + uint64_t zeropad = 0; + int rv, needsig = 0; + + buflen = sizeof(struct vmbus_gpa_range) * (nprp + 1); + pktlen = sizeof(cp) + datalen + buflen; + pktlen_aligned = roundup(pktlen, sizeof(uint64_t)); + + cp.cp_hdr.cph_type = VMBUS_CHANPKT_TYPE_GPA; + cp.cp_hdr.cph_flags = VMBUS_CHANPKT_FLAG_RC; + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_hlen, sizeof(cp) + buflen); + VMBUS_CHANPKT_SETLEN(cp.cp_hdr.cph_tlen, pktlen_aligned); + cp.cp_hdr.cph_tid = rid; + cp.cp_range_cnt = 1; + cp.cp_rsvd = 0; + + iov[0].iov_base = &cp; + iov[0].iov_len = sizeof(cp); + + iov[1].iov_base = prpl; + iov[1].iov_len = buflen; + + iov[2].iov_base = data; + iov[2].iov_len = datalen; + + iov[3].iov_base = &zeropad; + iov[3].iov_len = pktlen_aligned - pktlen; + + mutex_enter(&ch->ch_wrd.rd_lock); + rv = vmbus_ring_write(&ch->ch_wrd, iov, 4, &needsig); + mutex_exit(&ch->ch_wrd.rd_lock); + if (rv == 0 && needsig) + vmbus_channel_setevent(sc, ch); + + return rv; +} + +static int +vmbus_ring_peek(struct vmbus_ring_data *rrd, void *data, uint32_t datalen) +{ + uint32_t avail; + + KASSERT(datalen <= rrd->rd_dsize); + + vmbus_ring_avail(rrd, NULL, &avail); + if (avail < datalen) + return EAGAIN; + + vmbus_ring_get(rrd, (uint8_t *)data, datalen, 1); + return 0; +} + +static int +vmbus_ring_read(struct vmbus_ring_data *rrd, void *data, uint32_t datalen, + uint32_t offset) +{ + uint64_t indices; + uint32_t avail; + + KASSERT(datalen <= rrd->rd_dsize); + + vmbus_ring_avail(rrd, NULL, &avail); + if (avail < datalen) { + DPRINTF("%s: avail %u datalen %u\n", __func__, avail, datalen); + return EAGAIN; + } + + if (offset) { + rrd->rd_cons += offset; + if (rrd->rd_cons >= rrd->rd_dsize) + rrd->rd_cons -= rrd->rd_dsize; + } + + vmbus_ring_get(rrd, (uint8_t *)data, datalen, 0); + vmbus_ring_get(rrd, (uint8_t *)&indices, sizeof(indices), 0); + + membar_sync(); + rrd->rd_ring->br_rindex = rrd->rd_cons; + + return 0; +} + +int +vmbus_channel_recv(struct vmbus_channel *ch, void *data, uint32_t datalen, + uint32_t *rlen, uint64_t *rid, int raw) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanpkt_hdr cph; + uint32_t offset, pktlen; + int rv; + + *rlen = 0; + + mutex_enter(&ch->ch_rrd.rd_lock); + + if ((rv = vmbus_ring_peek(&ch->ch_rrd, &cph, sizeof(cph))) != 0) { + mutex_exit(&ch->ch_rrd.rd_lock); + return rv; + } + + offset = raw ? 0 : VMBUS_CHANPKT_GETLEN(cph.cph_hlen); + pktlen = VMBUS_CHANPKT_GETLEN(cph.cph_tlen) - offset; + if (pktlen > datalen) { + mutex_exit(&ch->ch_rrd.rd_lock); + device_printf(sc->sc_dev, "%s: pktlen %u datalen %u\n", + __func__, pktlen, datalen); + return EINVAL; + } + + rv = vmbus_ring_read(&ch->ch_rrd, data, pktlen, offset); + if (rv == 0) { + *rlen = pktlen; + *rid = cph.cph_tid; + } + + mutex_exit(&ch->ch_rrd.rd_lock); + + return rv; +} + +static inline void +vmbus_ring_mask(struct vmbus_ring_data *rd) +{ + + membar_sync(); + rd->rd_ring->br_imask = 1; + membar_sync(); +} + +static inline void +vmbus_ring_unmask(struct vmbus_ring_data *rd) +{ + + membar_sync(); + rd->rd_ring->br_imask = 0; + membar_sync(); +} + +static void +vmbus_channel_pause(struct vmbus_channel *ch) +{ + + vmbus_ring_mask(&ch->ch_rrd); +} + +static uint32_t +vmbus_channel_unpause(struct vmbus_channel *ch) +{ + uint32_t avail; + + vmbus_ring_unmask(&ch->ch_rrd); + vmbus_ring_avail(&ch->ch_rrd, NULL, &avail); + + return avail; +} + +static uint32_t +vmbus_channel_ready(struct vmbus_channel *ch) +{ + uint32_t avail; + + vmbus_ring_avail(&ch->ch_rrd, NULL, &avail); + + return avail; +} + +/* How many PFNs can be referenced by the header */ +#define VMBUS_NPFNHDR ((VMBUS_MSG_DSIZE_MAX - \ + sizeof(struct vmbus_chanmsg_gpadl_conn)) / sizeof(uint64_t)) + +/* How many PFNs can be referenced by the body */ +#define VMBUS_NPFNBODY ((VMBUS_MSG_DSIZE_MAX - \ + sizeof(struct vmbus_chanmsg_gpadl_subconn)) / sizeof(uint64_t)) + +int +vmbus_handle_alloc(struct vmbus_channel *ch, const struct hyperv_dma *dma, + uint32_t buflen, uint32_t *handle) +{ + const int prflags = cold ? PR_NOWAIT : PR_WAITOK; + const int kmemflags = cold ? KM_NOSLEEP : KM_SLEEP; + const int msgflags = cold ? MSGF_NOSLEEP : 0; + const int hcflags = cold ? HCF_NOSLEEP : HCF_SLEEPOK; + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanmsg_gpadl_conn *hdr; + struct vmbus_chanmsg_gpadl_subconn *cmd; + struct vmbus_chanmsg_gpadl_connresp rsp; + struct vmbus_msg *msg; + int i, j, last, left, rv; + int bodylen = 0, ncmds = 0, pfn = 0; + uint64_t *frames; + paddr_t pa; + uint8_t *body; + /* Total number of pages to reference */ + int total = atop(buflen); + /* Number of pages that will fit the header */ + int inhdr = MIN(total, VMBUS_NPFNHDR); + + KASSERT((buflen & PAGE_MASK) == 0); + KASSERT(buflen == (uint32_t)dma->map->dm_mapsize); + + msg = pool_cache_get_paddr(sc->sc_msgpool, prflags, &pa); + if (msg == NULL) + return ENOMEM; + + /* Prepare array of frame addresses */ + frames = kmem_zalloc(total * sizeof(*frames), kmemflags); + if (frames == NULL) { + pool_cache_put_paddr(sc->sc_msgpool, msg, pa); + return ENOMEM; + } + for (i = 0, j = 0; i < dma->map->dm_nsegs && j < total; i++) { + bus_dma_segment_t *seg = &dma->map->dm_segs[i]; + bus_addr_t addr = seg->ds_addr; + + KASSERT((addr & PAGE_MASK) == 0); + KASSERT((seg->ds_len & PAGE_MASK) == 0); + + while (addr < seg->ds_addr + seg->ds_len && j < total) { + frames[j++] = atop(addr); + addr += PAGE_SIZE; + } + } + + memset(msg, 0, sizeof(*msg)); + msg->msg_req.hc_dsize = sizeof(struct vmbus_chanmsg_gpadl_conn) + + inhdr * sizeof(uint64_t); + hdr = (struct vmbus_chanmsg_gpadl_conn *)msg->msg_req.hc_data; + msg->msg_rsp = &rsp; + msg->msg_rsplen = sizeof(rsp); + msg->msg_flags = msgflags; + + left = total - inhdr; + + /* Allocate additional gpadl_body structures if required */ + if (left > 0) { + ncmds = MAX(1, left / VMBUS_NPFNBODY + left % VMBUS_NPFNBODY); + bodylen = ncmds * VMBUS_MSG_DSIZE_MAX; + body = kmem_zalloc(bodylen, kmemflags); + if (body == NULL) { + kmem_free(frames, total * sizeof(*frames)); + pool_cache_put_paddr(sc->sc_msgpool, msg, pa); + return ENOMEM; + } + } + + *handle = atomic_add_int_nv(&sc->sc_handle, 1); + + hdr->chm_hdr.chm_type = VMBUS_CHANMSG_GPADL_CONN; + hdr->chm_chanid = ch->ch_id; + hdr->chm_gpadl = *handle; + + /* Single range for a contiguous buffer */ + hdr->chm_range_cnt = 1; + hdr->chm_range_len = sizeof(struct vmbus_gpa_range) + total * + sizeof(uint64_t); + hdr->chm_range.gpa_ofs = 0; + hdr->chm_range.gpa_len = buflen; + + /* Fit as many pages as possible into the header */ + for (i = 0; i < inhdr; i++) + hdr->chm_range.gpa_page[i] = frames[pfn++]; + + for (i = 0; i < ncmds; i++) { + cmd = (struct vmbus_chanmsg_gpadl_subconn *)(body + + VMBUS_MSG_DSIZE_MAX * i); + cmd->chm_hdr.chm_type = VMBUS_CHANMSG_GPADL_SUBCONN; + cmd->chm_gpadl = *handle; + last = MIN(left, VMBUS_NPFNBODY); + for (j = 0; j < last; j++) + cmd->chm_gpa_page[j] = frames[pfn++]; + left -= last; + } + + rv = vmbus_start(sc, msg, pa); + if (rv != 0) { + DPRINTF("%s: GPADL_CONN failed\n", device_xname(sc->sc_dev)); + goto out; + } + for (i = 0; i < ncmds; i++) { + int cmdlen = sizeof(*cmd); + cmd = (struct vmbus_chanmsg_gpadl_subconn *)(body + + VMBUS_MSG_DSIZE_MAX * i); + /* Last element can be short */ + if (i == ncmds - 1) + cmdlen += last * sizeof(uint64_t); + else + cmdlen += VMBUS_NPFNBODY * sizeof(uint64_t); + rv = vmbus_cmd(sc, cmd, cmdlen, NULL, 0, HCF_NOREPLY | hcflags); + if (rv != 0) { + DPRINTF("%s: GPADL_SUBCONN (iteration %d/%d) failed " + "with %d\n", device_xname(sc->sc_dev), i, ncmds, + rv); + goto out; + } + } + rv = vmbus_reply(sc, msg); + if (rv != 0) { + DPRINTF("%s: GPADL allocation failed with %d\n", + device_xname(sc->sc_dev), rv); + } + + out: + if (bodylen > 0) + kmem_free(body, bodylen); + kmem_free(frames, total * sizeof(*frames)); + pool_cache_put_paddr(sc->sc_msgpool, msg, pa); + if (rv) + return rv; + + KASSERT(*handle == rsp.chm_gpadl); + + return 0; +} + +void +vmbus_handle_free(struct vmbus_channel *ch, uint32_t handle) +{ + struct vmbus_softc *sc = ch->ch_sc; + struct vmbus_chanmsg_gpadl_disconn cmd; + struct vmbus_chanmsg_gpadl_disconn rsp; + int rv; + + memset(&cmd, 0, sizeof(cmd)); + cmd.chm_hdr.chm_type = VMBUS_CHANMSG_GPADL_DISCONN; + cmd.chm_chanid = ch->ch_id; + cmd.chm_gpadl = handle; + + rv = vmbus_cmd(sc, &cmd, sizeof(cmd), &rsp, sizeof(rsp), + cold ? HCF_NOSLEEP : HCF_SLEEPOK); + if (rv) { + DPRINTF("%s: GPADL_DISCONN failed with %d\n", + device_xname(sc->sc_dev), rv); + } +} + +static int +vmbus_attach_print(void *aux, const char *name) +{ + struct vmbus_attach_args *aa = aux; + + if (name) + printf("\"%s\" at %s", aa->aa_ident, name); + + return UNCONF; +} + +static int +vmbus_attach_icdevs(struct vmbus_softc *sc) +{ + struct vmbus_dev *dv; + struct vmbus_channel *ch; + + SLIST_INIT(&sc->sc_icdevs); + mutex_init(&sc->sc_icdev_lock, MUTEX_DEFAULT, IPL_NET); + + TAILQ_FOREACH(ch, &sc->sc_channels, ch_entry) { + if (ch->ch_state != VMBUS_CHANSTATE_OFFERED) + continue; + if (ch->ch_flags & CHF_MONITOR) + continue; + + dv = kmem_zalloc(sizeof(*dv), cold ? KM_NOSLEEP : KM_SLEEP); + if (dv == NULL) { + device_printf(sc->sc_dev, + "failed to allocate ic device object\n"); + return ENOMEM; + } + dv->dv_aa.aa_type = &ch->ch_type; + dv->dv_aa.aa_inst = &ch->ch_inst; + dv->dv_aa.aa_ident = ch->ch_ident; + dv->dv_aa.aa_chan = ch; + mutex_enter(&sc->sc_icdev_lock); + SLIST_INSERT_HEAD(&sc->sc_icdevs, dv, dv_entry); + mutex_exit(&sc->sc_icdev_lock); + ch->ch_dev = config_found_ia(sc->sc_dev, "hypervvmbus", + &dv->dv_aa, vmbus_attach_print); + } + return 0; +} + +static int +vmbus_attach_devices(struct vmbus_softc *sc) +{ + struct vmbus_dev *dv; + struct vmbus_channel *ch; + + SLIST_INIT(&sc->sc_devs); + mutex_init(&sc->sc_dev_lock, MUTEX_DEFAULT, IPL_NET); + + TAILQ_FOREACH(ch, &sc->sc_channels, ch_entry) { + if (ch->ch_state != VMBUS_CHANSTATE_OFFERED) + continue; + if (!(ch->ch_flags & CHF_MONITOR)) + continue; + + dv = kmem_zalloc(sizeof(*dv), cold ? KM_NOSLEEP : KM_SLEEP); + if (dv == NULL) { + device_printf(sc->sc_dev, + "failed to allocate device object\n"); + return ENOMEM; + } + dv->dv_aa.aa_type = &ch->ch_type; + dv->dv_aa.aa_inst = &ch->ch_inst; + dv->dv_aa.aa_ident = ch->ch_ident; + dv->dv_aa.aa_chan = ch; + mutex_enter(&sc->sc_dev_lock); + SLIST_INSERT_HEAD(&sc->sc_devs, dv, dv_entry); + mutex_exit(&sc->sc_dev_lock); + ch->ch_dev = config_found_ia(sc->sc_dev, "hypervvmbus", + &dv->dv_aa, vmbus_attach_print); + } + return 0; +} + +MODULE(MODULE_CLASS_DRIVER, vmbus, "hyperv"); + +#ifdef _MODULE +#include "ioconf.c" +#endif + +static int +vmbus_modcmd(modcmd_t cmd, void *aux) +{ + int rv = 0; + + switch (cmd) { + case MODULE_CMD_INIT: +#ifdef _MODULE + rv = config_init_component(cfdriver_ioconf_vmbus, + cfattach_ioconf_vmbus, cfdata_ioconf_vmbus); +#endif + break; + + case MODULE_CMD_FINI: +#ifdef _MODULE + rv = config_fini_component(cfdriver_ioconf_vmbus, + cfattach_ioconf_vmbus, cfdata_ioconf_vmbus); +#endif + break; + + default: + rv = ENOTTY; + break; + } + + return rv; +} diff --git a/sys/dev/hyperv/vmbusic.c b/sys/dev/hyperv/vmbusic.c new file mode 100644 index 00000000000..8743759e5f6 --- /dev/null +++ b/sys/dev/hyperv/vmbusic.c @@ -0,0 +1,271 @@ +/* $NetBSD: vmbusic.c,v 1.1 2019/02/15 08:54:02 nonaka Exp $ */ +/*- + * Copyright (c) 2014,2016 Microsoft Corp. + * 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. + */ + +#include <sys/cdefs.h> +#ifdef __KERNEL_RCSID +__KERNEL_RCSID(0, "$NetBSD: vmbusic.c,v 1.1 2019/02/15 08:54:02 nonaka Exp $"); +#endif +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/sys/dev/hyperv/utilities/vmbus_ic.c 310317 2016-12-20 07:14:24Z sephe $"); +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/reboot.h> + +#include <dev/hyperv/vmbusvar.h> +#include <dev/hyperv/vmbusicreg.h> +#include <dev/hyperv/vmbusicvar.h> + +#define VMBUS_IC_BRSIZE (4 * PAGE_SIZE) + +#define VMBUS_IC_VERCNT 2 +#define VMBUS_IC_NEGOSZ \ + offsetof(struct vmbus_icmsg_negotiate, ic_ver[VMBUS_IC_VERCNT]) +__CTASSERT(VMBUS_IC_NEGOSZ < VMBUS_IC_BRSIZE); + + +int +vmbusic_probe(struct vmbus_attach_args *aa, const struct hyperv_guid *guid) +{ + + if (memcmp(aa->aa_type, guid, sizeof(*aa->aa_type)) != 0) + return 0; + return 1; +} + +int +vmbusic_attach(device_t dv, struct vmbus_attach_args *aa, + vmbus_channel_callback_t cb) +{ + struct vmbusic_softc *sc = device_private(dv); + + sc->sc_dev = dv; + sc->sc_chan = aa->aa_chan; + + sc->sc_buflen = VMBUS_IC_BRSIZE; + sc->sc_buf = kmem_alloc(sc->sc_buflen, cold ? KM_NOSLEEP : KM_SLEEP); + + /* + * These services are not performance critical and do not need + * batched reading. Furthermore, some services such as KVP can + * only handle one message from the host at a time. + * Turn off batched reading for all util drivers before we open the + * channel. + */ + sc->sc_chan->ch_flags &= ~CHF_BATCHED; + + if (vmbus_channel_open(sc->sc_chan, sc->sc_buflen, NULL, 0, cb, sc)) { + aprint_error_dev(dv, "failed to open channel\n"); + kmem_free(sc->sc_buf, sc->sc_buflen); + sc->sc_buf = NULL; + return ENXIO; + } + + return 0; +} + +int +vmbusic_detach(device_t dv, int flags) +{ + struct vmbusic_softc *sc = device_private(dv); + int error; + + error = vmbus_channel_close(sc->sc_chan); + if (error != 0) + return error; + + if (sc->sc_buf != NULL) { + kmem_free(sc->sc_buf, sc->sc_buflen); + sc->sc_buf = NULL; + } + + if (sc->sc_log != NULL) { + sysctl_teardown(&sc->sc_log); + sc->sc_log = NULL; + } + + return 0; +} + +int +vmbusic_negotiate(struct vmbusic_softc *sc, void *data, uint32_t *dlen0, + uint32_t fw_ver, uint32_t msg_ver) +{ + struct vmbus_icmsg_negotiate *nego; + uint32_t sel_fw_ver = 0, sel_msg_ver = 0; + int i, cnt, dlen = *dlen0, error; + bool has_fw_ver, has_msg_ver = false; + + /* + * Preliminary message verification. + */ + if (dlen < sizeof(*nego)) { + device_printf(sc->sc_dev, "truncated ic negotiate, len %d\n", + dlen); + return EINVAL; + } + nego = data; + + if (nego->ic_fwver_cnt == 0) { + device_printf(sc->sc_dev, "ic negotiate does not contain " + "framework version %u\n", nego->ic_fwver_cnt); + return EINVAL; + } + if (nego->ic_msgver_cnt == 0) { + device_printf(sc->sc_dev, "ic negotiate does not contain " + "message version %u\n", nego->ic_msgver_cnt); + return EINVAL; + } + + cnt = nego->ic_fwver_cnt + nego->ic_msgver_cnt; + if (dlen < offsetof(struct vmbus_icmsg_negotiate, ic_ver[cnt])) { + device_printf(sc->sc_dev, "ic negotiate does not contain " + "versions %d\n", dlen); + return EINVAL; + } + + error = EOPNOTSUPP; + + /* + * Find the best match framework version. + */ + has_fw_ver = false; + for (i = 0; i < nego->ic_fwver_cnt; ++i) { + if (VMBUS_ICVER_LE(nego->ic_ver[i], fw_ver)) { + if (!has_fw_ver) { + sel_fw_ver = nego->ic_ver[i]; + has_fw_ver = true; + } else if (VMBUS_ICVER_GT(nego->ic_ver[i], + sel_fw_ver)) { + sel_fw_ver = nego->ic_ver[i]; + } + } + } + if (!has_fw_ver) { + device_printf(sc->sc_dev, "failed to select framework " + "version\n"); + goto done; + } + + /* + * Fine the best match message version. + */ + has_msg_ver = false; + for (i = nego->ic_fwver_cnt; + i < nego->ic_fwver_cnt + nego->ic_msgver_cnt; ++i) { + if (VMBUS_ICVER_LE(nego->ic_ver[i], msg_ver)) { + if (!has_msg_ver) { + sel_msg_ver = nego->ic_ver[i]; + has_msg_ver = true; + } else if (VMBUS_ICVER_GT(nego->ic_ver[i], + sel_msg_ver)) { + sel_msg_ver = nego->ic_ver[i]; + } + } + } + if (!has_msg_ver) { + device_printf(sc->sc_dev, "failed to select message version\n"); + goto done; + } + + error = 0; +done: + if (bootverbose || !has_fw_ver || !has_msg_ver) { + if (has_fw_ver) { + device_printf(sc->sc_dev, + "sel framework version: %u.%u\n", + VMBUS_ICVER_MAJOR(sel_fw_ver), + VMBUS_ICVER_MINOR(sel_fw_ver)); + } + for (i = 0; i < nego->ic_fwver_cnt; i++) { + device_printf(sc->sc_dev, + "supp framework version: %u.%u\n", + VMBUS_ICVER_MAJOR(nego->ic_ver[i]), + VMBUS_ICVER_MINOR(nego->ic_ver[i])); + } + + if (has_msg_ver) { + device_printf(sc->sc_dev, + "sel message version: %u.%u\n", + VMBUS_ICVER_MAJOR(sel_msg_ver), + VMBUS_ICVER_MINOR(sel_msg_ver)); + } + for (i = nego->ic_fwver_cnt; + i < nego->ic_fwver_cnt + nego->ic_msgver_cnt; i++) { + device_printf(sc->sc_dev, + "supp message version: %u.%u\n", + VMBUS_ICVER_MAJOR(nego->ic_ver[i]), + VMBUS_ICVER_MINOR(nego->ic_ver[i])); + } + } + if (error) + return error; + + /* Record the selected versions. */ + sc->sc_fwver = sel_fw_ver; + sc->sc_msgver = sel_msg_ver; + + /* One framework version. */ + nego->ic_fwver_cnt = 1; + nego->ic_ver[0] = sel_fw_ver; + + /* One message version. */ + nego->ic_msgver_cnt = 1; + nego->ic_ver[1] = sel_msg_ver; + + /* Update data size. */ + nego->ic_hdr.ic_dsize = VMBUS_IC_NEGOSZ - + sizeof(struct vmbus_icmsg_hdr); + + /* Update total size, if necessary. */ + if (dlen < VMBUS_IC_NEGOSZ) + *dlen0 = VMBUS_IC_NEGOSZ; + + return 0; +} + +int +vmbusic_sendresp(struct vmbusic_softc *sc, struct vmbus_channel *chan, + void *data, uint32_t dlen, uint64_t rid) +{ + struct vmbus_icmsg_hdr *hdr; + int error; + + KASSERTMSG(dlen >= sizeof(*hdr), "invalid data length %d", dlen); + hdr = data; + + hdr->ic_flags = VMBUS_ICMSG_FLAG_TRANSACTION|VMBUS_ICMSG_FLAG_RESPONSE; + error = vmbus_channel_send(chan, data, dlen, rid, + VMBUS_CHANPKT_TYPE_INBAND, 0); + if (error != 0) + device_printf(sc->sc_dev, "resp send failed: %d\n", error); + return error; +} diff --git a/sys/dev/hyperv/vmbusicreg.h b/sys/dev/hyperv/vmbusicreg.h new file mode 100644 index 00000000000..3ffaa1cc0b2 --- /dev/null +++ b/sys/dev/hyperv/vmbusicreg.h @@ -0,0 +1,248 @@ +/* $NetBSD: vmbusicreg.h,v 1.1 2019/02/15 08:54:02 nonaka Exp $ */ +/* $OpenBSD: hypervicreg.h,v 1.6 2017/11/07 16:49:42 mikeb Exp $ */ + +/*- + * Copyright (c) 2016 Microsoft Corp. + * 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. + * + * $FreeBSD: head/sys/dev/hyperv/utilities/vmbus_icreg.h 305281 2016-09-02 06:23:28Z sephe $ + */ + +#ifndef _VMBUSICREG_H_ +#define _VMBUSICREG_H_ + +#define VMBUS_IC_BUFRINGSIZE (4 * PAGE_SIZE) + +#define VMBUS_ICMSG_TYPE_NEGOTIATE 0 +#define VMBUS_ICMSG_TYPE_HEARTBEAT 1 +#define VMBUS_ICMSG_TYPE_KVP 2 +#define VMBUS_ICMSG_TYPE_SHUTDOWN 3 +#define VMBUS_ICMSG_TYPE_TIMESYNC 4 +#define VMBUS_ICMSG_TYPE_VSS 5 + +#define VMBUS_ICMSG_STATUS_OK 0x00000000 +#define VMBUS_ICMSG_STATUS_FAIL 0x80004005 + +#define VMBUS_IC_VERSION(major, minor) ((major) | (((uint32_t)(minor)) << 16)) +#define VMBUS_ICVER_MAJOR(ver) ((ver) & 0xffff) +#define VMBUS_ICVER_MINOR(ver) (((ver) & 0xffff0000) >> 16) +#define VMBUS_ICVER_SWAP(ver) \ + ((VMBUS_ICVER_MAJOR((ver)) << 16) | VMBUS_ICVER_MINOR((ver))) +#define VMBUS_ICVER_LE(v1, v2) \ + (VMBUS_ICVER_SWAP((v1)) <= VMBUS_ICVER_SWAP((v2))) +#define VMBUS_ICVER_GT(v1, v2) \ + (VMBUS_ICVER_SWAP((v1)) > VMBUS_ICVER_SWAP((v2))) + +struct vmbus_pipe_hdr { + uint32_t ph_flags; + uint32_t ph_msgsz; +} __packed; + +struct vmbus_icmsg_hdr { + struct vmbus_pipe_hdr ic_pipe; + uint32_t ic_fwver; /* framework version */ + uint16_t ic_type; + uint32_t ic_msgver; /* message version */ + uint16_t ic_dsize; /* data size */ + uint32_t ic_status; /* VMBUS_ICMSG_STATUS_ */ + uint8_t ic_tid; + uint8_t ic_flags; /* VMBUS_ICMSG_FLAG_ */ + uint8_t ic_rsvd[2]; +} __packed; + +#define VMBUS_ICMSG_FLAG_TRANSACTION 1 +#define VMBUS_ICMSG_FLAG_REQUEST 2 +#define VMBUS_ICMSG_FLAG_RESPONSE 4 + +/* VMBUS_ICMSG_TYPE_NEGOTIATE */ +struct vmbus_icmsg_negotiate { + struct vmbus_icmsg_hdr ic_hdr; + uint16_t ic_fwver_cnt; + uint16_t ic_msgver_cnt; + uint32_t ic_rsvd; + /* + * This version array contains two set of supported + * versions: + * - The first set consists of #ic_fwver_cnt supported framework + * versions. + * - The second set consists of #ic_msgver_cnt supported message + * versions. + */ + uint32_t ic_ver[0]; +} __packed; + +/* VMBUS_ICMSG_TYPE_HEARTBEAT */ +struct vmbus_icmsg_heartbeat { + struct vmbus_icmsg_hdr ic_hdr; + uint64_t ic_seq; + uint32_t ic_rsvd[8]; +} __packed; + +#define VMBUS_ICMSG_HEARTBEAT_SIZE_MIN \ + offsetof(struct vmbus_icmsg_heartbeat, ic_rsvd[0]) + +/* VMBUS_ICMSG_TYPE_SHUTDOWN */ +struct vmbus_icmsg_shutdown { + struct vmbus_icmsg_hdr ic_hdr; + uint32_t ic_code; + uint32_t ic_timeo; + uint32_t ic_haltflags; + uint8_t ic_msg[2048]; +} __packed; + +#define VMBUS_ICMSG_SHUTDOWN_SIZE_MIN \ + offsetof(struct vmbus_icmsg_shutdown, ic_msg[0]) + +/* VMBUS_ICMSG_TYPE_TIMESYNC */ +struct vmbus_icmsg_timesync { + struct vmbus_icmsg_hdr ic_hdr; + uint64_t ic_hvtime; + uint64_t ic_vmtime; + uint64_t ic_rtt; + uint8_t ic_tsflags; /* VMBUS_ICMSG_TS_FLAG_ */ +} __packed; + +/* VMBUS_ICMSG_TYPE_TIMESYNC, MSGVER4 */ +struct vmbus_icmsg_timesync4 { + struct vmbus_icmsg_hdr ic_hdr; + uint64_t ic_hvtime; + uint64_t ic_sent_tc; + uint8_t ic_tsflags; /* VMBUS_ICMSG_TS_FLAG_ */ + uint8_t ic_rsvd[5]; +} __packed; + +#define VMBUS_ICMSG_TS_FLAG_SYNC 0x01 +#define VMBUS_ICMSG_TS_FLAG_SAMPLE 0x02 + +#define VMBUS_ICMSG_TS_BASE 116444736000000000ULL + +/* Registry value types */ +#define VMBUS_KVP_REG_SZ 1 +#define VMBUS_KVP_REG_U32 4 +#define VMBUS_KVP_REG_U64 8 + +/* Hyper-V status codes */ +#define VMBUS_KVP_S_OK 0x00000000 +#define VMBUS_KVP_E_FAIL 0x80004005 +#define VMBUS_KVP_S_CONT 0x80070103 + +#define VMBUS_KVP_MAX_VAL_SIZE 2048 +#define VMBUS_KVP_MAX_KEY_SIZE 512 + +enum vmbus_kvp_op { + VMBUS_KVP_OP_GET = 0, + VMBUS_KVP_OP_SET, + VMBUS_KVP_OP_DELETE, + VMBUS_KVP_OP_ENUMERATE, + VMBUS_KVP_OP_GET_IP_INFO, + VMBUS_KVP_OP_SET_IP_INFO, + VMBUS_KVP_OP_COUNT +}; + +enum vmbus_kvp_pool { + VMBUS_KVP_POOL_EXTERNAL = 0, + VMBUS_KVP_POOL_GUEST, + VMBUS_KVP_POOL_AUTO, + VMBUS_KVP_POOL_AUTO_EXTERNAL, + VMBUS_KVP_POOL_COUNT +}; + +union vmbus_kvp_hdr { + struct { + uint8_t kvu_op; + uint8_t kvu_pool; + uint16_t kvu_pad; + } req; + struct { + uint32_t kvu_err; + } rsp; +#define kvh_op req.kvu_op +#define kvh_pool req.kvu_pool +#define kvh_err rsp.kvu_err +} __packed; + +struct vmbus_kvp_msg_val { + uint32_t kvm_valtype; + uint32_t kvm_keylen; + uint32_t kvm_vallen; + uint8_t kvm_key[VMBUS_KVP_MAX_KEY_SIZE]; + uint8_t kvm_val[VMBUS_KVP_MAX_VAL_SIZE]; +} __packed; + +struct vmbus_kvp_msg_enum { + uint32_t kvm_index; + uint32_t kvm_valtype; + uint32_t kvm_keylen; + uint32_t kvm_vallen; + uint8_t kvm_key[VMBUS_KVP_MAX_KEY_SIZE]; + uint8_t kvm_val[VMBUS_KVP_MAX_VAL_SIZE]; +} __packed; + +struct vmbus_kvp_msg_del { + uint32_t kvm_keylen; + uint8_t kvm_key[VMBUS_KVP_MAX_KEY_SIZE]; +} __packed; + +#define ADDR_FAMILY_NONE 0x00 +#define ADDR_FAMILY_IPV4 0x01 +#define ADDR_FAMILY_IPV6 0x02 + +#define MAX_MAC_ADDR_SIZE 256 +#define MAX_IP_ADDR_SIZE 2048 +#define MAX_GATEWAY_SIZE 1024 + +struct vmbus_kvp_msg_addr { + uint8_t kvm_mac[MAX_MAC_ADDR_SIZE]; + uint8_t kvm_family; + uint8_t kvm_dhcp; + uint8_t kvm_addr[MAX_IP_ADDR_SIZE]; + uint8_t kvm_netmask[MAX_IP_ADDR_SIZE]; + uint8_t kvm_gateway[MAX_GATEWAY_SIZE]; + uint8_t kvm_dns[MAX_IP_ADDR_SIZE]; +} __packed; + +union vmbus_kvp_msg { + struct vmbus_kvp_msg_val kvm_val; + struct vmbus_kvp_msg_enum kvm_enum; + struct vmbus_kvp_msg_del kvm_del; +}; + +struct vmbus_icmsg_kvp { + struct vmbus_icmsg_hdr ic_hdr; + union vmbus_kvp_hdr ic_kvh; + union vmbus_kvp_msg ic_kvm; +} __packed; + +struct vmbus_icmsg_kvp_addr { + struct vmbus_icmsg_hdr ic_hdr; + struct { + struct { + uint8_t kvu_op; + uint8_t kvu_pool; + } req; + } ic_kvh; + struct vmbus_kvp_msg_addr ic_kvm; +} __packed; + +#endif /* _VMBUSICREG_H_ */ diff --git a/sys/dev/hyperv/vmbusicvar.h b/sys/dev/hyperv/vmbusicvar.h new file mode 100644 index 00000000000..6871a761890 --- /dev/null +++ b/sys/dev/hyperv/vmbusicvar.h @@ -0,0 +1,68 @@ +/* $NetBSD: vmbusicvar.h,v 1.1 2019/02/15 08:54:02 nonaka Exp $ */ +/* $OpenBSD: hypervic.c,v 1.13 2017/08/10 15:25:57 mikeb Exp $ */ + +/*- + * Copyright (c) 2009-2016 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * Copyright (c) 2016 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. + */ + +#ifndef _VMBUSICVAR_H_ +#define _VMBUSICVAR_H_ + +#include <sys/sysctl.h> + +#include <dev/hyperv/vmbusvar.h> + +struct vmbusic_softc { + device_t sc_dev; + + struct vmbus_channel *sc_chan; + + void *sc_buf; + size_t sc_buflen; + + uint32_t sc_fwver; /* framework version */ + uint32_t sc_msgver; /* message version */ + + struct sysctllog *sc_log; +}; + +int vmbusic_probe(struct vmbus_attach_args *, const struct hyperv_guid *); +int vmbusic_attach(device_t, struct vmbus_attach_args *, + vmbus_channel_callback_t); +int vmbusic_detach(device_t, int); + +int vmbusic_negotiate(struct vmbusic_softc *, void *, uint32_t *, uint32_t, + uint32_t); +int vmbusic_sendresp(struct vmbusic_softc *, struct vmbus_channel *, + void *, uint32_t, uint64_t); + +#endif /* _VMBUSICVAR_H_ */ diff --git a/sys/dev/hyperv/vmbusvar.h b/sys/dev/hyperv/vmbusvar.h new file mode 100644 index 00000000000..41b35746c08 --- /dev/null +++ b/sys/dev/hyperv/vmbusvar.h @@ -0,0 +1,273 @@ +/* $NetBSD: vmbusvar.h,v 1.1 2019/02/15 08:54:02 nonaka Exp $ */ +/* $OpenBSD: hypervvar.h,v 1.13 2017/06/23 19:05:42 mikeb Exp $ */ + +/* + * Copyright (c) 2016 Mike Belopuhov <mike@esdenera.com> + * + * 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. + */ + +#ifndef _VMBUSVAR_H_ +#define _VMBUSVAR_H_ + +#include <sys/param.h> +#include <sys/device.h> +#include <sys/atomic.h> +#include <sys/bus.h> +#include <sys/evcnt.h> +#include <sys/kcpuset.h> +#include <sys/mutex.h> +#include <sys/pool.h> + +#include <dev/hyperv/hypervreg.h> +#include <dev/hyperv/hypervvar.h> + +/* #define HYPERV_DEBUG */ + +#ifdef HYPERV_DEBUG +#define DPRINTF(x...) printf(x) +#else +#define DPRINTF(x...) +#endif + +typedef void (*vmbus_channel_callback_t)(void *); + +struct vmbus_softc; + +struct vmbus_msg { + uint64_t msg_flags; +#define MSGF_NOSLEEP __BIT(0) +#define MSGF_NOQUEUE __BIT(1) +#define MSGF_ORPHANED __BIT(2) + struct hyperv_hypercall_postmsg_in msg_req __aligned(8); + void *msg_rsp; + size_t msg_rsplen; + TAILQ_ENTRY(vmbus_msg) msg_entry; +}; +__CTASSERT((offsetof(struct vmbus_msg, msg_req) % 8) == 0); +TAILQ_HEAD(vmbus_queue, vmbus_msg); + +struct vmbus_offer { + struct vmbus_chanmsg_choffer co_chan; + SIMPLEQ_ENTRY(vmbus_offer) co_entry; +}; +SIMPLEQ_HEAD(vmbus_offers, vmbus_offer); + +struct vmbus_ring_data { + struct vmbus_bufring *rd_ring; + uint32_t rd_size; + kmutex_t rd_lock; + uint32_t rd_prod; + uint32_t rd_cons; + uint32_t rd_dsize; +}; + +struct vmbus_channel; +TAILQ_HEAD(vmbus_channels, vmbus_channel); + +struct vmbus_channel { + struct vmbus_softc *ch_sc; + device_t ch_dev; + u_int ch_refs; + + int ch_state; +#define VMBUS_CHANSTATE_OFFERED 1 +#define VMBUS_CHANSTATE_OPENED 2 +#define VMBUS_CHANSTATE_CLOSING 3 +#define VMBUS_CHANSTATE_CLOSED 4 + uint32_t ch_id; + uint16_t ch_subidx; + + struct hyperv_guid ch_type; + struct hyperv_guid ch_inst; + char ch_ident[38]; + + void *ch_ring; + uint32_t ch_ring_gpadl; + u_long ch_ring_size; + struct hyperv_dma ch_ring_dma; + + struct vmbus_ring_data ch_wrd; + struct vmbus_ring_data ch_rrd; + + int ch_cpuid; + uint32_t ch_vcpu; + + void (*ch_handler)(void *); + void *ch_ctx; + struct evcnt ch_evcnt; + void *ch_taskq; + + uint32_t ch_flags; +#define CHF_BATCHED __BIT(0) +#define CHF_MONITOR __BIT(1) + + uint8_t ch_mgroup; + uint8_t ch_mindex; + struct hyperv_mon_param *ch_monprm; + struct hyperv_dma ch_monprm_dma; + + TAILQ_ENTRY(vmbus_channel) ch_entry; + + kmutex_t ch_subchannel_lock; + struct vmbus_channels ch_subchannels; + u_int ch_subchannel_count; + TAILQ_ENTRY(vmbus_channel) ch_subentry; + struct vmbus_channel *ch_primary_channel; +}; + +#define VMBUS_CHAN_ISPRIMARY(chan) ((chan)->ch_subidx == 0) + +struct vmbus_attach_args { + struct hyperv_guid *aa_type; + struct hyperv_guid *aa_inst; + char *aa_ident; + struct vmbus_channel *aa_chan; +}; + +struct vmbus_dev { + struct vmbus_attach_args dv_aa; + SLIST_ENTRY(vmbus_dev) dv_entry; +}; +SLIST_HEAD(vmbus_devices, vmbus_dev); + +struct vmbus_percpu_data { + void *simp; /* Synthetic Interrupt Message Page */ + void *siep; /* Synthetic Interrupt Event Flags Page */ + uint32_t vcpuid; /* Virtual cpuid */ + + /* Rarely used fields */ + struct hyperv_dma simp_dma; + struct hyperv_dma siep_dma; +} __aligned(CACHE_LINE_SIZE); + +struct vmbus_softc { + device_t sc_dev; + bus_dma_tag_t sc_dmat; + + pool_cache_t sc_msgpool; + + void *sc_msg_sih; + + u_long *sc_wevents; /* Write events */ + u_long *sc_revents; /* Read events */ + struct vmbus_mnf *sc_monitor[2]; + struct vmbus_percpu_data sc_percpu[MAXCPUS]; + + /* + * Rarely used fields + */ + uint32_t sc_flags; +#define VMBUS_SCFLAG_SYNIC __BIT(0) +#define VMBUS_SCFLAG_CONNECTED __BIT(1) +#define VMBUS_SCFLAG_OFFERS_DELIVERED __BIT(2) + uint32_t sc_proto; + int sc_channel_max; + + kcpuset_t *sc_intr_cpuset; + + /* Shared memory for Write/Read events */ + void *sc_events; + struct hyperv_dma sc_events_dma; + + struct hyperv_dma sc_monitor_dma[2]; + + struct vmbus_queue sc_reqs; /* Request queue */ + kmutex_t sc_req_lock; + struct vmbus_queue sc_rsps; /* Response queue */ + kmutex_t sc_rsp_lock; + + struct vmbus_offers sc_offers; + kmutex_t sc_offer_lock; + + struct vmbus_channels sc_channels; + kmutex_t sc_channel_lock; + + volatile uint32_t sc_handle; + + struct vmbus_devices sc_icdevs; + kmutex_t sc_icdev_lock; + + struct vmbus_devices sc_devs; + kmutex_t sc_dev_lock; +}; + +static __inline void +clear_bit(u_int b, volatile void *p) +{ + atomic_and_32(((volatile u_int *)p) + (b >> 5), ~(1 << (b & 0x1f))); +} + +static __inline void +set_bit(u_int b, volatile void *p) +{ + atomic_or_32(((volatile u_int *)p) + (b >> 5), 1 << (b & 0x1f)); +} + +static __inline int +test_bit(u_int b, volatile void *p) +{ + return !!(((volatile u_int *)p)[b >> 5] & (1 << (b & 0x1f))); +} + +extern const struct hyperv_guid hyperv_guid_network; +extern const struct hyperv_guid hyperv_guid_ide; +extern const struct hyperv_guid hyperv_guid_scsi; +extern const struct hyperv_guid hyperv_guid_shutdown; +extern const struct hyperv_guid hyperv_guid_timesync; +extern const struct hyperv_guid hyperv_guid_heartbeat; +extern const struct hyperv_guid hyperv_guid_kvp; +extern const struct hyperv_guid hyperv_guid_vss; +extern const struct hyperv_guid hyperv_guid_dynmem; +extern const struct hyperv_guid hyperv_guid_mouse; +extern const struct hyperv_guid hyperv_guid_kbd; +extern const struct hyperv_guid hyperv_guid_video; +extern const struct hyperv_guid hyperv_guid_fc; +extern const struct hyperv_guid hyperv_guid_fcopy; +extern const struct hyperv_guid hyperv_guid_pcie; +extern const struct hyperv_guid hyperv_guid_netdir; +extern const struct hyperv_guid hyperv_guid_rdesktop; +extern const struct hyperv_guid hyperv_guid_avma1; +extern const struct hyperv_guid hyperv_guid_avma2; +extern const struct hyperv_guid hyperv_guid_avma3; +extern const struct hyperv_guid hyperv_guid_avma4; + +int vmbus_match(device_t, cfdata_t, void *); +int vmbus_attach(struct vmbus_softc *); +int vmbus_detach(struct vmbus_softc *, int); + +int vmbus_handle_alloc(struct vmbus_channel *, const struct hyperv_dma *, + uint32_t, uint32_t *); +void vmbus_handle_free(struct vmbus_channel *, uint32_t); +int vmbus_channel_open(struct vmbus_channel *, size_t, void *, size_t, + vmbus_channel_callback_t, void *); +int vmbus_channel_close(struct vmbus_channel *); +int vmbus_channel_close_direct(struct vmbus_channel *); +int vmbus_channel_setdeferred(struct vmbus_channel *, const char *); +void vmbus_channel_schedule(struct vmbus_channel *); +int vmbus_channel_send(struct vmbus_channel *, void *, uint32_t, uint64_t, + int, uint32_t); +int vmbus_channel_send_sgl(struct vmbus_channel *, struct vmbus_gpa *, + uint32_t, void *, uint32_t, uint64_t); +int vmbus_channel_send_prpl(struct vmbus_channel *, + struct vmbus_gpa_range *, uint32_t, void *, uint32_t, uint64_t); +int vmbus_channel_recv(struct vmbus_channel *, void *, uint32_t, uint32_t *, + uint64_t *, int); +void vmbus_channel_cpu_set(struct vmbus_channel *, int); +void vmbus_channel_cpu_rr(struct vmbus_channel *); + +struct vmbus_channel ** + vmbus_subchannel_get(struct vmbus_channel *, int); +void vmbus_subchannel_put(struct vmbus_channel **, int); + +#endif /* _VMBUSVAR_H_ */ |
