git: 998acbc14051 - main - hvuio: Add HV_UIO driver

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Wed, 16 Sep 2026 14:12:52 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=998acbc14051c357a8b0a8f52c7f16885bf74fc6

commit 998acbc14051c357a8b0a8f52c7f16885bf74fc6
Author:     Vag Singh <vag.singh@oneconvergence.com>
AuthorDate: 2025-08-14 05:45:24 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-09-16 14:12:07 +0000

    hvuio: Add HV_UIO driver
    
    [PATCH 10/31] FreeBSD OFED support for DPDK MLX5 PMD
    
    Adding UIO driver that exposes the synthetic network device
    resources to userspace for application such as DPDK can drive it.
    
    Added rescind CB capability to the VMBUS driver. Whenever the
    hypervisor wants to close the device channel, VMBUS on receiving
    this msg, calls the CB of the device driver (hv_uio in our case).
    
    Differential revision:  https://reviews.freebsd.org/D32184
    MFC after:      1 month
---
 sys/dev/hyperv/hvuio/hv_uio.c        | 779 +++++++++++++++++++++++++++++++++++
 sys/dev/hyperv/include/vmbus.h       |   1 +
 sys/dev/hyperv/vmbus/vmbus.c         |  22 +
 sys/dev/hyperv/vmbus/vmbus_chan.c    |  20 +-
 sys/dev/hyperv/vmbus/vmbus_chanvar.h |   5 +-
 sys/modules/hyperv/Makefile          |   2 +
 sys/modules/hyperv/hvuio/Makefile    |  12 +
 7 files changed, 838 insertions(+), 3 deletions(-)

