git: 9ea7beb173b8 - main - ixv: Add Hyper-V virtual function support

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Tue, 15 Sep 2026 02:06:40 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=9ea7beb173b8b5c77663ee0f86b32cc76f20357f

commit 9ea7beb173b8b5c77663ee0f86b32cc76f20357f
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-09-07 03:34:50 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-15 02:05:00 +0000

    ixv: Add Hyper-V virtual function support
    
    Hyper-V does not expose the normal Intel PF/VF mailbox.
    
    Import the Hyper-V operation overrides from DPDK and complete its reset
    callback with the configuration-space mechanism submitted by Microsoft.
    Route ixv operations through the operation table, use mailbox API 1.0,
    and limit this environment to one queue pair.
    
    Claim the 82599, X540, X550, X550EM-X, and X550EM-A Hyper-V device IDs
    listed by DPDK.  Select the same path for the E610 Hyper-V subdevice
    identity defined there.
    
    For E610/Linkville, read the emulated VFLINKS-format status from PCI
    configuration space at offset 0x209.  Older families retain the MMIO
    VFLINKS path.
    
    Treat receive-mode changes as host-owned no-ops and do not retry VLAN
    operations that Hyper-V permanently rejects.  Limit the Hyper-V 82599
    MTU to 1504 because its VF does not implement the X540 RLPML field.
    
    Use mailbox reset indications only to refresh cached Hyper-V link state.
    Hyper-V does not provide the mailbox handshake required to treat that
    indication as a request for an iflib reinitialization.
    
    Tested on 82599 and E610 (with a followup commit) with Windows Server
    2025 Hyper-V.
    
    PR:             232472, 239849
    Obtained from:  DPDK (shared code)
    Sponsored by:   BBOX.io
    Co-authored-by: Wei Hu <weh@microsoft.com>
---
 share/man/man4/ixv.4        |  11 +-
 sys/conf/files              |   2 +
 sys/dev/ixgbe/if_ixv.c      | 134 ++++++++++++++-------
 sys/dev/ixgbe/ixgbe_hv_vf.c | 277 ++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/ixgbe/ixgbe_hv_vf.h |  12 ++
 sys/dev/ixgbe/ixgbe_osdep.c |   8 +-
 sys/dev/ixgbe/ixgbe_osdep.h |   4 +
 sys/modules/ixv/Makefile    |   1 +
 8 files changed, 406 insertions(+), 43 deletions(-)

diff --git a/share/man/man4/ixv.4 b/share/man/man4/ixv.4
index 6acc41737294..907ff30103ec 100644
--- a/share/man/man4/ixv.4
+++ b/share/man/man4/ixv.4
@@ -28,7 +28,7 @@
 .\" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 .\" SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd August 15, 2026
+.Dd September 6, 2026
 .Dt IXV 4
 .Os
 .Sh NAME
@@ -60,7 +60,8 @@ guest.
 .Pp
 The driver supports jumbo frames, MSI-X, receive-side scaling,
 TCP segmentation offload, checksum offload, and VLAN hardware offload.
-The maximum supported MTU is 9710 bytes.
+The maximum supported MTU is 9710 bytes, except that an 82599 VF on
+Hyper-V is limited to 1504 bytes.
 .Pp
 The physical-function driver controls the resources and policy presented to
 the virtual function.
@@ -70,6 +71,12 @@ MSI-X vectors.
 Requests to change the MAC address, VLAN membership, or receive mode may be
 rejected by physical-function policy.
 .Pp
+On Hyper-V, the host provides reset and assigned-address information through
+PCI configuration space instead of the Intel PF/VF mailbox.
+The driver uses one queue pair in this environment.
+MAC address, VLAN, multicast filter, and receive mode policies remain under
+host control.
+.Pp
 The virtual function cannot configure the physical media.
 Only automatic media selection is supported, and reported link state and
 speed are supplied by the physical function or virtual-function hardware.
diff --git a/sys/conf/files b/sys/conf/files
index a195d93cb169..c10592263caa 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -2348,6 +2348,8 @@ dev/ixgbe/ixgbe_common.c	optional ix inet | ixv inet \
 	compile-with "${NORMAL_C} -I$S/dev/ixgbe"
 dev/ixgbe/ixgbe_fw_logging.c	optional ix inet | ixv inet \
 	compile-with "${NORMAL_C} -I$S/dev/ixgbe"
