Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 09 Aug 2026 06:47:44 +0000
From:      Kevin Bowling <kbowling@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 2c04cfa148ec - main - ifconfig: Use nvlist to report SR-IOV VF status
Message-ID:  <6a782290.180ab.29135cbb@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=2c04cfa148ec4dd5cef7e228aaea6a05957fcb15

commit 2c04cfa148ec4dd5cef7e228aaea6a05957fcb15
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-08 17:34:07 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-09 06:46:41 +0000

    ifconfig: Use nvlist to report SR-IOV VF status
    
    Replace the records with a versioned nvlist transported through struct
    ifreq, following SIOCGIFCAPNV.  The network stack now packs and copies
    results, supports bounded retry for larger results, and handles native
    and 32-bit callers centrally.  Drivers only populate a kernel nvlist
    while their state is locked.
    
    Define optional common fields for identity, configuration and handshake
    state, VLAN policy, queue resources, runtime blocks, PF link state, and
    namespaced driver extensions.  Document the extension and versioning
    contract and require providers to omit values they cannot observe.
    
    Improve the ixl provider to track its mailbox handshake and report the
    expanded common policy.  Render the expanded status as grouped output
    under ifconfig -v.
---
 sbin/ifconfig/ifconfig.8      |  54 +++++++++-
 sbin/ifconfig/ifvfstatus.c    | 228 +++++++++++++++++++++++++++++++++++++-----
 sys/dev/ixl/if_ixl.c          |  52 ++++++----
 sys/dev/ixl/ixl_pf.h          |   1 +
 sys/dev/ixl/ixl_pf_iov.c      |   2 +
 sys/net/if.c                  | 174 ++++++++++++++++++++++++++++++--
 sys/net/if.h                  |  86 +++++++++++++---
 sys/net/if_var.h              |   1 +
 sys/net/ifdi_if.m             |   9 +-
 sys/net/iflib.c               |   9 +-
 sys/sys/sockio.h              |   2 +-
 usr.sbin/iovctl/iovctl.8      |  12 ++-
 usr.sbin/iovctl/iovctl.conf.5 |  12 ++-
 13 files changed, 555 insertions(+), 87 deletions(-)

diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index 1dc599a61623..eb0f663d149c 100644
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -28,7 +28,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd December 11, 2025
+.Dd August 8, 2026
 .Dt IFCONFIG 8
 .Os
 .Sh NAME
@@ -325,6 +325,58 @@ will attempt to load it.
 Display only the interfaces that are up.
 .It Fl v
 Get more verbose status for an interface.
+When supported, this includes the status of configured SR-IOV virtual
+functions.
+Fields that the PF driver cannot observe are omitted; an omitted field does
+not mean false or zero.
+The status begins with the PF link state and speed normally advertised to
+VFs, followed by the number of VFs and one record for each VF.
+PF link status does not demonstrate that a VF driver is operational.
+.Pp
+Per-VF fields are arranged in the following groups:
+.Bl -tag -width "resources"
+.It Cm identity
+The primary MAC address known to the PF.
+.It Cm state
+.Cm configured=yes
+means that the PF accepted the VF configuration.
+.Cm initialized=yes
+means that the VF completed its driver or mailbox handshake since its last
+reset.
+When available, this group also reports whether traffic is enabled, whether
+malicious-driver detection blocked the VF, and whether it is quarantined.
+.It Cm resources
+The number of symmetric RX/TX queue pairs allocated to the VF.
+This is not necessarily the number of queue pairs currently used by its
+driver.
+.It Cm vlan
+An access VLAN is imposed by the PF; trunk mode means that no access VLAN is
+imposed and does not promise unlimited filter capacity.
+The filter count includes explicit filters recorded by the PF and excludes
+implicit untagged and priority-tag membership.
+.It Cm policy
+Administrative permissions for VF requests, MAC anti-spoofing state, and an
+optional per-VF link-state policy.
+Permissions describe what the VF may request, not its current requests.
+.It Cm protocol
+The negotiated PF/VF mailbox API version, when known.
+.El
+.Pp
+For example:
+.Bd -literal -offset indent
+VF-visible PF link: state=up speed=10000Mbps
+virtual functions: 1
+	vf   0:
+		identity: mac=02:00:00:00:00:01
+		state: configured=yes initialized=yes traffic=enabled
+		resources: queue-pairs=4
+		vlan: mode=trunk filters=0
+		policy: set-mac=denied set-vlan=allowed anti-spoof=on
+.Ed
+.Pp
+See
+.Xr iovctl.conf 5
+for VF configuration.
 .It Ar address
 For the inet family,
 the address is either a host name present in the host name data
diff --git a/sbin/ifconfig/ifvfstatus.c b/sbin/ifconfig/ifvfstatus.c
index 0c90d667855c..af857944a12a 100644
--- a/sbin/ifconfig/ifvfstatus.c
+++ b/sbin/ifconfig/ifvfstatus.c
@@ -28,51 +28,231 @@
  */
 
 #include <sys/ioctl.h>
+#include <sys/nv.h>
 #include <sys/socket.h>
 
 #include <net/ethernet.h>
 #include <net/if.h>
 
 #include <err.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "ifconfig.h"
 
