summaryrefslogtreecommitdiff
path: root/sys/dev/hyperv
diff options
context:
space:
mode:
authornonaka <nonaka@NetBSD.org>2019-02-15 08:54:01 +0000
committernonaka <nonaka@NetBSD.org>2019-02-15 08:54:01 +0000
commitafdb7e40e269f2837fdd19c847e29c394199cd11 (patch)
tree6d8a68607667098c5299591fa91e81eefb53fb49 /sys/dev/hyperv
parentc8caf74aa9cfde718d636a3806ea428be4fb1391 (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.hyperv34
-rw-r--r--sys/dev/hyperv/hvheartbeat.c198
-rw-r--r--sys/dev/hyperv/hvs.c1139
-rw-r--r--sys/dev/hyperv/hvshutdown.c233
-rw-r--r--sys/dev/hyperv/hvtimesync.c338
-rw-r--r--sys/dev/hyperv/hyperv_common.c183
-rw-r--r--sys/dev/hyperv/hypervreg.h453
-rw-r--r--sys/dev/hyperv/hypervvar.h115
-rw-r--r--sys/dev/hyperv/if_hvn.c1849
-rw-r--r--sys/dev/hyperv/if_hvnreg.h204
-rw-r--r--sys/dev/hyperv/vmbus.c2112
-rw-r--r--sys/dev/hyperv/vmbusic.c271
-rw-r--r--sys/dev/hyperv/vmbusicreg.h248
-rw-r--r--sys/dev/hyperv/vmbusicvar.h68
-rw-r--r--sys/dev/hyperv/vmbusvar.h273
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(&params, 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, &params, 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_ */