diff --git a/sys/dev/hyperv/hvuio/hv_uio.c b/sys/dev/hyperv/hvuio/hv_uio.c
new file mode 100644
index 000000000000..db5436bd21b1
--- /dev/null
+++ b/sys/dev/hyperv/hvuio/hv_uio.c
@@ -0,0 +1,779 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2013-2016 Brocade Communications Systems, Inc.
+ * Copyright (c) 2016 Microsoft Corp.
+ * Copyright (c) 2021 One Convergence, 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.
+ */
+
+/*
+ * hv_uio - generic UIO driver for VMBus Network Devices
+ *
+ * Bind/unbind hv_uio with VMBus network device using devctl.
+ * For example:
+ * devctl set driver -f hn1 hv_uio
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/kernel.h>
+#include <sys/smp.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/errno.h>
+#include <sys/cdefs.h>
+#include <sys/sysctl.h>
+#include <sys/rwlock.h>
+#include <sys/lock.h>
+#include <sys/proc.h>
+#include <sys/selinfo.h>
+#include <sys/uio.h>
+#include <sys/poll.h>
+#include <sys/malloc.h>
+
+#include <machine/atomic.h>
+
+#include <vm/vm.h>
+#include <vm/vm_param.h>
+#include <vm/vm_object.h>
+#include <vm/vm_page.h>
+#include <vm/vm_pager.h>
+#include <vm/vm_phys.h>
+
+#include <dev/hyperv/include/hyperv.h>
+#include <dev/hyperv/include/hyperv_busdma.h>
+#include <dev/hyperv/vmbus/vmbus_reg.h>
+#include <dev/hyperv/vmbus/vmbus_chanvar.h>
+
+#include "vmbus_if.h"
+
+#define UH_DNAME	"hv_uio"
+#define DRIVER_VERSION  1
+
+#define VMBUS_VPREF	1
+#define VMBUS_VMIN	VMBUS_VPREF
+#define VMBUS_VMAX	VMBUS_VPREF
+
+#define HV_RING_SIZE     512    /* pages */
+#define SEND_BUFFER_SIZE (16 * 1024 * 1024)
+#define RECV_BUFFER_SIZE (31 * 1024 * 1024)
+
+#define UH_NAMESIZE	32
+
+/* Macros to distinguish mmap request
+ * [7-0] - Device memory region
+ * [15-8]- Sub-channel id
+ */
+#define UH_MEM_MASK		0x00ff
+#define UH_SUBCHAN_MASK_SHIFT	8
+
+/* ioctl */
+#define HVIOOPENSUBCHAN     _IOW('h', 14, uint32_t)
+
+/* Maximum number of VMBUS resources. */
+enum uio_hv_map {
+	HV_TXRX_RING_MAP = 0,
+	HV_INT_PAGE_MAP,
+	HV_MON_PAGE_MAP,
+	HV_RECV_BUF_MAP,
+	HV_SEND_BUF_MAP,
+	HV_MAX_RESOURCE
+};
+
+enum uh_cdev_st {
+	UH_ST_CLOSE,
+	UH_ST_OPEN,
+};
+
+/*
+ * Network GUID
+ */
+static const struct hyperv_guid hn_guid = {
+	.hv_guid = {
+		0x63, 0x51, 0x61, 0xf8, 0x3e, 0xdf, 0xc5, 0x46,
+		0x91, 0x3f, 0xf2, 0xd2, 0xf9, 0x65, 0xed, 0x0e }
+};
+
+struct uh_res {
+	struct hyperv_dma *hvdma;
+	void	*buf;
+	uint32_t gpadl;
+	uint64_t size;
+	struct sysctl_oid *oid;
+	char	name[UH_NAMESIZE];
+};
+
+struct uio_hv_softc {
+	device_t		dev;
+	struct cdev		*uh_cdev;
+
+	int			cdev_st;
+	volatile uint32_t	revent;	/* total read events */
+	uint32_t		uevent;	/* events read by user */
+
+	struct vmbus_channel	*chan;
+	uint32_t		subchan_cnt;
+
+	uint32_t		cbr_txsz;
+	uint32_t		cbr_rxsz;
+
+	struct uh_res		res[HV_MAX_RESOURCE];
+
+	struct selinfo		uh_selinfo;
+	boolean_t		allow_read;
+	struct sysctl_ctx_list	*ctx;
+};
+
+static void
+uio_hv_sysctl_add(struct uio_hv_softc *sc, struct uh_res *res,
+		  const char *descr, boolean_t publish_gpadl);
+
+static inline void
+uio_hv_event_notify(struct uio_hv_softc *sc)
+{
+	atomic_add_32(&sc->revent, 1);
+	selwakeup(&sc->uh_selinfo);
+}
+
+/*
+ * Callback from vmbus_event when something is in inbound ring.
+ */
+static void
+uio_hv_channel_cb(struct vmbus_channel *chan, void *arg)
+{
+	struct uio_hv_softc *sc = (struct uio_hv_softc *)arg;
+
+	vmbus_rxbr_intr_mask(&chan->ch_rxbr);
+	uio_hv_event_notify(sc);
+}
+
+static void
+uio_hv_rescind_cb(struct vmbus_channel *chan)
+{
+	struct uio_hv_softc *sc = device_get_softc(chan->ch_dev);
+
+	sc->allow_read = false;
+	uio_hv_event_notify(sc);
+}
+
+static int
+uio_hv_schan_open(struct uio_hv_softc *sc, struct vmbus_channel *schan)
+{
+	int ret;
+
+	vmbus_chan_cpu_set(schan, schan->ch_subidx % mp_ncpus);
+	ret = vmbus_chan_open(schan, sc->cbr_txsz, sc->cbr_rxsz, NULL, 0,
+			      uio_hv_channel_cb, sc);
+	if (ret)
+		return ret;
+
+	/* Disable interrupts on sub channel */
+	vmbus_rxbr_intr_mask(&schan->ch_rxbr);
+	sc->subchan_cnt++;
+	return 0;
+}
+
+static int
+uio_hv_read(struct cdev *cdev, struct uio *uio, int ioflag __unused)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	uint32_t evcount;
+	int ret;
+
+	if (sc->allow_read == false)
+		return EIO;
+
+	if (uio->uio_resid < sizeof(uint32_t))
+		return EINVAL;
+
+	evcount = atomic_load_32(&sc->revent);
+	ret = uiomove(&evcount, sizeof(uint32_t), uio);
+	if (ret)
+		device_printf(sc->dev, "Failed to read\n");
+	else
+		sc->uevent = evcount;
+
+	return ret;
+}
+
+/* Write is used only to disable/enable interrupt from user space processes */
+static int
+uio_hv_write(struct cdev *cdev, struct uio *uio, int ioflag __unused)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	struct vmbus_channel *pchan = sc->chan;
+	uint32_t irq_state;
+	int ret;
+
+	ret = copyin(uio->uio_iov->iov_base, &irq_state, sizeof(uint32_t));
+	if (ret) {
+		device_printf(sc->dev, "Failed to write\n");
+		return ret;
+	}
+
+	pchan->ch_rxbr.rxbr_imask = !irq_state;
+	mb();
+
+	return 0;
+}
+
+static int
+uio_hv_open(struct cdev *cdev, int oflags, int devtype, struct thread *td __unused)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	struct vmbus_channel *pchan = sc->chan;
+	struct uh_res *res = NULL;
+	int ret = 0;
+
+	if (!atomic_cmpset_int(&sc->cdev_st, UH_ST_CLOSE, UH_ST_OPEN))
+		return 0;
+
+	pchan->ch_rescind_cb = uio_hv_rescind_cb;
+
+	ret = vmbus_chan_open(pchan, sc->cbr_txsz, sc->cbr_rxsz, NULL, 0,
+			      uio_hv_channel_cb, sc);
+	if (ret) {
+		device_printf(sc->dev, "failed opening primary chan%u\n",
+			      vmbus_chan_id(pchan));
+		goto cleanup;
+	}
+	vmbus_rxbr_intr_mask(&pchan->ch_rxbr);
+
+	res = &sc->res[HV_TXRX_RING_MAP];
+	res->hvdma = &pchan->ch_bufring_dma;
+	uio_hv_sysctl_add(sc, res, "Channel Tx Rx rings size", false);
+
+	sc->allow_read = true;
+
+	if (bootverbose)
+		device_printf(sc->dev, "device opened\n");
+
+	return 0;
+cleanup:
+	device_printf(sc->dev, "device open failed\n");
+	atomic_set_int(&sc->cdev_st, UH_ST_CLOSE);
+
+	return ret;
+}
+
+static void
+uio_hv_reset(struct uio_hv_softc *sc)
+{
+	if (!atomic_cmpset_int(&sc->cdev_st, UH_ST_OPEN, UH_ST_CLOSE))
+		return;
+
+	sc->allow_read = false;
+	sysctl_remove_oid(sc->res[HV_TXRX_RING_MAP].oid, 1, 1);
+	sc->res[HV_TXRX_RING_MAP].hvdma = NULL;
+	vmbus_chan_close(sc->chan);
+	sc->subchan_cnt = 0;
+}
+
+/* Only the last close call to the userspace process object will reach here */
+static int
+uio_hv_close(struct cdev *cdev, int fflag __unused, int devtype __unused,
+	     struct thread *td __unused)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+
+	if (bootverbose)
+		device_printf(sc->dev, "device close\n");
+
+	uio_hv_reset(sc);
+
+	return 0;
+}
+
+static int
+uio_hv_poll(struct cdev *cdev, int events, struct thread *td)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	int ret = 0;
+
+	if (sc->uevent != atomic_load_32(&sc->revent))
+		ret = POLLIN;
+	else
+		selrecord(td, &sc->uh_selinfo);
+
+	return ret;
+}
+
+static int
+uio_hv_cdev_pg_ctor(void *handle, vm_ooffset_t size, vm_prot_t prot,
+			  vm_ooffset_t foff, struct ucred *cred, u_short *color)
+{
+	struct uio_hv_softc *sc = handle;
+
+	if (color)
+		*color = 0;
+	dev_ref(sc->uh_cdev);
+	return 0;
+}
+
+static void
+uio_hv_cdev_pg_dtor(void *handle)
+{
+	struct uio_hv_softc *sc = handle;
+
+	dev_rel(sc->uh_cdev);
+}
+
+	static int
+uio_hv_cdev_pg_fault(vm_object_t object, vm_ooffset_t offset, int prot,
+			vm_page_t *mres)
+{
+	vm_memattr_t memattr, memattr1;
+	vm_page_t page, m_paddr;
+	vm_paddr_t paddr;
+
+	memattr = object->memattr;
+	VM_OBJECT_WUNLOCK(object);
+	paddr = offset;
+
+	m_paddr = vm_phys_paddr_to_vm_page(paddr);
+	if (m_paddr != NULL) {
+		memattr1 = pmap_page_get_memattr(m_paddr);
+		if (memattr1 != memattr)
+			memattr = memattr1;
+	}
+	if (((*mres)->flags & PG_FICTITIOUS) != 0) {
+		/*
+		 * If the passed in result page is a fake page, update it with
+		 * the new physical address.
+		 */
+		page = *mres;
+		VM_OBJECT_WLOCK(object);
+		vm_page_updatefake(page, paddr, memattr);
+	} else {
+		/*
+		 * Replace the passed in reqpage page with our own fake page and
+		 * free up the original page.
+		 */
+		page = vm_page_getfake(paddr, memattr);
+		VM_OBJECT_WLOCK(object);
+#if __FreeBSD__ >= 13
+		vm_page_replace(page, object, (*mres)->pindex, *mres);
+#else
+		vm_page_t mret = vm_page_replace(page, object, (*mres)->pindex);
+
+		KASSERT(mret == *mres,
+			("invalid page replacement, old=%p, ret=%p", *mres, mret));
+		vm_page_lock(mret);
+		vm_page_free(mret);
+		vm_page_unlock(mret);
+#endif
+		*mres = page;
+	}
+	page->valid = VM_PAGE_BITS_ALL;
+	return VM_PAGER_OK;
+}
+
+static struct cdev_pager_ops uh_cdev_pager_ops = {
+	.cdev_pg_ctor = uio_hv_cdev_pg_ctor,
+	.cdev_pg_dtor = uio_hv_cdev_pg_dtor,
+	.cdev_pg_fault = uio_hv_cdev_pg_fault,
+};
+
+static int
+uio_hv_mmap_single(struct cdev *cdev, vm_ooffset_t *offset, vm_size_t size,
+		   struct vm_object **obj, int nprot)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	struct vmbus_channel *pchan = vmbus_get_channel(sc->dev);
+	struct vmbus_channel **schans;
+	uint32_t req = *offset/PAGE_SIZE;	/* map region is encoded in the offset */
+	uint32_t reg = req & UH_MEM_MASK;
+	uint32_t subchan_id = req >> UH_SUBCHAN_MASK_SHIFT;
+	bool schan_found = false;
+	int i, ret = 0;
+
+	if ((pchan->ch_stflags & VMBUS_CHAN_ST_OPENED) == 0) {
+		ret = ENODEV;
+		goto end;
+	}
+
+	if (bootverbose)
+		device_printf(sc->dev, "mmap request for reg %u subchan_id %u\n",
+			      reg, subchan_id);
+
+	if (!subchan_id) {
+		if (reg >= HV_MAX_RESOURCE) {
+			device_printf(sc->dev, "Invalid resource request\n");
+			ret = EINVAL;
+			goto end;
+		}
+
+		*offset = (vm_ooffset_t) sc->res[reg].hvdma->hv_paddr;
+	} else {
+		schans = vmbus_subchan_get(pchan, sc->subchan_cnt);
+
+		for (i = 0; i < sc->subchan_cnt; ++i) {
+			if (schans[i]->ch_id == subchan_id) {
+				schan_found = true;
+				*offset =
+				    (vm_ooffset_t)schans[i]->ch_bufring_dma.hv_paddr;
+				break;
+			}
+		}
+
+		vmbus_subchan_rel(schans, sc->subchan_cnt);
+		if (schan_found == false) {
+			device_printf(sc->dev, "Invalid subchan idx\n");
+			ret = ENODEV;
+			goto end;
+		}
+	}
+
+	*obj = cdev_pager_allocate(sc, OBJT_DEVICE, &uh_cdev_pager_ops, size, nprot,
+				   *offset, curthread->td_ucred);
+	if (*obj == NULL) {
+		device_printf(sc->dev, "vm_pager_allocate failed\n");
+		ret = ENOMEM;
+	}
+end:
+	return ret;
+}
+
+static int
+uio_hv_schan_open_all(struct uio_hv_softc *sc, uint32_t subchan_cnt)
+{
+	struct vmbus_channel *pchan = sc->chan;
+	struct vmbus_channel **subchans;
+	int ret, i, sc_fail = 0;
+
+	KASSERT(subchan_cnt > 0, ("subchan count should not be 0"));
+
+	if (subchan_cnt < pchan->ch_subchan_cnt) {
+		device_printf(sc->dev, "%u subchan open req received"
+				"pchan has %u subchannels", subchan_cnt,
+				pchan->ch_subchan_cnt);
+		return EINVAL;
+	}
+
+	/* Waits for all sub-channels to become ready */
+	subchans = vmbus_subchan_get(pchan, subchan_cnt);
+	for (i = 0; i < subchan_cnt; ++i) {
+		ret = uio_hv_schan_open(sc, subchans[i]);
+		/* On err, all subchans will be closed later */
+		if (ret)
+			sc_fail = ret;
+	}
+	vmbus_subchan_rel(subchans, subchan_cnt);
+
+	if (sc_fail) {
+		device_printf(sc->dev, "subchan open failed, device closing\n");
+		return sc_fail;
+	}
+
+	return 0;
+}
+
+static int
+uio_hv_ioctl(struct cdev *cdev, u_long cmd, caddr_t data, int fflag, struct thread *td)
+{
+	struct uio_hv_softc *sc = cdev->si_drv1;
+	uint32_t nschan;
+	int ret = 0;
+
+	if (bootverbose)
+		device_printf(sc->dev, "ioctl cmd %lu\n", cmd);
+	switch (cmd) {
+	case HVIOOPENSUBCHAN:
+		/* userspace should pass the subchan cnt returned by NVS */
+		nschan = *(uint32_t *)data;
+		if (nschan == 0)
+			break;
+
+		ret = uio_hv_schan_open_all(sc, nschan);
+		break;
+	default:
+		if (bootverbose)
+			device_printf(sc->dev,
+				      "invalid ioctl cmd %lu\n", cmd);
+		ret = EINVAL;
+	}
+	return ret;
+}
+
+static struct cdevsw uh_cdevsw = {
+	.d_name		= UH_DNAME,
+	.d_version	= D_VERSION,
+	.d_open		= uio_hv_open,
+	.d_close	= uio_hv_close,
+	.d_read		= uio_hv_read,
+	.d_write	= uio_hv_write,
+	.d_ioctl	= uio_hv_ioctl,
+	.d_poll		= uio_hv_poll,
+	.d_mmap_single	= uio_hv_mmap_single,
+};
+
+static void
+uio_hv_sysctl_add(struct uio_hv_softc *sc, struct uh_res *res,
+		  const char *descr, boolean_t publish_gpadl)
+{
+	struct sysctl_oid_list *child;
+	struct sysctl_oid_list *child2;
+
+	child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev));
+	res->oid = SYSCTL_ADD_NODE(sc->ctx, child, OID_AUTO, res->name,
+				   CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
+	child2 = SYSCTL_CHILDREN(res->oid);
+	SYSCTL_ADD_U64(sc->ctx, child2, OID_AUTO, "size", CTLFLAG_RD, NULL,
+		       res->size, descr);
+	if (publish_gpadl)
+		SYSCTL_ADD_U32(sc->ctx, child2, OID_AUTO, "gpadl", CTLFLAG_RD,
+			       &res->gpadl, 0, "");
+}
+
+static void uio_hv_sysctl_create(struct uio_hv_softc *sc)
+{
+	struct uh_res *res = NULL;
+
+	sc->ctx = device_get_sysctl_ctx(sc->dev);
+
+	res = &sc->res[HV_INT_PAGE_MAP];
+	uio_hv_sysctl_add(sc, res, "VMBUS Interrupt Page size", false);
+
+	res = &sc->res[HV_MON_PAGE_MAP];
+	uio_hv_sysctl_add(sc, res, "VMBUS Monitor Page size", false);
+
+	res = &sc->res[HV_RECV_BUF_MAP];
+	uio_hv_sysctl_add(sc, res, "Channel Receive Buffer size", true);
+
+	res = &sc->res[HV_SEND_BUF_MAP];
+	uio_hv_sysctl_add(sc, res, "Channel Send Buffer size", true);
+
+	SYSCTL_ADD_U32(sc->ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+		       OID_AUTO, "subchan_cnt", CTLFLAG_RD, &sc->subchan_cnt, 0,
+		       "sub channels count");
+}
+
+static void
+uio_hv_gpadl_free(struct uio_hv_softc *sc, enum uio_hv_map mr)
+{
+	struct uh_res *res = &sc->res[mr];
+
+	if (res->gpadl) {
+		vmbus_chan_gpadl_disconnect(sc->chan, res->gpadl);
+		res->gpadl = 0;
+	}
+
+	if (res->buf) {
+		hyperv_dmamem_free(res->hvdma, res->buf);
+		res->buf = NULL;
+	}
+
+	if (res->hvdma) {
+		free(res->hvdma, M_DEVBUF);
+		res->hvdma = NULL;
+	}
+}
+
+static int
+uio_hv_gpadl_alloc(struct uio_hv_softc *sc, enum uio_hv_map mr, uint64_t size)
+{
+	struct uh_res *res = &sc->res[mr];
+	int ret = 0;
+
+	res->hvdma = malloc(sizeof(struct hyperv_dma), M_DEVBUF, M_WAITOK);
+	if (!res->hvdma)
+		return ENOMEM;
+
+	res->buf = hyperv_dmamem_alloc(bus_get_dma_tag(sc->dev), PAGE_SIZE, 0,
+				       size, res->hvdma,
+				       BUS_DMA_WAITOK | BUS_DMA_ZERO);
+	if (!res->buf) {
+		ret = ENOMEM;
+		goto cleanup;
+	}
+
+	ret = vmbus_chan_gpadl_connect(sc->chan, res->hvdma->hv_paddr, size,
+				       &res->gpadl);
+	if (ret)
+		goto cleanup;
+
+	return 0;
+cleanup:
+	uio_hv_gpadl_free(sc, mr);
+	return ret;
+}
+
+static int
+uio_hv_detach(device_t dev)
+{
+	struct uio_hv_softc *sc = device_get_softc(dev);
+
+	if (sc->uh_cdev) {
+		uio_hv_reset(sc);
+		destroy_dev(sc->uh_cdev);
+	}
+
+	uio_hv_gpadl_free(sc, HV_SEND_BUF_MAP);
+	uio_hv_gpadl_free(sc, HV_RECV_BUF_MAP);
+
+	sc->chan = NULL;
+
+	return 0;
+}
+
+static int
+uio_hv_attach(device_t dev)
+{
+	struct vmbus_channel *chan = vmbus_get_channel(dev);
+	struct uio_hv_softc *sc = device_get_softc(dev);
+	int ret;
+
+	/* Communicating with host has to be via shared memory not hypercall */
+	if (!(chan->ch_txflags & VMBUS_CHAN_TXF_HASMNF)) {
+		device_printf(dev, "vmbus channel requires shared memory communication\n");
+		return ENOTSUP;
+	}
+
+	sc->dev = dev;
+	sc->chan = chan;
+
+	sc->cbr_txsz = HV_RING_SIZE * PAGE_SIZE;
+	sc->cbr_rxsz = HV_RING_SIZE * PAGE_SIZE;
+
+	vmbus_chan_cpu_set(chan, chan->ch_subidx % mp_ncpus);
+
+	/* Channel TxRx rings are created while cdev open and not here
+	 * since closing the channel destroys the rings
+	 */
+	strncpy(sc->res[HV_TXRX_RING_MAP].name, "txrx_rings", UH_NAMESIZE);
+	sc->res[HV_TXRX_RING_MAP].size = sc->cbr_txsz + sc->cbr_rxsz;
+
+	strncpy(sc->res[HV_INT_PAGE_MAP].name, "int_page", UH_NAMESIZE);
+	sc->res[HV_INT_PAGE_MAP].hvdma = vmbus_get_mem_evtflags();
+	sc->res[HV_INT_PAGE_MAP].size = PAGE_SIZE;
+
+	strncpy(sc->res[HV_MON_PAGE_MAP].name, "monitor_page", UH_NAMESIZE);
+	sc->res[HV_MON_PAGE_MAP].hvdma = vmbus_get_mem_mnf2();
+	sc->res[HV_MON_PAGE_MAP].size = PAGE_SIZE;
+
+	ret = uio_hv_gpadl_alloc(sc, HV_RECV_BUF_MAP, RECV_BUFFER_SIZE);
+	if (ret) {
+		device_printf(dev, "Failed to get recv gpadl\n");
+		goto cleanup;
+	}
+	strncpy(sc->res[HV_RECV_BUF_MAP].name, "recv_buf", UH_NAMESIZE);
+	sc->res[HV_RECV_BUF_MAP].size = RECV_BUFFER_SIZE;
+
+	ret = uio_hv_gpadl_alloc(sc, HV_SEND_BUF_MAP, SEND_BUFFER_SIZE);
+	if (ret) {
+		device_printf(dev, "Failed to get send gpadl\n");
+		goto cleanup;
+	}
+	strncpy(sc->res[HV_SEND_BUF_MAP].name, "send_buf", UH_NAMESIZE);
+	sc->res[HV_SEND_BUF_MAP].size = SEND_BUFFER_SIZE;
+
+	ret = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, &sc->uh_cdev,
+			 &uh_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0640, "%s",
+			 device_get_nameunit(dev));
+	if (ret)
+		goto cleanup;
+
+	sc->uh_cdev->si_drv1 = sc;
+
+	/*Create sysctl tree for this device */
+	uio_hv_sysctl_create(sc);
+
+	return 0;
+cleanup:
+	uio_hv_detach(dev);
+	return ret;
+}
+
+static int
+uio_hv_probe(device_t dev)
+{
+	/* Presently only VMBus Network Device is driven */
+	if (VMBUS_PROBE_GUID(device_get_parent(dev), dev, &hn_guid) == 0) {
+		device_set_desc(dev, "Hyper-V UIO Network Interface");
+		return BUS_PROBE_DEFAULT;
+	}
+	return ENXIO;
+}
+
+static int
+uio_hv_shutdown(device_t dev)
+{
+	return 0;
+}
+
+static inline void
+uio_hv_load(void)
+{
+	printf("%s ver %d module loaded\n", UH_DNAME, DRIVER_VERSION);
+}
+
+static inline void
+uio_hv_unload(void)
+{
+	printf("%s module unloaded\n", UH_DNAME);
+}
+
+/* KLD event handler */
+static int
+uio_hv_modevh(module_t mod __unused, int event, void *arg __unused)
+{
+	int ret = 0;
+
+	switch (event) {
+	case MOD_LOAD:
+		uio_hv_load();
+		break;
+	case MOD_UNLOAD:
+		uio_hv_unload();
+		break;
+	case MOD_SHUTDOWN:
+		break;
+	default:
+		ret = EOPNOTSUPP;
+		break;
+	}
+	return ret;
+}
+
+static device_method_t uio_hv_methods[] = {
+	DEVMETHOD(device_probe,     uio_hv_probe),
+	DEVMETHOD(device_shutdown,  uio_hv_shutdown),
+	DEVMETHOD(device_attach,    uio_hv_attach),
+	DEVMETHOD(device_detach,    uio_hv_detach),
+	DEVMETHOD_END
+};
+
+static devclass_t uio_hv_devclass;
+
+static driver_t uio_hv_driver = {
+	UH_DNAME,
+	uio_hv_methods,
+	sizeof(struct uio_hv_softc)
+};
+
+DRIVER_MODULE(hv_uio, vmbus, uio_hv_driver, uio_hv_devclass, uio_hv_modevh, NULL);
+MODULE_VERSION(hv_uio, DRIVER_VERSION);
+MODULE_DEPEND(hv_uio, vmbus, VMBUS_VMIN, VMBUS_VPREF, VMBUS_VMAX);
diff --git a/sys/dev/hyperv/include/vmbus.h b/sys/dev/hyperv/include/vmbus.h
index ab67a7436b71..345212938060 100644
--- a/sys/dev/hyperv/include/vmbus.h
+++ b/sys/dev/hyperv/include/vmbus.h
@@ -130,6 +130,7 @@ struct taskqueue;
 
 typedef void	(*vmbus_chan_callback_t)(struct vmbus_channel *, void *);
 typedef int	(*vmbus_br_copy_callback_t)(void *, int, void *);
