git: 41418a7cd00c - main - ifconfig: Add SR-IOV VF status output

From: Kevin Bowling <kbowling_at_FreeBSD.org>
Date: Sun, 06 Sep 2026 13:52:03 UTC
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=41418a7cd00c4a20eb72bc8c315c0e364d4e4ad2

commit 41418a7cd00c4a20eb72bc8c315c0e364d4e4ad2
Author:     Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-10 22:30:22 +0000
Commit:     Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-09-06 13:51:53 +0000

    ifconfig: Add SR-IOV VF status output
    
    Add NIC-specific VF status to the existing ifconfig -v output.  Fetch
    the data through libifconfig using a separate native route Netlink
    query.
    
    Group optional identity, initialization, resources, VLAN policy,
    administrator policy, protocol, traffic-permission, and
    fault containment fields.  Omitted fields remain distinct from false or
    zero.
    
    Refer users to iovctl -L for device-neutral PCI attachment and
    passthrough state.
    
    This is a Netlink-native evolution of the original interface by Eric
    Joyner.
    
    Relnotes:       yes
    Sponsored by:   Intel Corporation (initial version)
    Sponsored by:   BBOX.io
    Differential Revision:  https://reviews.freebsd.org/D58778
    
    Co-authored-by: Eric Joyner <erj@FreeBSD.org>
---
 sbin/ifconfig/Makefile           |   1 +
 sbin/ifconfig/ifconfig.8         |  70 ++++++++-
 sbin/ifconfig/ifconfig.h         |   3 +
 sbin/ifconfig/ifconfig_netlink.c |   8 +-
 sbin/ifconfig/ifvfstatus.c       | 300 +++++++++++++++++++++++++++++++++++++++
 usr.sbin/iovctl/iovctl.8         |  10 +-
 usr.sbin/iovctl/iovctl.conf.5    |   8 ++
 7 files changed, 395 insertions(+), 5 deletions(-)

diff --git a/sbin/ifconfig/Makefile b/sbin/ifconfig/Makefile
index 26391023d54a..fa3cc26a3023 100644
--- a/sbin/ifconfig/Makefile
+++ b/sbin/ifconfig/Makefile
@@ -66,6 +66,7 @@ LIBADD+=	nv
 
 .if ${MK_NETLINK_SUPPORT} != "no"
 SRCS+=	ifconfig_netlink.c
+SRCS+=	ifvfstatus.c		# VF status information
 SRCS+=	ifgeneve.c		# GENEVE support
 .else
 CFLAGS+=-DWITHOUT_NETLINK
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index 7a85ba314f43..c1d560fb4d12 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 August 10, 2026
+.Dd September 6, 2026
 .Dt IFCONFIG 8
 .Os
 .Sh NAME
@@ -325,6 +325,74 @@ 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 "driver.*"
+.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 the PF permits traffic,
+whether fault containment blocked the VF, and whether it is quarantined.
+Traffic permission does not imply that the VF has enabled queues, has link,
+or is actively passing packets.
+.It Cm resources
+The numbers of transmit and receive queues allocated to the VF.
+These are not necessarily the numbers of queues currently used by its driver.
+When available, configured minimum and maximum aggregate transmit rates are
+also shown.
+A zero minimum is displayed as
+.Cm min-tx-rate=none
+(no guaranteed allocation), while a zero maximum is displayed as
+.Cm max-tx-rate=unlimited .
+.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.
+Access mode may also report the priority code point and VLAN protocol.
+The VLAN identifier can be zero when the PF imposes priority tagging only.
+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.
+.It Cm driver.*
+Driver-specific extension namespaces.
+The namespace version and scalar boolean, numeric, string, and binary fields
+are displayed using their stable schema names.
+See the PF driver's manual page for their meanings.
+.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=allowed
+		resources: tx-queues=4 rx-queues=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 and
+.Xr iovctl 8
+for PCI attachment and passthrough status.
 .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/ifconfig.h b/sbin/ifconfig/ifconfig.h
index 672020443b8c..45e5b108accb 100644
--- a/sbin/ifconfig/ifconfig.h
+++ b/sbin/ifconfig/ifconfig.h
@@ -281,6 +281,9 @@ void	clone_setdefcallback_prefix(const char *, clone_callback_func *);
 void	clone_setdefcallback_filter(clone_match_func *, clone_callback_func *);
 
 void	sfp_status(if_ctx *ctx);