+dev/ixgbe/ixgbe_hv_vf.c		optional ixv inet \
+	compile-with "${NORMAL_C} -I$S/dev/ixgbe"
 dev/ixgbe/ixgbe_mbx.c		optional ix inet | ixv inet \
 	compile-with "${NORMAL_C} -I$S/dev/ixgbe"
 dev/ixgbe/ixgbe_vf.c		optional ix inet | ixv inet \
diff --git a/sys/dev/ixgbe/if_ixv.c b/sys/dev/ixgbe/if_ixv.c
index 8405ac1aa8f9..a5e8a9b39de1 100644
--- a/sys/dev/ixgbe/if_ixv.c
+++ b/sys/dev/ixgbe/if_ixv.c
@@ -37,6 +37,7 @@
 #include "opt_rss.h"
 
 #include "ixgbe.h"
+#include "ixgbe_hv_vf.h"
 #include "ifdi_if.h"
 
 #include <net/netmap.h>
@@ -74,14 +75,27 @@ static const pci_vendor_info_t ixv_vendor_info_array[] =
 {
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_82599_VF,
 	    "Intel(R) X520 82599 Virtual Function"),
+	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_82599_VF_HV,
+	    "Intel(R) X520 82599 Hyper-V Virtual Function"),
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X540_VF,
 	    "Intel(R) X540 Virtual Function"),
+	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X540_VF_HV,
+	    "Intel(R) X540 Hyper-V Virtual Function"),
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550_VF,
 	    "Intel(R) X550 Virtual Function"),
+	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550_VF_HV,
+	    "Intel(R) X550 Hyper-V Virtual Function"),
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550EM_X_VF,
 	    "Intel(R) X552 Virtual Function"),
+	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550EM_X_VF_HV,
+	    "Intel(R) X552 Hyper-V Virtual Function"),
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550EM_A_VF,
 	    "Intel(R) X553 Virtual Function"),
+	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_X550EM_A_VF_HV,
+	    "Intel(R) X553 Hyper-V Virtual Function"),
+	PVID_OEM(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_E610_VF, 0,
+	    IXGBE_SUBDEV_ID_E610_VF_HV, 0,
+	    "Intel(R) E610 Hyper-V Virtual Function"),
 	PVID(IXGBE_INTEL_VENDOR_ID, IXGBE_DEV_ID_E610_VF,
 	    "Intel(R) E610 Virtual Function"),
 	/* required last entry */
@@ -92,7 +106,6 @@ static const pci_vendor_info_t ixv_vendor_info_array[] =
  * Function prototypes
  ************************************************************************/
 static void     *ixv_register(device_t);
-static int      ixv_probe(device_t);
 static int      ixv_if_attach_pre(if_ctx_t);
 static int      ixv_if_attach_post(if_ctx_t);
 static int      ixv_if_detach(if_ctx_t);
@@ -104,6 +117,7 @@ static int      ixv_if_rx_queues_alloc(if_ctx_t, caddr_t *, uint64_t *, int,
     int);
 static void     ixv_if_queues_free(if_ctx_t);
 static void     ixv_identify_hardware(if_ctx_t);
+static bool     ixv_is_hyperv(const struct ixgbe_hw *);
 static void     ixv_init_device_features(struct ixgbe_softc *);
 static int      ixv_allocate_pci_resources(if_ctx_t);
 static void     ixv_free_pci_resources(if_ctx_t);
@@ -133,6 +147,7 @@ static void     ixv_initialize_receive_units(if_ctx_t);
 static void     ixv_initialize_rss_mapping(struct ixgbe_softc *);
 
 static void     ixv_setup_vlan_support(if_ctx_t);
+static bool     ixv_vlan_error_retryable(s32);
 static void     ixv_vlan_retry_add(struct ixgbe_softc *, u16);
 static void     ixv_vlan_retry_clear(struct ixgbe_softc *, u16);
 static bool     ixv_vlan_retry_pending(const struct ixgbe_softc *);
@@ -168,7 +183,7 @@ static int      ixv_msix_mbx(void *);
 static device_method_t ixv_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_register, ixv_register),
-	DEVMETHOD(device_probe, ixv_probe),
+	DEVMETHOD(device_probe, iflib_device_probe),
 	DEVMETHOD(device_attach, iflib_device_attach),
 	DEVMETHOD(device_detach, iflib_device_detach),
 	DEVMETHOD(device_shutdown, iflib_device_shutdown),
@@ -257,16 +272,6 @@ ixv_register(device_t dev)
 	return (&ixv_sctx_init);
 }
 