+typedef void	(*vmbus_chan_rescind_callback_t)(struct vmbus_channel *);
 
 static __inline struct vmbus_channel *
 vmbus_get_channel(device_t dev)
diff --git a/sys/dev/hyperv/vmbus/vmbus.c b/sys/dev/hyperv/vmbus/vmbus.c
index 15683f21ea6a..895352d6debe 100644
--- a/sys/dev/hyperv/vmbus/vmbus.c
+++ b/sys/dev/hyperv/vmbus/vmbus.c
@@ -848,6 +848,28 @@ vmbus_synic_teardown(void *arg)
 	WRMSR(MSR_HV_SIEFP, (orig & MSR_HV_SIEFP_RSVD_MASK));
 }
 
+struct hyperv_dma *
+vmbus_get_mem_evtflags(void)
+{
+	struct vmbus_softc *sc = vmbus_get_softc();
+
+	if (!sc)
+		return NULL;
+
+	return NULL; //&sc->vmbus_evtflags_dma;
+}
+
+struct hyperv_dma *
+vmbus_get_mem_mnf2(void)
+{
+	struct vmbus_softc *sc = vmbus_get_softc();
+
+	if (!sc)
+		return NULL;
+
+	return NULL; //&sc->vmbus_mnf2_dma;
+}
+
 static int
 vmbus_dma_alloc(struct vmbus_softc *sc)
 {
diff --git a/sys/dev/hyperv/vmbus/vmbus_chan.c b/sys/dev/hyperv/vmbus/vmbus_chan.c
index 7ea60a499c72..9adcb64de124 100644
--- a/sys/dev/hyperv/vmbus/vmbus_chan.c
+++ b/sys/dev/hyperv/vmbus/vmbus_chan.c
@@ -274,9 +274,11 @@ vmbus_chan_sysctl_create(struct vmbus_channel *chan)
 	/*
 	 * Create dev.NAME.UNIT.channel.CHANID tree.
 	 */
-	if (VMBUS_CHAN_ISPRIMARY(chan))
+	if (VMBUS_CHAN_ISPRIMARY(chan)) {
 		ch_id = chan->ch_id;
-	else
+		SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(ch_tree), OID_AUTO,
+		    "ch_id", CTLFLAG_RD, &chan->ch_id, 0, "owner channel id");
+	} else
 		ch_id = chan->ch_prichan->ch_id;
 	snprintf(name, sizeof(name), "%d", ch_id);
 	chid_tree = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(ch_tree),