+#ifndef WITHOUT_NETLINK
+void	vf_status(if_ctx *);
+#endif
 
 struct sockaddr_dl;
 bool	match_ether(const struct sockaddr_dl *sdl);
diff --git a/sbin/ifconfig/ifconfig_netlink.c b/sbin/ifconfig/ifconfig_netlink.c
index 38e3edb3e597..2b24cac7ab1c 100644
--- a/sbin/ifconfig/ifconfig_netlink.c
+++ b/sbin/ifconfig/ifconfig_netlink.c
@@ -150,8 +150,8 @@ prepare_ifmap(struct snl_state *ss, const char *ifname)
 	struct nlmsghdr *hdr = snl_create_msg_request(&nw, RTM_GETLINK);
 	hdr->nlmsg_flags |= NLM_F_DUMP;
 	snl_reserve_msg_object(&nw, struct ifinfomsg);
-       if (ifname != NULL)
-               snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname);
+	if (ifname != NULL)
+		snl_add_msg_attr_string(&nw, IFLA_IFNAME, ifname);
 
 	if (! (hdr = snl_finalize_msg(&nw)) || !snl_send_message(ss, hdr))
 		return (NULL);
@@ -450,6 +450,8 @@ status_nl(if_ctx *ctx, struct iface *iface)
 		args->afp->af_other_status(ctx);
 
 	print_ifstatus(ctx);