+static void
+vf_group_begin(bool *printed, const char *name)
+{
+	if (!*printed) {
+		printf("\t\t\t%s:", name);
+		*printed = true;
+	}
+}
+
+static void
+vf_group_end(bool printed)
+{
+	if (printed)
+		putchar('\n');
+}
+
 void
 vf_status(if_ctx *ctx)
 {
-	struct ifvfstatus_entry *entries;
-	struct ifvfstatus ifvfs;
+	const nvlist_t * const *vfs;
+	const nvlist_t *vf;
+	struct ifreq ifr;
+	const void *mac;
+	nvlist_t *status;
+	const char *mode, *state;
+	size_t maclen, num_vfs;
+	uint64_t speed;
+	void *buf, *newbuf;
+	u_int buflen, nextlen;
+	bool printed;
 
-	memset(&ifvfs, 0, sizeof(ifvfs));
-	strlcpy(ifvfs.ifvfs_name, ctx->ifname, sizeof(ifvfs.ifvfs_name));
-	if (ioctl_ctx(ctx, SIOCGIFVFSTATUS, &ifvfs) < 0)
-		return;
-	if (ifvfs.ifvfs_count == 0)
-		return;
+	buflen = 16 * 1024;
+	buf = malloc(buflen);
+	if (buf == NULL)
+		err(1, "malloc");
+	for (;;) {
+		memset(&ifr, 0, sizeof(ifr));
+		strlcpy(ifr.ifr_name, ctx->ifname, sizeof(ifr.ifr_name));
+		ifr.ifr_vf_status_nv.buffer = buf;
+		ifr.ifr_vf_status_nv.buf_length = buflen;
+		if (ioctl_ctx(ctx, SIOCGIFVFSTATUS, &ifr) == 0)
+			break;
+		if (errno == EINVAL || errno == ENOTTY || errno == ENXIO ||
+		    errno == ENOTSUP || errno == EOPNOTSUPP) {
+			free(buf);
+			return;
+		}
+		if (errno != EFBIG ||
+		    buflen == IFR_VF_STATUS_NV_MAXBUFSIZE ||
+		    ifr.ifr_vf_status_nv.length >
+		    IFR_VF_STATUS_NV_MAXBUFSIZE) {
+			free(buf);
+			warn("SIOCGIFVFSTATUS");
+			return;
+		}
+		if (ifr.ifr_vf_status_nv.length > buflen)
+			nextlen = ifr.ifr_vf_status_nv.length;
+		else if (buflen > IFR_VF_STATUS_NV_MAXBUFSIZE / 2)
+			nextlen = IFR_VF_STATUS_NV_MAXBUFSIZE;
+		else
+			nextlen = buflen * 2;
+		newbuf = realloc(buf, nextlen);
+		if (newbuf == NULL) {
+			free(buf);
+			err(1, "realloc");
+		}
+		buf = newbuf;
+		buflen = nextlen;
+	}
 
-	entries = calloc(ifvfs.ifvfs_count, sizeof(*entries));
-	if (entries == NULL)
-		err(1, "calloc");
-	ifvfs.ifvfs_list = entries;
-	if (ioctl_ctx(ctx, SIOCGIFVFSTATUS, &ifvfs) < 0) {
-		free(entries);
-		warn("SIOCGIFVFSTATUS");
+	status = nvlist_unpack(buf, ifr.ifr_vf_status_nv.length, 0);
+	free(buf);
+	if (status == NULL ||
+	    !nvlist_exists_number(status, IFVF_STATUS_VERSION_KEY) ||
+	    nvlist_get_number(status, IFVF_STATUS_VERSION_KEY) !=
+	    IFVF_STATUS_VERSION ||
+	    !nvlist_exists_nvlist_array(status, IFVF_STATUS_VFS)) {
+		warnx("SIOCGIFVFSTATUS returned an unsupported format");
+		nvlist_destroy(status);
 		return;
 	}
 
-	printf("\tvirtual functions: %d\n", ifvfs.ifvfs_count);
-	for (int i = 0; i < ifvfs.ifvfs_count; i++) {
-		printf("\t\tvf %3d: mac %s", i,
-		    ether_ntoa((const struct ether_addr *)entries[i].mac_addr));
-		if (entries[i].vlan > -1)
-			printf(" vlan %d", entries[i].vlan);
-		if (entries[i].active)
-			printf(" active");
+	if (nvlist_exists_string(status, IFVF_STATUS_PF_LINK_STATE)) {
+		state = nvlist_get_string(status, IFVF_STATUS_PF_LINK_STATE);
+		printf("\tVF-visible PF link: state=%s", state);
+		if (nvlist_exists_number(status, IFVF_STATUS_PF_LINK_SPEED)) {
+			speed = nvlist_get_number(status,
+			    IFVF_STATUS_PF_LINK_SPEED);
+			if (speed % IF_Mbps(1) == 0)
+				printf(" speed=%juMbps", (uintmax_t)(speed /
+				    IF_Mbps(1)));
+			else
+				printf(" speed=%jubps", (uintmax_t)speed);
+		}
 		putchar('\n');
 	}
 
-	free(entries);
+	vfs = nvlist_get_nvlist_array(status, IFVF_STATUS_VFS, &num_vfs);
+	printf("\tvirtual functions: %zu\n", num_vfs);
+	for (size_t i = 0; i < num_vfs; i++) {
+		vf = vfs[i];
+		printf("\t\tvf %3ju:\n",
+		    (uintmax_t)(nvlist_exists_number(vf, IFVF_STATUS_INDEX) ?
+		    nvlist_get_number(vf, IFVF_STATUS_INDEX) : i));
+
+		printed = false;
+		if (nvlist_exists_binary(vf, IFVF_STATUS_MAC)) {
+			mac = nvlist_get_binary(vf, IFVF_STATUS_MAC, &maclen);
+			if (maclen == ETHER_ADDR_LEN) {
+				vf_group_begin(&printed, "identity");
+				printf(" mac=%s", ether_ntoa(mac));
+			}
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if (nvlist_exists_bool(vf, IFVF_STATUS_CONFIGURED)) {
+			vf_group_begin(&printed, "state");
+			printf(" configured=%s",
+			    nvlist_get_bool(vf, IFVF_STATUS_CONFIGURED) ?
+			    "yes" : "no");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_INITIALIZED)) {
+			vf_group_begin(&printed, "state");
+			printf(" initialized=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_INITIALIZED) ? "yes" : "no");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_TRAFFIC_ENABLED)) {
+			vf_group_begin(&printed, "state");
+			printf(" traffic=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_TRAFFIC_ENABLED) ? "enabled" : "disabled");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_MDD_BLOCKED)) {
+			vf_group_begin(&printed, "state");
+			printf(" mdd-blocked=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_MDD_BLOCKED) ? "yes" : "no");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_QUARANTINED)) {
+			vf_group_begin(&printed, "state");
+			printf(" quarantined=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_QUARANTINED) ? "yes" : "no");
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if (nvlist_exists_number(vf, IFVF_STATUS_NUM_QUEUES)) {
+			vf_group_begin(&printed, "resources");
+			printf(" queue-pairs=%ju",
+			    (uintmax_t)nvlist_get_number(vf,
+			    IFVF_STATUS_NUM_QUEUES));
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if (nvlist_exists_string(vf, IFVF_STATUS_VLAN_MODE)) {
+			mode = nvlist_get_string(vf, IFVF_STATUS_VLAN_MODE);
+			vf_group_begin(&printed, "vlan");
+			printf(" mode=%s", mode);
+			if (strcmp(mode, IFVF_VLAN_MODE_ACCESS) == 0 &&
+			    nvlist_exists_number(vf, IFVF_STATUS_VLAN))
+				printf(" vid=%ju", (uintmax_t)nvlist_get_number(vf,
+				    IFVF_STATUS_VLAN));
+		}
+		if (nvlist_exists_number(vf, IFVF_STATUS_VLAN_COUNT)) {
+			vf_group_begin(&printed, "vlan");
+			printf(" filters=%ju", (uintmax_t)nvlist_get_number(vf,
+			    IFVF_STATUS_VLAN_COUNT));
+		}
+		if (nvlist_exists_number(vf, IFVF_STATUS_VLAN_LIMIT)) {
+			vf_group_begin(&printed, "vlan");
+			printf(" limit=%ju", (uintmax_t)nvlist_get_number(vf,
+			    IFVF_STATUS_VLAN_LIMIT));
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if (nvlist_exists_bool(vf, IFVF_STATUS_ALLOW_SET_MAC)) {
+			vf_group_begin(&printed, "policy");
+			printf(" set-mac=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_ALLOW_SET_MAC) ? "allowed" : "denied");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_ALLOW_SET_VLAN)) {
+			vf_group_begin(&printed, "policy");
+			printf(" set-vlan=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_ALLOW_SET_VLAN) ? "allowed" : "denied");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_MAC_ANTI_SPOOF)) {
+			vf_group_begin(&printed, "policy");
+			printf(" anti-spoof=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_MAC_ANTI_SPOOF) ? "on" : "off");
+		}
+		if (nvlist_exists_bool(vf, IFVF_STATUS_ALLOW_PROMISC)) {
+			vf_group_begin(&printed, "policy");
+			printf(" promisc=%s", nvlist_get_bool(vf,
+			    IFVF_STATUS_ALLOW_PROMISC) ? "allowed" : "denied");
+		}
+		if (nvlist_exists_string(vf, IFVF_STATUS_LINK_STATE_POLICY)) {
+			vf_group_begin(&printed, "policy");
+			printf(" link-state=%s", nvlist_get_string(vf,
+			    IFVF_STATUS_LINK_STATE_POLICY));
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if (nvlist_exists_string(vf, IFVF_STATUS_API_VERSION)) {
+			vf_group_begin(&printed, "protocol");
+			printf(" api=%s", nvlist_get_string(vf,
+			    IFVF_STATUS_API_VERSION));
+		}
+		vf_group_end(printed);
+
+	}
+	nvlist_destroy(status);
 }
diff --git a/sys/dev/ixl/if_ixl.c b/sys/dev/ixl/if_ixl.c
index 439b93f0b80a..11e78235f9f9 100644
--- a/sys/dev/ixl/if_ixl.c
+++ b/sys/dev/ixl/if_ixl.c
@@ -122,7 +122,7 @@ static uint64_t	 ixl_if_get_counter(if_ctx_t ctx, ift_counter cnt);
 static int	 ixl_if_i2c_req(if_ctx_t ctx, struct ifi2creq *req);
 static int	 ixl_if_priv_ioctl(if_ctx_t ctx, u_long command, caddr_t data);
 static bool	 ixl_if_needs_restart(if_ctx_t ctx, enum iflib_restart_event event);
-static int	 ixl_if_vfstat_ioctl(if_ctx_t ctx, struct ifvfstatus *ifvfs);
+static int	 ixl_if_vf_status(if_ctx_t ctx, nvlist_t *status);
 #ifdef PCI_IOV
 static void	 ixl_if_vflr_handle(if_ctx_t ctx);
 #endif
@@ -194,7 +194,7 @@ static device_method_t ixl_if_methods[] = {
 	DEVMETHOD(ifdi_i2c_req, ixl_if_i2c_req),
 	DEVMETHOD(ifdi_priv_ioctl, ixl_if_priv_ioctl),
 	DEVMETHOD(ifdi_needs_restart, ixl_if_needs_restart),
-	DEVMETHOD(ifdi_vfstat_ioctl, ixl_if_vfstat_ioctl),
+	DEVMETHOD(ifdi_vf_status, ixl_if_vf_status),
 #ifdef PCI_IOV
 	DEVMETHOD(ifdi_iov_init, ixl_if_iov_init),
 	DEVMETHOD(ifdi_iov_uninit, ixl_if_iov_uninit),
@@ -1911,35 +1911,49 @@ ixl_if_needs_restart(if_ctx_t ctx __unused, enum iflib_restart_event event)
 }
 
 static int
-ixl_if_vfstat_ioctl(if_ctx_t ctx, struct ifvfstatus *ifvfs)
+ixl_if_vf_status(if_ctx_t ctx, nvlist_t *status)
 {
 	struct ixl_pf *pf;
-	struct ifvfstatus_entry ent;
+	nvlist_t **vfs;
 	struct ixl_vf *vf;
 	int error;
 
 	pf = iflib_get_softc(ctx);
 	if (pf->num_vfs < 1)
 		return (ENXIO);
-	if (ifvfs->ifvfs_count == 0) {
-		ifvfs->ifvfs_count = pf->num_vfs;
-		return (0);
-	}
-	if (ifvfs->ifvfs_count != pf->num_vfs)
-		return (EINVAL);
 
+	vfs = mallocarray(pf->num_vfs, sizeof(*vfs), M_IXL,
+	    M_WAITOK | M_ZERO);
 	for (int i = 0; i < pf->num_vfs; i++) {
 		vf = &pf->vfs[i];
-		memset(&ent, 0, sizeof(ent));
-		ent.active = !!(vf->vf_flags & VF_FLAG_ENABLED);
-		memcpy(ent.mac_addr, vf->mac, sizeof(ent.mac_addr));
-		/* No host VLAN support. */
-		ent.vlan = -1;
-		error = copyout(&ent, &ifvfs->ifvfs_list[i], sizeof(ent));
-		if (error != 0)
-			return (error);
+		vfs[i] = nvlist_create(0);
+		nvlist_add_number(vfs[i], IFVF_STATUS_INDEX, vf->vf_num);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_CONFIGURED,
+		    (vf->vf_flags & VF_FLAG_ENABLED) != 0);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_INITIALIZED,
+		    (vf->vf_flags & VF_FLAG_INITIALIZED) != 0);
+		nvlist_add_binary(vfs[i], IFVF_STATUS_MAC, vf->mac,
+		    ETHER_ADDR_LEN);
+		nvlist_add_string(vfs[i], IFVF_STATUS_VLAN_MODE,
+		    IFVF_VLAN_MODE_TRUNK);
+		nvlist_add_number(vfs[i], IFVF_STATUS_NUM_QUEUES,
+		    vf->qtag.num_active);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_ALLOW_SET_MAC,
+		    (vf->vf_flags & VF_FLAG_SET_MAC_CAP) != 0);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_ALLOW_SET_VLAN,
+		    (vf->vf_flags & VF_FLAG_VLAN_CAP) != 0);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_MAC_ANTI_SPOOF,
+		    (vf->vf_flags & VF_FLAG_MAC_ANTI_SPOOF) != 0);
+		nvlist_add_bool(vfs[i], IFVF_STATUS_ALLOW_PROMISC,
+		    (vf->vf_flags & VF_FLAG_PROMISC_CAP) != 0);
 	}
-	return (0);
+	nvlist_add_nvlist_array(status, IFVF_STATUS_VFS,
+	    (const nvlist_t * const *)vfs, pf->num_vfs);
+	error = nvlist_error(status);
+	for (int i = 0; i < pf->num_vfs; i++)
+		nvlist_destroy(vfs[i]);
+	free(vfs, M_IXL);
+	return (error);
 }
 
 /*
diff --git a/sys/dev/ixl/ixl_pf.h b/sys/dev/ixl/ixl_pf.h
index f0d42f18cef0..465a72092da1 100644
--- a/sys/dev/ixl/ixl_pf.h
+++ b/sys/dev/ixl/ixl_pf.h
@@ -45,6 +45,7 @@
 #define	VF_FLAG_VLAN_CAP		0x04
 #define	VF_FLAG_PROMISC_CAP		0x08
 #define	VF_FLAG_MAC_ANTI_SPOOF		0x10
+#define	VF_FLAG_INITIALIZED		0x20
 
 #define IXL_ICR0_CRIT_ERR_MASK 			\
     (I40E_PFINT_ICR0_PCI_EXCEPTION_MASK | 	\
diff --git a/sys/dev/ixl/ixl_pf_iov.c b/sys/dev/ixl/ixl_pf_iov.c
index 1e475e4767cc..de8e2c27b9eb 100644
--- a/sys/dev/ixl/ixl_pf_iov.c
+++ b/sys/dev/ixl/ixl_pf_iov.c
@@ -397,6 +397,7 @@ ixl_reinit_vf(struct ixl_pf *pf, struct ixl_vf *vf)
 	int i, error;
 
 	hw = &pf->hw;
+	vf->vf_flags &= ~VF_FLAG_INITIALIZED;
 
 	error = ixl_flush_pcie(pf, vf);
 	if (error != 0)
@@ -550,6 +551,7 @@ ixl_vf_get_resources_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 
 	ixl_send_vf_msg(pf, vf, VIRTCHNL_OP_GET_VF_RESOURCES,
 	    I40E_SUCCESS, &reply, sizeof(reply));
+	vf->vf_flags |= VF_FLAG_INITIALIZED;
 }
 
 static int
diff --git a/sys/net/if.c b/sys/net/if.c
index 1c57eb1492c2..4598337f3964 100644
--- a/sys/net/if.c
+++ b/sys/net/if.c
@@ -124,6 +124,12 @@ struct ifreq_buffer32 {
 	uint32_t	buffer;		/* (void *) */
 };
 
+struct ifreq_nv_req32 {
+	u_int		buf_length;
+	u_int		length;
+	uint32_t	buffer;		/* (void *) */
+};
+
 /*
  * Interface request structure used for socket
  * ioctl's.  All interface ioctl's must have parameter
@@ -148,6 +154,7 @@ struct ifreq32 {
 		int		ifru_cap[2];
 		u_int		ifru_fib;
 		u_char		ifru_vlan_pcp;
+		struct ifreq_nv_req32 ifru_nv;
 	} ifr_ifru;
 };
 CTASSERT(sizeof(struct ifreq) == sizeof(struct ifreq32));
@@ -2237,6 +2244,74 @@ ifr_buffer_set_length(void *data, size_t len)
 		ifrup->ifr.ifr_ifru.ifru_buffer.length = len;
 }
 
+static void *
+ifr_nv_get_buffer(void *data)
+{
+	union ifreq_union *ifrup;
+
+	ifrup = data;
+#ifdef COMPAT_FREEBSD32
+	if (SV_CURPROC_FLAG(SV_ILP32))
+		return ((void *)(uintptr_t)
+		    ifrup->ifr32.ifr_ifru.ifru_nv.buffer);
+#endif
+	return (ifrup->ifr.ifr_ifru.ifru_nv.buffer);
+}
+
+static void
+ifr_nv_set_buffer_null(void *data)
+{
+	union ifreq_union *ifrup;
+
+	ifrup = data;
+#ifdef COMPAT_FREEBSD32
+	if (SV_CURPROC_FLAG(SV_ILP32))
+		ifrup->ifr32.ifr_ifru.ifru_nv.buffer = 0;
+	else
+#endif
+		ifrup->ifr.ifr_ifru.ifru_nv.buffer = NULL;
+}
+
+static u_int
+ifr_nv_get_buf_length(void *data)
+{
+	union ifreq_union *ifrup;
+
+	ifrup = data;
+#ifdef COMPAT_FREEBSD32
+	if (SV_CURPROC_FLAG(SV_ILP32))
+		return (ifrup->ifr32.ifr_ifru.ifru_nv.buf_length);
+#endif
+	return (ifrup->ifr.ifr_ifru.ifru_nv.buf_length);
+}
+
+static u_int
+ifr_nv_get_length(void *data)
+{
+	union ifreq_union *ifrup;
+
+	ifrup = data;
+#ifdef COMPAT_FREEBSD32
+	if (SV_CURPROC_FLAG(SV_ILP32))
+		return (ifrup->ifr32.ifr_ifru.ifru_nv.length);
+#endif
+	return (ifrup->ifr.ifr_ifru.ifru_nv.length);
+}
+
+static void
+ifr_nv_set_length(void *data, u_int len)
+{
+	union ifreq_union *ifrup;
+
+	ifrup = data;
+#ifdef COMPAT_FREEBSD32
+	if (SV_CURPROC_FLAG(SV_ILP32))
+		ifrup->ifr32.ifr_ifru.ifru_nv.length = len;
+	else
+#endif
+		ifrup->ifr.ifr_ifru.ifru_nv.length = len;
+}
+
 void *
 ifr_data_get_ptr(void *ifrp)
 {
@@ -2336,6 +2411,53 @@ if_capint_to_capnv(nvlist_t *nv, const struct ifcap_nv_bit_name *nn,
 	}
 }
 
+int
+if_get_vf_status(if_t ifp, nvlist_t **statusp)
+{
+	nvlist_t *status;
+	uint64_t baudrate;
+	int error;
+
+	*statusp = NULL;
+	if (ifp->if_ioctl == NULL)
+		return (EOPNOTSUPP);
+
+	status = nvlist_create(0);
+	nvlist_add_number(status, IFVF_STATUS_VERSION_KEY,
+	    IFVF_STATUS_VERSION);
+	error = (*ifp->if_ioctl)(ifp, SIOCGIFVFSTATUS,
+	    __DECONST(caddr_t, status));
+	if (error != 0)
+		goto out;
+
+	switch (if_getlinkstate(ifp)) {
+	case LINK_STATE_DOWN:
+		nvlist_add_string(status, IFVF_STATUS_PF_LINK_STATE,
+		    IFVF_LINK_STATE_DOWN);
+		break;
+	case LINK_STATE_UP:
+		nvlist_add_string(status, IFVF_STATUS_PF_LINK_STATE,
+		    IFVF_LINK_STATE_UP);
+		break;
+	default:
+		nvlist_add_string(status, IFVF_STATUS_PF_LINK_STATE,
+		    IFVF_LINK_STATE_UNKNOWN);
+		break;
+	}
+	baudrate = if_getbaudrate(ifp);
+	if (baudrate != 0)
+		nvlist_add_number(status, IFVF_STATUS_PF_LINK_SPEED, baudrate);
+	error = nvlist_error(status);
+	if (error == 0) {
+		*statusp = status;
+		return (0);
+	}
+
+out:
+	nvlist_destroy(status);
+	return (error);
+}
+
 /*
  * Hardware specific interface ioctls.
  */
@@ -2349,7 +2471,7 @@ ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td)
 	char *descrbuf;
 	char new_name[IFNAMSIZ];
 	void *buf;
-	nvlist_t *nvcap;
+	nvlist_t *nvcap, *nvstatus;
 	struct siocsifcapnv_driver_data drv_ioctl_data;
 
 	ifr = (struct ifreq *)data;
@@ -2396,20 +2518,50 @@ ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td)
 					error = EDOOFUS;
 				break;
 			}
-			if (nvbuflen > ifr->ifr_cap_nv.buf_length) {
-				ifr->ifr_cap_nv.length = nvbuflen;
-				ifr->ifr_cap_nv.buffer = NULL;
+			if (nvbuflen > ifr_nv_get_buf_length(data)) {
+				ifr_nv_set_length(data, nvbuflen);
+				ifr_nv_set_buffer_null(data);
 				error = EFBIG;
 				break;
 			}
-			ifr->ifr_cap_nv.length = nvbuflen;
-			error = copyout(buf, ifr->ifr_cap_nv.buffer, nvbuflen);
+			ifr_nv_set_length(data, nvbuflen);
+			error = copyout(buf, ifr_nv_get_buffer(data), nvbuflen);
 			break;
 		}
 		free(buf, M_NVLIST);
 		nvlist_destroy(nvcap);
 		break;
 
+	case SIOCGIFVFSTATUS:
+		buf = NULL;
+		nvstatus = NULL;
+		for (;;) {
+			error = if_get_vf_status(ifp, &nvstatus);
+			if (error != 0)
+				break;
+			buf = nvlist_pack(nvstatus, &nvbuflen);
+			if (buf == NULL) {
+				error = nvlist_error(nvstatus);
+				if (error == 0)
+					error = EDOOFUS;
+				break;
+			}
+			if (nvbuflen > IFR_VF_STATUS_NV_MAXBUFSIZE) {
+				error = E2BIG;
+				break;
+			}
+			ifr_nv_set_length(data, nvbuflen);
+			if (nvbuflen > ifr_nv_get_buf_length(data)) {
+				error = EFBIG;
+				break;
+			}
+			error = copyout(buf, ifr_nv_get_buffer(data), nvbuflen);
+			break;
+		}
+		free(buf, M_NVLIST);
+		nvlist_destroy(nvstatus);
+		break;
+
 	case SIOCGIFDATA:
 	{
 		struct if_data ifd;
@@ -2563,16 +2715,16 @@ ifhwioctl(u_long cmd, struct ifnet *ifp, caddr_t data, struct thread *td)
 			return (EOPNOTSUPP);
 		if ((ifp->if_capabilities & IFCAP_NV) == 0)
 			return (EINVAL);
-		if (ifr->ifr_cap_nv.length > IFR_CAP_NV_MAXBUFSIZE)
+		if (ifr_nv_get_length(data) > IFR_CAP_NV_MAXBUFSIZE)
 			return (EINVAL);
 		nvcap = NULL;
-		buf = malloc(ifr->ifr_cap_nv.length, M_TEMP, M_WAITOK);
+		buf = malloc(ifr_nv_get_length(data), M_TEMP, M_WAITOK);
 		for (;;) {
-			error = copyin(ifr->ifr_cap_nv.buffer, buf,
-			    ifr->ifr_cap_nv.length);
+			error = copyin(ifr_nv_get_buffer(data), buf,
+			    ifr_nv_get_length(data));
 			if (error != 0)
 				break;
-			nvcap = nvlist_unpack(buf, ifr->ifr_cap_nv.length, 0);
+			nvcap = nvlist_unpack(buf, ifr_nv_get_length(data), 0);
 			if (nvcap == NULL) {
 				error = EINVAL;
 				break;
diff --git a/sys/net/if.h b/sys/net/if.h
index c5b7768f033d..0e729a275f66 100644
--- a/sys/net/if.h
+++ b/sys/net/if.h
@@ -450,6 +450,76 @@ struct ifreq_nv_req {
 };
 
 #define	IFR_CAP_NV_MAXBUFSIZE	(2 * 1024 * 1024)
+#define	IFR_VF_STATUS_NV_MAXBUFSIZE	(2 * 1024 * 1024)
+
+/*
+ * SIOCGIFVFSTATUS schema contract.
+ *
+ * The top-level nvlist contains a version number and an array of per-VF
+ * nvlists.  Per-VF keys other than the index are optional: providers omit
+ * values that are unknown or cannot be observed rather than reporting a
+ * false or zero value.  "configured" means that the PF accepted the VF
+ * configuration, while "initialized" means that the VF completed its
+ * driver/mailbox handshake since its last reset.  The MAC address is the
+ * PF's known primary address for the VF.  The queue count is the number of
+ * allocated symmetric RX/TX queue pairs, not necessarily the number the
+ * guest currently uses.
+ *
+ * An access VLAN is imposed by the PF.  Trunk mode means that no access
+ * VLAN is imposed; it does not promise unlimited VLAN-filter capacity.
+ * The VLAN count is the number of explicit filters recorded by the PF and
+ * excludes implicit untagged or priority-tag membership.  "allow-*" values
+ * are administrative permissions, not the VF's current requests, and the
+ * blocked values describe current runtime state.  The top-level PF link
+ * state and speed are the PF ifnet values normally advertised to VFs, not
+ * evidence that a guest driver is operational; speed is in bits per second.
+ * A per-VF link-state policy, when present, can override PF link visibility.
+ * Driver-specific data is stored in the optional "extensions" nvlist.  Its
+ * keys are stable namespace names, not runtime device names.  Each namespace
+ * contains a mandatory version number and owns the types and meanings of its
+ * remaining keys.  Consumers must ignore unknown namespaces and keys.  A
+ * provider omits an extension value that it cannot observe, and must not
+ * duplicate a field whose semantics are already represented by the common
+ * schema.  Additive optional keys retain the namespace version; incompatible
+ * type or structural changes require a new namespace version.
+ *
+ * Additive optional common keys retain the top-level version.  Change the
+ * top-level version only for an incompatible type or structural change.
+ */
+#define	IFVF_STATUS_VERSION		1
+#define	IFVF_STATUS_VERSION_KEY		"version"
+#define	IFVF_STATUS_VFS			"vfs"
+#define	IFVF_STATUS_PF_LINK_STATE	"pf-link-state"
+#define	IFVF_STATUS_PF_LINK_SPEED	"pf-link-speed"
+#define	IFVF_STATUS_INDEX		"index"
+#define	IFVF_STATUS_CONFIGURED		"configured"
+#define	IFVF_STATUS_INITIALIZED		"initialized"
+#define	IFVF_STATUS_MAC			"mac-address"
+#define	IFVF_STATUS_VLAN_MODE		"vlan-mode"
+#define	IFVF_STATUS_VLAN		"vlan"
+#define	IFVF_STATUS_VLAN_COUNT		"vlan-count"
+#define	IFVF_STATUS_VLAN_LIMIT		"vlan-limit"
+#define	IFVF_STATUS_NUM_QUEUES		"num-queues"
+#define	IFVF_STATUS_ALLOW_SET_MAC	"allow-set-mac"
+#define	IFVF_STATUS_ALLOW_SET_VLAN	"allow-set-vlan"
+#define	IFVF_STATUS_MAC_ANTI_SPOOF	"mac-anti-spoof"
+#define	IFVF_STATUS_ALLOW_PROMISC	"allow-promisc"
+#define	IFVF_STATUS_TRAFFIC_ENABLED	"traffic-enabled"
+#define	IFVF_STATUS_MDD_BLOCKED		"mdd-blocked"
+#define	IFVF_STATUS_QUARANTINED		"quarantined"
+#define	IFVF_STATUS_API_VERSION		"api-version"
+#define	IFVF_STATUS_LINK_STATE_POLICY	"link-state-policy"
+#define	IFVF_STATUS_EXTENSIONS		"extensions"
+
+#define	IFVF_STATUS_EXT_VERSION		"version"
+
+#define	IFVF_VLAN_MODE_ACCESS		"access"
+#define	IFVF_VLAN_MODE_TRUNK		"trunk"
+
+#define	IFVF_LINK_STATE_UNKNOWN		"unknown"
+#define	IFVF_LINK_STATE_DOWN		"down"
+#define	IFVF_LINK_STATE_UP		"up"
+#define	IFVF_LINK_STATE_AUTO		"auto"
 
 /*
  * Interface request structure used for socket
@@ -500,6 +570,7 @@ struct ifreq {
 #define	ifr_vlan_pcp	ifr_ifru.ifru_vlan_pcp	/* VLAN priority */
 #define	ifr_lan_pcp	ifr_ifru.ifru_vlan_pcp	/* VLAN priority */
 #define	ifr_cap_nv	ifr_ifru.ifru_nv	/* nv-based cap interface */
+#define	ifr_vf_status_nv	ifr_ifru.ifru_nv /* nv-based VF status */
 };
 
 #define	_SIZEOF_ADDR_IFREQ(ifr) \
@@ -662,21 +733,6 @@ struct ifdownreason {
 	char		ifdr_msg[IFDR_MSG_SIZE];
 };
 
-struct ifvfstatus_entry {
-	uint8_t		mac_addr[6];
-	int16_t		vlan;
-	uint8_t		active;
-	uint8_t		spare0;
-	uint16_t	spare1;
-	uint32_t	spare2;
-};
-
-struct ifvfstatus {
-	char		ifvfs_name[IFNAMSIZ];
-	int		ifvfs_count;
-	struct ifvfstatus_entry *ifvfs_list;
-};
-
 #endif /* __BSD_VISIBLE */
 
 /*
diff --git a/sys/net/if_var.h b/sys/net/if_var.h
index 9a425fd81054..5b892ab7f426 100644
--- a/sys/net/if_var.h
+++ b/sys/net/if_var.h
@@ -763,6 +763,7 @@ void *ifr_buffer_get_buffer(void *data);
 size_t ifr_buffer_get_length(void *data);
 
 int ifhwioctl(u_long, if_t, caddr_t, struct thread *);
+int if_get_vf_status(if_t, struct nvlist **);
 
 #ifdef DEVICE_POLLING
 enum poll_cmd { POLL_ONLY, POLL_AND_CHECK_STATUS };
diff --git a/sys/net/ifdi_if.m b/sys/net/ifdi_if.m
index 65a130d64ee4..70e8a44e5f00 100644
--- a/sys/net/ifdi_if.m
+++ b/sys/net/ifdi_if.m
@@ -113,8 +113,7 @@ CODE {
 	}
 
 	static int
-	null_vfstat_ioctl(if_ctx_t _ctx __unused,
-	    struct ifvfstatus *_ifvfs __unused)
+	null_vf_status(if_ctx_t _ctx __unused, nvlist_t *_status __unused)
 	{
 		return (ENOTSUP);
 	}
@@ -383,7 +382,7 @@ METHOD int get_downreason {
 	struct ifdownreason *_ifdr;
 } DEFAULT null_get_downreason;
 
-METHOD int vfstat_ioctl {
+METHOD int vf_status {
 	if_ctx_t _ctx;
-	struct ifvfstatus *_ifvfs;
-} DEFAULT null_vfstat_ioctl;
+	nvlist_t *_status;
+} DEFAULT null_vf_status;
diff --git a/sys/net/iflib.c b/sys/net/iflib.c
index 314e8e527628..54001be3eae5 100644
--- a/sys/net/iflib.c
+++ b/sys/net/iflib.c
@@ -4658,17 +4658,10 @@ iflib_if_ioctl(if_t ifp, u_long command, caddr_t data)
 		CTX_UNLOCK(ctx);
 		break;
 	case SIOCGIFVFSTATUS:
-	{
-		struct ifvfstatus *ifvfs;
-
-		ifvfs = (struct ifvfstatus *)data;
-		if (ifvfs->ifvfs_count < 0)
-			return (EINVAL);
 		CTX_LOCK(ctx);
-		err = IFDI_VFSTAT_IOCTL(ctx, ifvfs);
+		err = IFDI_VF_STATUS(ctx, (nvlist_t *)data);
 		CTX_UNLOCK(ctx);
 		break;
-	}
 	default:
 		err = ether_ioctl(ifp, command, data);
 		break;
diff --git a/sys/sys/sockio.h b/sys/sys/sockio.h
index a3ce196c7959..b3c499f1ccca 100644
--- a/sys/sys/sockio.h
+++ b/sys/sys/sockio.h
@@ -152,6 +152,6 @@
 #define	SIOCSUMBPARAM	 _IOW('i', 158, struct ifreq)	/* set MBIM param */
 #define	SIOCGUMBPARAM	_IOWR('i', 159, struct ifreq)	/* get MBIM param */
 
-#define	SIOCGIFVFSTATUS	_IOWR('i', 163, struct ifvfstatus) /* get VF status */
+#define	SIOCGIFVFSTATUS	_IOWR('i', 163, struct ifreq) /* get VF status */
 
 #endif /* !_SYS_SOCKIO_H_ */
diff --git a/usr.sbin/iovctl/iovctl.8 b/usr.sbin/iovctl/iovctl.8
index 2574503e5ae7..84c5d05cc795 100644
--- a/usr.sbin/iovctl/iovctl.8
+++ b/usr.sbin/iovctl/iovctl.8
@@ -23,7 +23,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 31, 2020
+.Dd August 8, 2026
 .Dt IOVCTL 8
 .Os
 .Sh NAME
@@ -113,9 +113,17 @@ to stdout.
 This action may be used to discover the configuration parameters supported on
 a given PF device.
 .El
+.Pp
+After creating VFs, use
+.Xr ifconfig 8
+with the
+.Fl v
+option on the PF interface to display NIC-specific VF initialization,
+resource, and policy state.
 .Sh SEE ALSO
 .Xr iovctl.conf 5 ,
-.Xr rc.conf 5
+.Xr rc.conf 5 ,
+.Xr ifconfig 8
 .Sh AUTHORS
 This manual page was written by
 .An Ryan Stone Aq Mt rstone@FreeBSD.org .
diff --git a/usr.sbin/iovctl/iovctl.conf.5 b/usr.sbin/iovctl/iovctl.conf.5
index 1bae96e150ba..741f343e7e5f 100644
--- a/usr.sbin/iovctl/iovctl.conf.5
+++ b/usr.sbin/iovctl/iovctl.conf.5
@@ -23,7 +23,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 30, 2024
+.Dd August 8, 2026
 .Dt IOVCTL.CONF 5
 .Os
 .Sh NAME
@@ -140,6 +140,15 @@ The default value of this parameter is false.
 .Pp
 See the PF driver manual page for configuration parameters specific to
 particular hardware.
+.Pp
+For network devices, display VF initialization, resources, and policy state
+with:
+.Bd -literal -offset indent
+ifconfig -v ix0
+.Ed
+See
+.Xr ifconfig 8
+for the meaning of the reported fields.
 .Sh EXAMPLES
 This sample file will create 3 VFs as children of the ix0 device.
 VF-1 and VF-2 are set as
@@ -166,6 +175,7 @@ VF-0 {
 .Ed
 .Sh SEE ALSO
 .Xr rc.conf 5 ,
+.Xr ifconfig 8 ,
 .Xr iovctl 8
 .Sh AUTHORS
*** 1 LINES SKIPPED ***


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a782290.180ab.29135cbb>