@@ -309,6 +311,11 @@ vmbus_chan_sysctl_create(struct vmbus_channel *chan)
 		    "chanid", CTLFLAG_RD, &chan->ch_id, 0, "channel id");
 	}
 
+	SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(chid_tree), OID_AUTO,
+	     "ch_subidx", CTLFLAG_RD, &chan->ch_subidx, 0, "subchan index");
+	SYSCTL_ADD_U8(ctx, SYSCTL_CHILDREN(chid_tree), OID_AUTO,
+	    "monitor_id", CTLFLAG_RD, &chan->ch_monitor_id, 0,
+	    "owner monitor id");
 	SYSCTL_ADD_UINT(ctx, SYSCTL_CHILDREN(chid_tree), OID_AUTO,
 	    "cpu", CTLFLAG_RD, &chan->ch_cpuid, 0, "owner CPU id");
 	SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chid_tree), OID_AUTO,
@@ -1854,6 +1861,7 @@ vmbus_chan_msgproc_choffer(struct vmbus_softc *sc,
 	chan->ch_subidx = offer->chm_subidx;
 	chan->ch_guid_type = offer->chm_chtype;
 	chan->ch_guid_inst = offer->chm_chinst;
+	chan->ch_monitor_id = offer->chm_montrig;
 
 	/* Batch reading is on by default */
 	chan->ch_flags |= VMBUS_CHAN_FLAG_BATCHREAD;
@@ -1960,6 +1968,14 @@ vmbus_chan_msgproc_chrescind(struct vmbus_softc *sc,
 		    note->chm_chanid);
 		return;
 	}