+	if (args->verbose > 0)
+		vf_status(ctx);
 	if (args->drivername || args->verbose) {
 		if (ifconfig_get_orig_name(lifh, link->ifla_ifname,
 		    &drivername) != 0) {
@@ -493,7 +495,7 @@ list_interfaces_nl(struct ifconfig_args *args)
 
 	nl_init_socket(&ss);
 
-       struct ifmap *ifmap = prepare_ifmap(&ss, args->ifname);
+	struct ifmap *ifmap = prepare_ifmap(&ss, args->ifname);
 	struct iface **sorted_ifaces = snl_allocz(&ss, ifmap->count * sizeof(void *));
 	for (uint32_t i = 0, num = 0; i < ifmap->size; i++) {
 		if (ifmap->ifaces[i] != NULL) {
diff --git a/sbin/ifconfig/ifvfstatus.c b/sbin/ifconfig/ifvfstatus.c
new file mode 100644
index 000000000000..82cf0c5400d7
--- /dev/null
+++ b/sbin/ifconfig/ifvfstatus.c
@@ -0,0 +1,300 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Intel Corporation
+ * Copyright (c) 2026 Kevin Bowling <kbowling@FreeBSD.org>
+ */
+
+#include <sys/types.h>
+
+#include <net/ethernet.h>
+#include <net/if.h>
+
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.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');
+}
+
+static void
+vf_print_rate(const char *name, uint64_t rate, const char *zero)
+{
+
+	if (rate == 0)
+		printf(" %s=%s", name, zero);
+	else if (rate % IF_Mbps(1) == 0)
+		printf(" %s=%" PRIu64 "Mbps", name, rate / IF_Mbps(1));
+	else
+		printf(" %s=%" PRIu64 "bps", name, rate);
+}
+
+static const char *
+vf_link_state_name(enum ifconfig_vf_link_state state)
+{
+
+	switch (state) {
+	case IFCONFIG_VF_LINK_DOWN:
+		return ("down");
+	case IFCONFIG_VF_LINK_UP:
+		return ("up");
+	case IFCONFIG_VF_LINK_AUTO:
+		return ("auto");
+	default:
+		return ("unknown");
+	}
+}
+
+static const char *
+vf_vlan_mode_name(enum ifconfig_vf_vlan_mode mode)
+{
+
+	switch (mode) {
+	case IFCONFIG_VF_VLAN_ACCESS:
+		return ("access");
+	case IFCONFIG_VF_VLAN_TRUNK:
+		return ("trunk");
+	default:
+		return ("unknown");
+	}
+}
+
+static void
+vf_print_vlan_proto(uint16_t proto)
+{
+
+	switch (proto) {
+	case ETHERTYPE_VLAN:
+		printf("802.1q");
+		break;
+	case ETHERTYPE_QINQ:
+		printf("802.1ad");
+		break;
+	default:
+		printf("0x%04x", proto);
+		break;
+	}
+}
+
+static void
+vf_driver_field(const struct ifconfig_vf_extension_field *field)
+{
+	const uint8_t *data;
+	size_t i, length;
+
+	printf(" %s=", field->name);
+	switch (field->type) {
+	case IFCONFIG_VF_EXT_BOOL:
+		printf("%s", field->value.boolean ? "yes" : "no");
+		break;
+	case IFCONFIG_VF_EXT_NUMBER:
+		printf("%" PRIu64, field->value.number);
+		break;
+	case IFCONFIG_VF_EXT_STRING:
+		printf("%s", field->value.string);
+		break;
+	case IFCONFIG_VF_EXT_BINARY:
+		data = field->value.binary.data;
+		length = field->value.binary.length;
+		printf("0x");
+		for (i = 0; i < length; i++)
+			printf("%02x", data[i]);
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+vf_driver_status(const struct ifconfig_vf_info *vf)
+{
+	const struct ifconfig_vf_extension *driver;
+	const struct ifconfig_vf_extension_field *field;
+	size_t i, j;
+
+	for (i = 0; i < vf->num_extensions; i++) {
+		driver = &vf->extensions[i];
+		printf("\t\t\t%s: version=%u", driver->name,
+		    driver->version);
+		for (j = 0; j < driver->num_fields; j++) {
+			field = &driver->fields[j];
+			vf_driver_field(field);
+		}
+		putchar('\n');
+	}
+}
+
+void
+vf_status(if_ctx *ctx)
+{
+	struct ifconfig_vf_status *status;
+	const struct ifconfig_vf_info *vf;
+	const struct ether_addr *mac;
+	const char *mode;
+	uint64_t speed;
+	bool printed;
+	size_t i;
+	int error;
+
+	if (ifconfig_get_vf_status(lifh, ctx->ifname, &status) != 0) {
+		error = ifconfig_err_errno(lifh);
+		if (error != EOPNOTSUPP)
+			warnc(error, "%s: VF status", ctx->ifname);
+		return;
+	}
+
+	if (status->pf_link_state_present) {
+		printf("\tVF-visible PF link: state=%s",
+		    vf_link_state_name(status->pf_link_state));
+	}
+	speed = status->pf_link_speed;
+	if (status->pf_link_speed_present) {
+		if (speed % IF_Mbps(1) == 0)
+			printf(" speed=%" PRIu64 "Mbps", speed / IF_Mbps(1));
+		else
+			printf(" speed=%" PRIu64 "bps", speed);
+	}
+	if (status->pf_link_state_present || status->pf_link_speed_present)
+		putchar('\n');
+
+	printf("\tvirtual functions: %zu\n", status->num_vfs);
+	for (i = 0; i < status->num_vfs; i++) {
+		vf = status->vfs[i];
+		printf("\t\tvf %3u:\n", vf->index);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_MAC)) != 0) {
+			mac = (const struct ether_addr *)(const void *)vf->mac;
+			vf_group_begin(&printed, "identity");
+			printf(" mac=%s", ether_ntoa(mac));
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_CONFIGURED)) != 0) {
+			vf_group_begin(&printed, "state");
+			printf(" configured=%s", vf->configured ? "yes" : "no");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_INITIALIZED)) != 0) {
+			vf_group_begin(&printed, "state");
+			printf(" initialized=%s", vf->initialized ? "yes" : "no");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_TRAFFIC_ALLOWED)) != 0) {
+			vf_group_begin(&printed, "state");
+			printf(" traffic=%s", vf->traffic_allowed ?
+			    "allowed" : "denied");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_FAULT_BLOCKED)) != 0) {
+			vf_group_begin(&printed, "state");
+			printf(" fault-blocked=%s", vf->fault_blocked ?
+			    "yes" : "no");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_QUARANTINED)) != 0) {
+			vf_group_begin(&printed, "state");
+			printf(" quarantined=%s", vf->quarantined ? "yes" : "no");
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_NUM_TX_QUEUES)) != 0) {
+			vf_group_begin(&printed, "resources");
+			printf(" tx-queues=%u", vf->tx_queue_count);
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_NUM_RX_QUEUES)) != 0) {
+			vf_group_begin(&printed, "resources");
+			printf(" rx-queues=%u", vf->rx_queue_count);
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_MIN_TX_RATE)) != 0) {
+			vf_group_begin(&printed, "resources");
+			vf_print_rate("min-tx-rate", vf->min_tx_rate_bps, "none");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_MAX_TX_RATE)) != 0) {
+			vf_group_begin(&printed, "resources");
+			vf_print_rate("max-tx-rate", vf->max_tx_rate_bps,
+			    "unlimited");
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_VLAN_MODE)) != 0) {
+			mode = vf_vlan_mode_name(vf->vlan_mode);
+			vf_group_begin(&printed, "vlan");
+			printf(" mode=%s", mode);
+			if (vf->vlan_mode == IFCONFIG_VF_VLAN_ACCESS &&
+			    (vf->fields & (1ULL << IFLAF_VF_VLAN)) != 0)
+				printf(" vid=%u", vf->vlan);
+			if (vf->vlan_mode == IFCONFIG_VF_VLAN_ACCESS &&
+			    (vf->fields & (1ULL << IFLAF_VF_VLAN_PCP)) != 0)
+				printf(" pcp=%u", vf->vlan_pcp);
+			if (vf->vlan_mode == IFCONFIG_VF_VLAN_ACCESS &&
+			    (vf->fields & (1ULL << IFLAF_VF_VLAN_PROTO)) != 0) {
+				printf(" proto=");
+				vf_print_vlan_proto(vf->vlan_proto);
+			}
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_VLAN_COUNT)) != 0) {
+			vf_group_begin(&printed, "vlan");
+			printf(" filters=%u", vf->vlan_count);
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_VLAN_LIMIT)) != 0) {
+			vf_group_begin(&printed, "vlan");
+			printf(" limit=%u", vf->vlan_limit);
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_ALLOW_SET_MAC)) != 0) {
+			vf_group_begin(&printed, "policy");
+			printf(" set-mac=%s", vf->allow_set_mac ?
+			    "allowed" : "denied");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_ALLOW_SET_VLAN)) != 0) {
+			vf_group_begin(&printed, "policy");
+			printf(" set-vlan=%s", vf->allow_set_vlan ?
+			    "allowed" : "denied");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_MAC_ANTI_SPOOF)) != 0) {
+			vf_group_begin(&printed, "policy");
+			printf(" anti-spoof=%s", vf->mac_anti_spoof ? "on" : "off");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_ALLOW_PROMISC)) != 0) {
+			vf_group_begin(&printed, "policy");
+			printf(" promisc=%s", vf->allow_promisc ?
+			    "allowed" : "denied");
+		}
+		if ((vf->fields & (1ULL << IFLAF_VF_LINK_STATE_POLICY)) != 0) {
+			vf_group_begin(&printed, "policy");
+			printf(" link-state=%s",
+			    vf_link_state_name(vf->link_state_policy));
+		}
+		vf_group_end(printed);
+
+		printed = false;
+		if ((vf->fields & (1ULL << IFLAF_VF_API_VERSION)) != 0) {
+			vf_group_begin(&printed, "protocol");
+			printf(" api=%s", vf->api_version);
+		}
+		vf_group_end(printed);
+
+		vf_driver_status(vf);
+	}
+	ifconfig_free_vf_status(status);
+}
diff --git a/usr.sbin/iovctl/iovctl.8 b/usr.sbin/iovctl/iovctl.8
index 67c2ed22199c..3e1fc8c18639 100644
--- a/usr.sbin/iovctl/iovctl.8
+++ b/usr.sbin/iovctl/iovctl.8
@@ -131,10 +131,18 @@ to stdout.
 This action may be used to discover the configuration parameters supported on
 a given PF device.
 .El
+.Pp
+For network devices, 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 vmm 4 ,
 .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 864637626d92..c9d5404970c7 100644
--- a/usr.sbin/iovctl/iovctl.conf.5
+++ b/usr.sbin/iovctl/iovctl.conf.5
@@ -145,7 +145,14 @@ After creating VFs, display their PCI attachment and passthrough state with:
 .Bd -literal -offset indent
 iovctl -L -d ix0
 .Ed
+For network devices, display NIC-specific initialization, resources, and
+policy state with:
+.Bd -literal -offset indent
+ifconfig -v ix0
+.Ed
 See
+.Xr ifconfig 8
+and
 .Xr iovctl 8
 for the meaning of the reported fields.
 .Sh EXAMPLES
@@ -174,6 +181,7 @@ VF-0 {
 .Ed
 .Sh SEE ALSO
 .Xr rc.conf 5 ,
+.Xr ifconfig 8 ,
 .Xr iovctl 8
 .Sh AUTHORS
 This manual page was written by