-static int
-ixv_probe(device_t dev)
-{
-	if (pci_get_device(dev) == IXGBE_DEV_ID_E610_VF &&
-	    pci_get_subdevice(dev) == IXGBE_SUBDEV_ID_E610_VF_HV)
-		return (ENXIO);
-
-	return (iflib_device_probe(dev));
-}
-
 /************************************************************************
  * ixv_if_tx_queues_alloc
  ************************************************************************/
@@ -457,9 +462,12 @@ ixv_if_attach_pre(if_ctx_t ctx)
 	sc->vf_link_poll_tick = device_get_unit(dev) % IXV_LINK_POLL_TICKS;
 
 	/* Initialize the shared code */
-	error = ixgbe_init_ops_vf(hw);
+	if (ixv_is_hyperv(hw))
+		error = ixgbevf_hv_init_ops_vf(hw);
+	else
+		error = ixgbe_init_ops_vf(hw);
 	if (error) {
-		device_printf(dev, "ixgbe_init_ops_vf() failed!\n");
+		device_printf(dev, "VF shared-code initialization failed\n");
 		error = EIO;
 		goto err_out;
 	}
@@ -603,10 +611,16 @@ ixv_if_mtu_set(if_ctx_t ctx, uint32_t mtu)
 {
 	struct ixgbe_softc *sc = iflib_get_softc(ctx);
 	if_t ifp = iflib_get_ifp(ctx);
+	uint32_t max_mtu;
 	int error = 0;
 
 	IOCTL_DEBUGOUT("ioctl: SIOCSIFMTU (Set Interface MTU)");
-	if (mtu > IXGBE_MAX_FRAME_SIZE - IXGBE_MTU_HDR) {
+	max_mtu = IXGBE_MAX_FRAME_SIZE - IXGBE_MTU_HDR;
+	/* The 82599 VF cannot enforce the X540 RLPML field. */
+	if (ixv_is_hyperv(&sc->hw) &&
+	    sc->hw.mac.type == ixgbe_mac_82599_vf)
+		max_mtu = ETHERMTU + ETHER_CRC_LEN;
+	if (mtu > max_mtu) {
 		error = EINVAL;
 	} else {
 		if_setmtu(ifp, mtu);
@@ -1071,12 +1085,12 @@ ixv_negotiate_api(struct ixgbe_softc *sc)
 	};
 	int i = 0;
 
-	if (hw->mac.type == ixgbe_mac_E610_vf &&
-	    ixgbevf_negotiate_api_version(hw, ixgbe_mbox_api_16) == 0)
+	if (hw->mac.type == ixgbe_mac_E610_vf && !ixv_is_hyperv(hw) &&
+	    hw->mac.ops.negotiate_api_version(hw, ixgbe_mbox_api_16) == 0)
 		return (0);
 
 	while (mbx_api[i] != ixgbe_mbox_api_unknown) {
-		if (ixgbevf_negotiate_api_version(hw, mbx_api[i]) == 0)
+		if (hw->mac.ops.negotiate_api_version(hw, mbx_api[i]) == 0)
 			return (0);
 		i++;
 	}
@@ -1097,22 +1111,26 @@ ixv_queue_limit(struct ixgbe_softc *sc, bool mailbox_ready)
 	int admin_vectors, limit, msix_vectors;
 
 	hw = &sc->hw;
-	/* Preserve the current family limit as the mailbox fallback. */
-	switch (hw->mac.type) {
-	case ixgbe_mac_82599_vf:
-	case ixgbe_mac_X540_vf:
-		limit = 1;
-		break;
-	case ixgbe_mac_X550_vf:
-	case ixgbe_mac_X550EM_x_vf:
-	case ixgbe_mac_X550EM_a_vf:
-		limit = 2;
-		break;
-	case ixgbe_mac_E610_vf:
+	if (ixv_is_hyperv(hw)) {
 		limit = 1;
-		break;
-	default:
-		return (1);
+	} else {
+		/* Preserve the current family limit as the mailbox fallback. */
+		switch (hw->mac.type) {
+		case ixgbe_mac_82599_vf:
+		case ixgbe_mac_X540_vf:
+			limit = 1;
+			break;
+		case ixgbe_mac_X550_vf:
+		case ixgbe_mac_X550EM_x_vf:
+		case ixgbe_mac_X550EM_a_vf:
+			limit = 2;
+			break;
+		case ixgbe_mac_E610_vf:
+			limit = 1;
+			break;
+		default:
+			return (1);
+		}
 	}
 
 	/* Replace the fallback with the queue grant reported by the PF. */
@@ -1162,7 +1180,7 @@ ixv_update_xcast_mode(struct ixgbe_softc *sc, int flags)
 		mode = IXGBEVF_XCAST_MODE_MULTI;
 	else
 		mode = IXGBEVF_XCAST_MODE_NONE;
-	return (ixgbevf_update_xcast_mode(&sc->hw, mode));
+	return (sc->hw.mac.ops.update_xcast_mode(&sc->hw, mode));
 }
 
 static int
@@ -1306,7 +1324,13 @@ ixv_if_update_admin_status(if_ctx_t ctx)
 	if (sc->hw.mac.type != ixgbe_mac_E610_vf ||
 	    sc->hw.api_version != ixgbe_mbox_api_16)
 		check_link = true;
-	reset_seen = ixgbe_check_for_rst(&sc->hw, 0) == IXGBE_SUCCESS;
+	/*
+	 * Hyper-V uses this indication only to invalidate cached VFLINKS
+	 * state; it does not expose the mailbox handshake needed to turn it
+	 * into a driver reset.  Its check_link operation samples the bit.
+	 */
+	reset_seen = !ixv_is_hyperv(&sc->hw) &&
+	    ixgbe_check_for_rst(&sc->hw, 0) == IXGBE_SUCCESS;
 	if (reset_seen)
 		sc->hw.mac.get_link_status = true;
 	if (check_link) {
@@ -1442,18 +1466,23 @@ ixv_identify_hardware(if_ctx_t ctx)
 	/* A subset of set_mac_type */
 	switch (hw->device_id) {
 	case IXGBE_DEV_ID_82599_VF:
+	case IXGBE_DEV_ID_82599_VF_HV:
 		hw->mac.type = ixgbe_mac_82599_vf;
 		break;
 	case IXGBE_DEV_ID_X540_VF:
+	case IXGBE_DEV_ID_X540_VF_HV:
 		hw->mac.type = ixgbe_mac_X540_vf;
 		break;
 	case IXGBE_DEV_ID_X550_VF:
+	case IXGBE_DEV_ID_X550_VF_HV:
 		hw->mac.type = ixgbe_mac_X550_vf;
 		break;
 	case IXGBE_DEV_ID_X550EM_X_VF:
+	case IXGBE_DEV_ID_X550EM_X_VF_HV:
 		hw->mac.type = ixgbe_mac_X550EM_x_vf;
 		break;
 	case IXGBE_DEV_ID_X550EM_A_VF:
+	case IXGBE_DEV_ID_X550EM_A_VF_HV:
 		hw->mac.type = ixgbe_mac_X550EM_a_vf;
 		break;
 	case IXGBE_DEV_ID_E610_VF:
@@ -1466,6 +1495,24 @@ ixv_identify_hardware(if_ctx_t ctx)
 	}
 } /* ixv_identify_hardware */
 
+static bool
+ixv_is_hyperv(const struct ixgbe_hw *hw)
+{
+	switch (hw->device_id) {
+	case IXGBE_DEV_ID_82599_VF_HV:
+	case IXGBE_DEV_ID_X540_VF_HV:
+	case IXGBE_DEV_ID_X550_VF_HV:
+	case IXGBE_DEV_ID_X550EM_X_VF_HV:
+	case IXGBE_DEV_ID_X550EM_A_VF_HV:
+		return (true);
+	case IXGBE_DEV_ID_E610_VF:
+		return (hw->subsystem_device_id ==
+		    IXGBE_SUBDEV_ID_E610_VF_HV);
+	default:
+		return (false);
+	}
+}
+
 /************************************************************************
  * ixv_if_msix_intr_assign - Setup MSI-X Interrupt resources and handlers
  ************************************************************************/
@@ -1854,7 +1901,7 @@ ixv_initialize_receive_units(if_ctx_t ctx)
 	IXGBE_WRITE_REG(hw, IXGBE_VFPSRTYPE, psrtype);
 
 	/* Tell PF our max_frame size */
-	if (ixgbevf_rlpml_set_vf(hw, sc->max_frame_size) != 0) {
+	if (hw->mac.ops.set_rlpml(hw, sc->max_frame_size) != 0) {
 		device_printf(sc->dev,
 		    "There is a problem with the PF setup.  It is likely the"
 		    " receive unit for this VF will not function correctly."
@@ -1954,6 +2001,12 @@ ixv_initialize_receive_units(if_ctx_t ctx)
 /************************************************************************
  * VF VLAN mailbox retry helpers
  ************************************************************************/
+static bool
+ixv_vlan_error_retryable(s32 error)
+{
+	return (error != IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
 static void
 ixv_vlan_retry_add(struct ixgbe_softc *sc, u16 vid)
 {
@@ -2051,7 +2104,7 @@ ixv_vlan_retry_tick(struct ixgbe_softc *sc)
 		attempts++;
 		enable = (sc->shadow_vfta[vid >> 5] & bit) != 0;
 		error = hw->mac.ops.set_vfta(hw, vid, 0, enable, false);
-		if (error != IXGBE_SUCCESS)
+		if (error != IXGBE_SUCCESS && ixv_vlan_error_retryable(error))
 			break;
 		ixv_vlan_retry_clear(sc, vid);
 	}
@@ -2109,7 +2162,8 @@ ixv_setup_vlan_support(if_ctx_t ctx)
 				    false);
 			else
 				error = IXGBE_ERR_MBX;
-			if (error != IXGBE_SUCCESS) {
+			if (error != IXGBE_SUCCESS &&
+			    ixv_vlan_error_retryable(error)) {
 				ixv_vlan_retry_add(sc, vid);
 				restore_failures++;
 			} else
@@ -2150,7 +2204,7 @@ ixv_if_register_vlan(if_ctx_t ctx, u16 vtag)
 		return;
 
 	error = sc->hw.mac.ops.set_vfta(&sc->hw, vtag, 0, true, false);
-	if (error != IXGBE_SUCCESS) {
+	if (error != IXGBE_SUCCESS && ixv_vlan_error_retryable(error)) {
 		ixv_vlan_retry_add(sc, vtag);
 		if (!pending)
 			device_printf(sc->dev,
@@ -2184,7 +2238,7 @@ ixv_if_unregister_vlan(if_ctx_t ctx, u16 vtag)
 		return;
 
 	error = sc->hw.mac.ops.set_vfta(&sc->hw, vtag, 0, false, false);
-	if (error != IXGBE_SUCCESS) {
+	if (error != IXGBE_SUCCESS && ixv_vlan_error_retryable(error)) {
 		ixv_vlan_retry_add(sc, vtag);
 		if (!pending)
 			device_printf(sc->dev,
diff --git a/sys/dev/ixgbe/ixgbe_hv_vf.c b/sys/dev/ixgbe/ixgbe_hv_vf.c
new file mode 100644
index 000000000000..6ce994ff06f1
--- /dev/null
+++ b/sys/dev/ixgbe/ixgbe_hv_vf.c
@@ -0,0 +1,277 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2001-2024 Intel Corporation
+ */
+
+#include "ixgbe_common.h"
+#include "ixgbe_vf.h"
+#include "ixgbe_hv_vf.h"
+
+/* Reading these PCI configuration bytes resets a VF on Hyper-V. */
+#define IXGBE_HV_RESET_OFFSET	0x201
+
+/* E610 exposes VFLINKS in four emulated PCI configuration bytes. */
+#define IXGBE_HV_LINK_STATUS_OFFSET	0x209
+#define IXGBE_HV_LINK_STATUS_SIZE	4
+
+/**
+ * ixgbevf_hv_update_mc_addr_list_vf - Hyper-V variant - just a stub.
+ * @hw: unused
+ * @mc_addr_list: unused
+ * @mc_addr_count: unused
+ * @next: unused
+ * @clear: unused
+ */
+static s32
+ixgbevf_hv_update_mc_addr_list_vf(struct ixgbe_hw *hw, u8 *mc_addr_list,
+    u32 mc_addr_count, ixgbe_mc_addr_itr next, bool clear)
+{
+	UNREFERENCED_5PARAMETER(hw, mc_addr_list, mc_addr_count, next, clear);
+
+	return (IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
+/**
+ * ixgbevf_hv_update_xcast_mode - Leave receive-mode policy to Hyper-V
+ * @hw: unused
+ * @xcast_mode: unused
+ */
+static s32
+ixgbevf_hv_update_xcast_mode(struct ixgbe_hw *hw, int xcast_mode)
+{
+	UNREFERENCED_2PARAMETER(hw, xcast_mode);
+
+	return (IXGBE_SUCCESS);
+}
+
+/**
+ * ixgbevf_hv_set_vfta_vf - Hyper-V variant - just a stub.
+ * @hw: unused
+ * @vlan: unused
+ * @vind: unused
+ * @vlan_on: unused
+ * @vlvf_bypass: unused
+ */
+static s32
+ixgbevf_hv_set_vfta_vf(struct ixgbe_hw *hw, u32 vlan, u32 vind,
+    bool vlan_on, bool vlvf_bypass)
+{
+	UNREFERENCED_5PARAMETER(hw, vlan, vind, vlan_on, vlvf_bypass);
+
+	return (IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
+static s32
+ixgbevf_hv_set_uc_addr_vf(struct ixgbe_hw *hw, u32 index, u8 *addr)
+{
+	UNREFERENCED_3PARAMETER(hw, index, addr);
+
+	return (IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
+/**
+ * ixgbevf_hv_reset_hw_vf - Reset through the Hyper-V PCI side channel
+ * @hw: pointer to hardware structure
+ *
+ * Hyper-V returns the permanent VF address when the guest reads six bytes
+ * beginning at offset 0x201 in PCI configuration space.  The reads also
+ * perform the host-side VF reset handshake.
+ */
+static s32
+ixgbevf_hv_reset_hw_vf(struct ixgbe_hw *hw)
+{
+	int i;
+
+	hw->api_version = ixgbe_mbox_api_10;
+	for (i = 0; i < IXGBE_ETH_LENGTH_OF_ADDRESS; i++)
+		hw->mac.perm_addr[i] = IXGBE_READ_PCIE_BYTE(hw,
+		    IXGBE_HV_RESET_OFFSET + i);
+	if (ixgbe_validate_mac_addr(hw->mac.perm_addr) != IXGBE_SUCCESS)
+		return (IXGBE_ERR_INVALID_MAC_ADDR);
+
+	return (IXGBE_SUCCESS);
+}
+
+/**
+ * ixgbevf_hv_set_rar_vf - Hyper-V variant - just a stub.
+ * @hw: unused
+ * @index: unused
+ * @addr: unused
+ * @vmdq: unused
+ * @enable_addr: unused
+ */
+static s32
+ixgbevf_hv_set_rar_vf(struct ixgbe_hw *hw, u32 index, u8 *addr, u32 vmdq,
+    u32 enable_addr)
+{
+	UNREFERENCED_5PARAMETER(hw, index, addr, vmdq, enable_addr);
+
+	return (IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
+static u32
+ixgbevf_hv_read_links(struct ixgbe_hw *hw)
+{
+	u32 links_reg;
+	int i;
+
+	if (hw->mac.type != ixgbe_mac_E610_vf)
+		return (IXGBE_READ_REG(hw, IXGBE_VFLINKS));
+
+	links_reg = 0;
+	for (i = 0; i < IXGBE_HV_LINK_STATUS_SIZE; i++)
+		links_reg |= (u32)IXGBE_READ_PCIE_BYTE(hw,
+		    IXGBE_HV_LINK_STATUS_OFFSET + i) << (i * NBBY);
+	return (links_reg);
+}
+
+/**
+ * ixgbevf_hv_check_mac_link_vf - Check link without mailbox communication
+ * @hw: pointer to hardware structure
+ * @speed: pointer to link speed
+ * @link_up: true is link is up, false otherwise
+ * @autoneg_wait_to_complete: unused
+ */
+static s32
+ixgbevf_hv_check_mac_link_vf(struct ixgbe_hw *hw, ixgbe_link_speed *speed,
+    bool *link_up, bool autoneg_wait_to_complete)
+{
+	struct ixgbe_mbx_info *mbx = &hw->mbx;
+	struct ixgbe_mac_info *mac = &hw->mac;
+	u32 links_reg;
+
+	UNREFERENCED_1PARAMETER(autoneg_wait_to_complete);
+
+	/* If we were hit with a reset, drop the cached link state. */
+	if (!mbx->ops[0].check_for_rst(hw, 0) || !mbx->timeout)
+		mac->get_link_status = true;
+	if (!mac->get_link_status)
+		goto out;
+
+	links_reg = ixgbevf_hv_read_links(hw);
+	if (!(links_reg & IXGBE_LINKS_UP))
+		goto out;
+
+	/* Link status can take up to 500 usec to settle on 82599. */
+	if (mac->type == ixgbe_mac_82599_vf) {
+		int i;
+
+		for (i = 0; i < 5; i++) {
+			usec_delay(100);
+			links_reg = ixgbevf_hv_read_links(hw);
+			if (!(links_reg & IXGBE_LINKS_UP))
+				goto out;
+		}
+	}
+
+	switch (links_reg & IXGBE_LINKS_SPEED_82599) {
+	case IXGBE_LINKS_SPEED_10G_82599:
+		*speed = IXGBE_LINK_SPEED_10GB_FULL;
+		if (hw->mac.type >= ixgbe_mac_X550_vf &&
+		    (links_reg & IXGBE_LINKS_SPEED_NON_STD) != 0)
+			*speed = IXGBE_LINK_SPEED_2_5GB_FULL;
+		break;
+	case IXGBE_LINKS_SPEED_1G_82599:
+		*speed = IXGBE_LINK_SPEED_1GB_FULL;
+		break;
+	case IXGBE_LINKS_SPEED_100_82599:
+		*speed = IXGBE_LINK_SPEED_100_FULL;
+		if ((hw->mac.type == ixgbe_mac_X550_vf ||
+		    hw->mac.type == ixgbe_mac_E610_vf) &&
+		    (links_reg & IXGBE_LINKS_SPEED_NON_STD) != 0)
+			*speed = IXGBE_LINK_SPEED_5GB_FULL;
+		break;
+	case IXGBE_LINKS_SPEED_10_X550EM_A:
+		*speed = IXGBE_LINK_SPEED_UNKNOWN;
+		if (hw->mac.type >= ixgbe_mac_X550_vf)
+			*speed = IXGBE_LINK_SPEED_10_FULL;
+		break;
+	default:
+		*speed = IXGBE_LINK_SPEED_UNKNOWN;
+	}
+
+	mac->get_link_status = false;
+
+out:
+	*link_up = !mac->get_link_status;
+	return (IXGBE_SUCCESS);
+}
+
+/**
+ * ixgbevf_hv_get_link_state_vf - Hyper-V has no mailbox link policy
+ * @hw: unused
+ * @link_state: unused
+ */
+static s32
+ixgbevf_hv_get_link_state_vf(struct ixgbe_hw *hw, bool *link_state)
+{
+	UNREFERENCED_2PARAMETER(hw, link_state);
+
+	return (IXGBE_ERR_FEATURE_NOT_SUPPORTED);
+}
+
+/**
+ * ixgbevf_hv_set_rlpml_vf - Set the maximum receive packet length
+ * @hw: pointer to hardware structure
+ * @max_size: maximum frame size
+ */
+static s32
+ixgbevf_hv_set_rlpml_vf(struct ixgbe_hw *hw, u16 max_size)
+{
+	u32 reg;
+
+	/* RLPML is not implemented by the 82599 VF. */
+	if (hw->mac.type == ixgbe_mac_82599_vf)
+		return (IXGBE_SUCCESS);
+
+	reg = IXGBE_READ_REG(hw, IXGBE_VFRXDCTL(0));
+	reg &= ~IXGBE_RXDCTL_RLPMLMASK;
+	reg |= ((max_size + 4) & IXGBE_RXDCTL_RLPMLMASK) |
+	    IXGBE_RXDCTL_RLPML_EN;
+	IXGBE_WRITE_REG(hw, IXGBE_VFRXDCTL(0), reg);
+
+	return (IXGBE_SUCCESS);
+}
+
+/**
+ * ixgbevf_hv_negotiate_api_version_vf - Negotiate the Hyper-V API version
+ * @hw: pointer to hardware structure
+ * @api: requested API version
+ */
+static int
+ixgbevf_hv_negotiate_api_version_vf(struct ixgbe_hw *hw, int api)
+{
+	if (api != ixgbe_mbox_api_10)
+		return (IXGBE_ERR_INVALID_ARGUMENT);
+
+	hw->api_version = api;
+	return (IXGBE_SUCCESS);
+}
+
+/**
+ * ixgbevf_hv_init_ops_vf - Initialize Hyper-V VF operations
+ * @hw: pointer to hardware structure
+ */
+s32
+ixgbevf_hv_init_ops_vf(struct ixgbe_hw *hw)
+{
+	s32 status;
+
+	status = ixgbe_init_ops_vf(hw);
+	if (status != IXGBE_SUCCESS)
+		return (status);
+
+	hw->mac.ops.reset_hw = ixgbevf_hv_reset_hw_vf;
+	hw->mac.ops.check_link = ixgbevf_hv_check_mac_link_vf;
+	hw->mac.ops.negotiate_api_version =
+	    ixgbevf_hv_negotiate_api_version_vf;
+	hw->mac.ops.set_rar = ixgbevf_hv_set_rar_vf;
+	hw->mac.ops.update_mc_addr_list =
+	    ixgbevf_hv_update_mc_addr_list_vf;
+	hw->mac.ops.update_xcast_mode = ixgbevf_hv_update_xcast_mode;
+	hw->mac.ops.get_link_state = ixgbevf_hv_get_link_state_vf;
+	hw->mac.ops.set_uc_addr = ixgbevf_hv_set_uc_addr_vf;
+	hw->mac.ops.set_vfta = ixgbevf_hv_set_vfta_vf;
+	hw->mac.ops.set_rlpml = ixgbevf_hv_set_rlpml_vf;
+
+	return (IXGBE_SUCCESS);
+}
diff --git a/sys/dev/ixgbe/ixgbe_hv_vf.h b/sys/dev/ixgbe/ixgbe_hv_vf.h
new file mode 100644
index 000000000000..dd2e1eee4e9c
--- /dev/null
+++ b/sys/dev/ixgbe/ixgbe_hv_vf.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2001-2020 Intel Corporation
+ */
+
+#ifndef _IXGBE_HV_VF_H_
+#define _IXGBE_HV_VF_H_
+
+#include "ixgbe_type.h"
+
+s32 ixgbevf_hv_init_ops_vf(struct ixgbe_hw *hw);
+
+#endif /* _IXGBE_HV_VF_H_ */
diff --git a/sys/dev/ixgbe/ixgbe_osdep.c b/sys/dev/ixgbe/ixgbe_osdep.c
index d96e15f4f87f..00b7f27faead 100644
--- a/sys/dev/ixgbe/ixgbe_osdep.c
+++ b/sys/dev/ixgbe/ixgbe_osdep.c
@@ -39,10 +39,16 @@ ixgbe_dev_from_hw(struct ixgbe_hw *hw)
 	return ((struct ixgbe_softc *)hw->back)->dev;
 }
 
+inline u8
+ixgbe_read_pci_cfg_byte(struct ixgbe_hw *hw, u32 reg)
+{
+	return (pci_read_config(ixgbe_dev_from_hw(hw), reg, 1));
+}
+
 inline u16
 ixgbe_read_pci_cfg(struct ixgbe_hw *hw, u32 reg)
 {
-	return pci_read_config(((struct ixgbe_softc *)hw->back)->dev, reg, 2);
+	return (pci_read_config(ixgbe_dev_from_hw(hw), reg, 2));
 }
 
 inline void
diff --git a/sys/dev/ixgbe/ixgbe_osdep.h b/sys/dev/ixgbe/ixgbe_osdep.h
index ec2b3274ec31..2066033ea174 100644
--- a/sys/dev/ixgbe/ixgbe_osdep.h
+++ b/sys/dev/ixgbe/ixgbe_osdep.h
@@ -126,6 +126,7 @@ enum {
 #define UNREFERENCED_2PARAMETER(_p, _q)
 #define UNREFERENCED_3PARAMETER(_p, _q, _r)
 #define UNREFERENCED_4PARAMETER(_p, _q, _r, _s)
+#define UNREFERENCED_5PARAMETER(_p, _q, _r, _s, _t)
 
 #define IXGBE_NTOHL(_i)	ntohl(_i)
 #define IXGBE_NTOHS(_i)	ntohs(_i)
@@ -208,6 +209,9 @@ struct ixgbe_hw;
 device_t ixgbe_dev_from_hw(struct ixgbe_hw *hw);
 
 /* These routines are needed by the shared code */
+extern u8 ixgbe_read_pci_cfg_byte(struct ixgbe_hw *, u32);
+#define IXGBE_READ_PCIE_BYTE ixgbe_read_pci_cfg_byte
+
 extern u16 ixgbe_read_pci_cfg(struct ixgbe_hw *, u32);
 #define IXGBE_READ_PCIE_WORD ixgbe_read_pci_cfg
 
diff --git a/sys/modules/ixv/Makefile b/sys/modules/ixv/Makefile
index 743b2b407a5e..c2b27a689f76 100644
--- a/sys/modules/ixv/Makefile
+++ b/sys/modules/ixv/Makefile
@@ -6,6 +6,7 @@ SRCS	+= opt_inet.h opt_inet6.h opt_rss.h
 SRCS    += if_ixv.c if_fdir.c ix_txrx.c ixgbe_osdep.c
 # Shared source
 SRCS    += ixgbe_common.c ixgbe_api.c ixgbe_phy.c ixgbe_mbx.c ixgbe_vf.c
+SRCS    += ixgbe_hv_vf.c
 SRCS    += ixgbe_dcb.c ixgbe_dcb_82598.c ixgbe_dcb_82599.c
 SRCS    += ixgbe_82598.c ixgbe_82599.c ixgbe_x540.c ixgbe_x550.c ixgbe_e610.c
 SRCS    += ixgbe_fw_logging.c