+	mtx_unlock(&sc->vmbus_chan_lock);
+
+	if (chan->ch_dev && chan->ch_rescind_cb) {
+		chan->ch_rescind_cb(chan);
+		return;
+	}
+
+	mtx_lock(&sc->vmbus_chan_lock);
 	vmbus_chan_rem_list(sc, chan);
 	mtx_unlock(&sc->vmbus_chan_lock);
 
diff --git a/sys/dev/hyperv/vmbus/vmbus_chanvar.h b/sys/dev/hyperv/vmbus/vmbus_chanvar.h
index c02ec24c70a1..0b254886c9d8 100644
--- a/sys/dev/hyperv/vmbus/vmbus_chanvar.h
+++ b/sys/dev/hyperv/vmbus/vmbus_chanvar.h
@@ -61,6 +61,7 @@ struct vmbus_channel {
 	struct callout			ch_poll_timeo;
 	vmbus_chan_callback_t		ch_cb;
 	void				*ch_cbarg;
+	vmbus_chan_rescind_callback_t	ch_rescind_cb;
 
 	/*
 	 * TX bufring; at the beginning of ch_bufring.
@@ -81,6 +82,7 @@ struct vmbus_channel {
 	 * These are based on the vmbus_chanmsg_choffer.chm_montrig.
 	 * Save it here for easy access.
 	 */
+	uint8_t				ch_monitor_id;
 	uint32_t			ch_montrig_mask;/* MNF trig mask */
 	volatile uint32_t		*ch_montrig;	/* MNF trigger loc. */
 
@@ -186,5 +188,6 @@ void		vmbus_event_proc_compat(struct vmbus_softc *, int);
 void		vmbus_chan_msgproc(struct vmbus_softc *,
 		    const struct vmbus_message *);
 void		vmbus_chan_destroy_all(struct vmbus_softc *);
-
+struct hyperv_dma	*vmbus_get_mem_evtflags(void);
+struct hyperv_dma	*vmbus_get_mem_mnf2(void);
 #endif	/* !_VMBUS_CHANVAR_H_ */
diff --git a/sys/modules/hyperv/Makefile b/sys/modules/hyperv/Makefile
index 170564a730ac..aaa059e8d7d1 100644
--- a/sys/modules/hyperv/Makefile
+++ b/sys/modules/hyperv/Makefile
@@ -1,3 +1,5 @@
 SUBDIR = vmbus netvsc storvsc utilities hvsock hid
+# XXXKIB
+#SUBDIR+= hvuio
 
 .include <bsd.subdir.mk>
diff --git a/sys/modules/hyperv/hvuio/Makefile b/sys/modules/hyperv/hvuio/Makefile
new file mode 100644
index 000000000000..3fca2a916801
--- /dev/null
+++ b/sys/modules/hyperv/hvuio/Makefile
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+.PATH:  ${SRCTOP}/sys/dev/hyperv/hvuio \
+	${SRCTOP}/sys/dev/hyperv/vmbus
+
+KMOD=	hv_uio
+SRCS=	hv_uio.c
+SRCS+=	device_if.h bus_if.h vmbus_if.h
+
+CFLAGS+= -I${SRCTOP}/sys/dev/hyperv/hvuio
+
+.include <bsd.kmod.mk>