udev: predictable names for auxiliary sub-function (SF) devices (#42154)

Some drivers (currently mlx5_core) expose sub-functions (SFs) of a PCI
Physical or Virtual Function as auxiliary devices that carry a stable
`sfnum` sysfs attribute (the user-defined SF number passed to `devlink
port add ... sfnum N`). Their leaf devices (net, infiniband, ...)
currently inherit the parent PF/VF's `ID_PATH` and the SF netdev falls
through to the kernel-assigned `eth<N>` name.

Two patches:

1. `udev-builtin-path_id`: prepend an `sf-<N>` token when the walk
crosses an aux device with `sfnum`, so SF leaf devices get an `ID_PATH`
distinct from the parent PF/VF (e.g. `pci-0000:c1:00.0-sf-88`). Aux
devices without `sfnum` keep the pre-patch behaviour, so existing
`ID_PATH` values are unchanged and the patch needs no naming-scheme gate
(path_id is unversioned).

2. `udev-builtin-net_id`: name SF host netdevs analogously to SR-IOV VF
host netdevs — walk to the parent PCI function and append a
single-character `S<sfnum>` suffix. SFs hosted on SR-IOV VFs (VF-SF) get
both suffixes chained on the PF's base name (`enp193s0f0v0S88`). Gated
behind `NAMING_SUBFUNC` / `NAMING_V261`. Man page
(`systemd.net-naming-scheme(7)`) updated.

The patches apply to any driver that exposes its SF leaf devices below
an aux device exposing `sfnum`.

Validated on mlx5 hardware: PF, VF, SF-on-PF (`enp8s0f0S88`,
`pci-0000:08:00.0-sf-88`). VF-SF case verified with a fake-sysfs setup
since this is not supported by any real device and is more a theoretical
use case.
This commit is contained in:
Yu Watanabe
2026-05-20 03:33:15 +09:00
committed by GitHub
6 changed files with 145 additions and 9 deletions
+5
View File
@@ -299,6 +299,11 @@ CHANGES WITH 261 in spe:
GPT header (i.e. is partitioned) but the block device has partition
table processing turned off.
* Persistent network interface naming has been extended to auxiliary
sub-function (SF) network devices (such as mlx5_core SFs), using an
"S<sfnum>" suffix appended to the parent PCI function's name (e.g.
"enp193s0f0S88").
Changes in systemd-boot, systemd-stub, bootctl, ukify:
* systemd-stub will now maintain a "boot secret" and pass it to the OS
+42
View File
@@ -170,6 +170,7 @@
<term><varname>ID_NET_NAME_SLOT=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>u</constant><replaceable>port</replaceable>…[<constant>c</constant><replaceable>config</replaceable>][<constant>i</constant><replaceable>interface</replaceable>]</term>
<term><varname>ID_NET_NAME_SLOT=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>v</constant><replaceable>slot</replaceable></term>
<term><varname>ID_NET_NAME_SLOT=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>r</constant><replaceable>slot</replaceable></term>
<term><varname>ID_NET_NAME_SLOT=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>S</constant><replaceable>sfnum</replaceable></term>
<listitem><para>This property describes the slot position. Different schemes are used depending on
the bus type, as described in the table below. In case of USB, BCMA, and SR-VIO devices, the full
@@ -222,6 +223,11 @@
<entry>… <constant>r</constant><replaceable>slot</replaceable></entry>
<entry>SR-IOV slot number</entry>
</row>
<row>
<entry>… <constant>S</constant><replaceable>sfnum</replaceable></entry>
<entry>Auxiliary sub-function (SF) number</entry>
</row>
</tbody>
</tgroup>
</table>
@@ -247,6 +253,20 @@
is linked to the particular representor, with any leading zeros removed. The physical port
name and the bus number are ignored.</para>
<para>Auxiliary sub-function (SF) network devices, where the network device's parent is an
auxiliary device exposing a <constant>sfnum</constant> sysfs attribute (currently mlx5_core SFs),
are named based on the underlying PCI function (the PF, or for VF-SF the PF behind the VF),
with a suffix of <constant>S</constant> and the user-defined sub-function number from
<constant>sfnum</constant>. This is analogous to how SR-IOV virtual function devices are named
with a <constant>v</constant> suffix.</para>
<para>If the SF's parent PCI function is itself an SR-IOV virtual function (VF-SF), the
name is rooted at the PF and both suffixes are chained, with the <constant>v</constant>
suffix preceding the <constant>S</constant> suffix
(e.g. <literal>enp193s0f0v0S88</literal>). The PF, the VF, and the SF therefore form a
stable, hierarchical sequence regardless of the VF's underlying PCI bus/device/function
numbering.</para>
<para>In some configurations a parent PCI bridge of a given network controller may be associated
with a slot. In such case we do not generate this device property to avoid possible naming conflicts.</para>
@@ -262,6 +282,7 @@
<term><varname>ID_NET_NAME_PATH=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]</term>
<term><varname>ID_NET_NAME_PATH=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>b</constant><replaceable>number</replaceable></term>
<term><varname>ID_NET_NAME_PATH=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>u</constant><replaceable>port</replaceable>…[<constant>c</constant><replaceable>config</replaceable>][<constant>i</constant><replaceable>interface</replaceable>]</term>
<term><varname>ID_NET_NAME_PATH=</varname><replaceable>prefix</replaceable>[<constant>P</constant><replaceable>domain</replaceable>]<constant>p</constant><replaceable>bus</replaceable><constant>s</constant><replaceable>slot</replaceable>[<constant>f</constant><replaceable>function</replaceable>][<constant>n</constant><replaceable>phys_port_name</replaceable>|<constant>d</constant><replaceable>dev_port</replaceable>]<constant>S</constant><replaceable>sfnum</replaceable></term>
<listitem><para>This property describes the device installation location. Different schemes are
used depending on the bus type, as described in the table below. For BCMA and USB devices, PCI path
@@ -310,6 +331,11 @@
<entry>USB port number chain</entry>
</row>
<row>
<entry>… <constant>S</constant><replaceable>sfnum</replaceable></entry>
<entry>Auxiliary sub-function (SF) number</entry>
</row>
</tbody>
</tgroup>
</table>
@@ -575,6 +601,22 @@
<xi:include href="version-info.xml" xpointer="v260"/>
</listitem>
</varlistentry>
<varlistentry>
<term><constant>v261</constant></term>
<listitem><para>Stable names are now generated for auxiliary sub-function (SF) network devices
(such as <literal>mlx5_core</literal> SFs). The name is built from the parent PCI Physical Function's
path with an <literal>S<replaceable>sfnum</replaceable></literal> suffix, where
<replaceable>sfnum</replaceable> is the user-defined SF number (the value passed to
<command>devlink port add … sfnum <replaceable>N</replaceable></command>, exposed by the kernel as
the <constant>sfnum</constant> sysfs attribute on the auxiliary device). This is analogous to the
<literal>v<replaceable>N</replaceable></literal> suffix used for SR-IOV virtual functions; for SFs
hosted on SR-IOV VFs (VF-SF), the two suffixes are chained on top of the PF's base name.</para>
<xi:include href="version-info.xml" xpointer="v261"/>
</listitem>
</varlistentry>
</variablelist>
<para>Note that <constant>latest</constant> may be used to denote the latest scheme known (to this
+1
View File
@@ -31,6 +31,7 @@ static const NamingScheme naming_schemes[] = {
{ "v258", NAMING_V258 },
{ "v259", NAMING_V259 },
{ "v260", NAMING_V260 },
{ "v261", NAMING_V261 },
/* … add more schemes here, as the logic to name devices is updated … */
EXTRA_NET_NAMING_MAP
+4
View File
@@ -44,6 +44,9 @@ typedef enum NamingSchemeFlags {
NAMING_USE_INTERFACE_PROPERTY = 1 << 20, /* Use INTERFACE udev property, rather than sysname, when no renaming is requested. */
NAMING_DEVICETREE_ALIASES_WLAN = 1 << 21, /* Generate names from devicetree aliases for WLAN devices */
NAMING_MCTP = 1 << 22, /* Use "mc" prefix for MCTP devices */
NAMING_SUBFUNC = 1 << 23, /* Generate names for auxiliary sub-function (SF) network
* devices (e.g. mlx5_core SFs), based on the parent PF's
* PCI path and the user-defined sfnum, with an "S" suffix. */
/* And now the masks that combine the features above */
NAMING_V238 = 0,
@@ -67,6 +70,7 @@ typedef enum NamingSchemeFlags {
NAMING_V258 = NAMING_V257 | NAMING_USE_INTERFACE_PROPERTY,
NAMING_V259 = NAMING_V258 | NAMING_DEVICETREE_ALIASES_WLAN,
NAMING_V260 = NAMING_V259 | NAMING_MCTP,
NAMING_V261 = NAMING_V260 | NAMING_SUBFUNC,
EXTRA_NET_NAMING_SCHEMES
+81 -9
View File
@@ -165,6 +165,46 @@ static int get_virtfn_info(sd_device *pcidev, sd_device **ret_physfn_pcidev, cha
return -ENOENT;
}
static int get_subfunc_info(sd_device *aux_dev, sd_device **ret_parent_pcidev, char **ret_suffix) {
sd_device *parent;
unsigned sfnum;
char *suffix;
int r;
assert(aux_dev);
assert(ret_parent_pcidev);
assert(ret_suffix);
/* The auxiliary device must expose an 'sfnum' attribute. This is currently used by the
* mlx5_core driver for sub-function (SF) auxiliary devices. The sfnum is the user-defined
* stable identifier passed to "devlink port add ... sfnum N". */
r = device_get_sysattr_unsigned_filtered(aux_dev, "sfnum", &sfnum);
if (r < 0)
return r;
/* Walk one hop up: the auxiliary device's parent must be a PCI function. It can be either
* the PF directly, or an SR-IOV VF — mlx5 also supports SFs hosted on VFs (VF-SF), see
* Documentation/networking/representors.rst in the kernel tree. The VF case is handled by
* the existing virtfn logic in names_pci(), so here we just return the immediate PCI
* parent and a single "S<sfnum>" suffix piece. */
r = sd_device_get_parent(aux_dev, &parent);
if (r < 0)
return r;
r = device_in_subsystem(parent, "pci");
if (r < 0)
return r;
if (r == 0)
return -ENODEV;
if (asprintf(&suffix, "S%u", sfnum) < 0)
return log_oom_debug();
*ret_parent_pcidev = sd_device_ref(parent);
*ret_suffix = suffix;
return 0;
}
static int get_port_specifier(sd_device *dev, char **ret) {
const char *phys_port_name;
unsigned dev_port;
@@ -928,24 +968,56 @@ static int names_devicetree(UdevEvent *event, const char *prefix) {
static int names_pci(UdevEvent *event, const char *prefix) {
sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
_cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL;
_cleanup_free_ char *virtfn_suffix = NULL;
_cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL, *parent_pcidev = NULL;
_cleanup_free_ char *virtfn_suffix = NULL, *subfunc_suffix = NULL, *combined_suffix = NULL;
const char *suffix = NULL;
assert(prefix);
/* check if our direct parent is a PCI device with no other bus in-between */
if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0)
/* If the network device's direct parent is an auxiliary device that exposes a stable
* 'sfnum' attribute (currently mlx5_core sub-functions), peel off the SF identity into
* a 'S<sfnum>' suffix piece and pick up the aux device's underlying PCI function as
* 'parent'. The aux device is just a bump on the path; everything below — PF/VF
* resolution, slot/onboard lookup — proceeds the same way as for any PCI-rooted
* network device. */
if (naming_scheme_has(NAMING_SUBFUNC)) {
sd_device *aux;
if (get_matching_parent(dev, STRV_MAKE("auxiliary"),
/* skip_virtio= */ false, &aux) >= 0)
(void) get_subfunc_info(aux, &parent_pcidev, &subfunc_suffix);
}
/* SF: parent is the aux device's PCI function. Otherwise the network device's direct
* parent must itself be a PCI device. */
if (subfunc_suffix)
parent = parent_pcidev;
else if (get_matching_parent(dev, STRV_MAKE("pci"), /* skip_virtio= */ true, &parent) < 0)
return 0;
/* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */
/* If parent is an SR-IOV VF, walk to the parent PF and add a 'v<N>' suffix piece. The
* onboard BIOS label is intentionally not exposed for any "child function" (VF, SF, or
* VF-SF), since the label refers to the parent's physical port, not to a logical child. */
bool is_child_function = !!subfunc_suffix;
if (naming_scheme_has(NAMING_SR_IOV_V) &&
get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0)
get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) {
parent = physfn_pcidev;
else
is_child_function = true;
}
if (!is_child_function)
(void) names_pci_onboard_label(event, parent, prefix);
(void) names_pci_onboard(event, parent, prefix, virtfn_suffix);
(void) names_pci_slot(event, parent, prefix, virtfn_suffix);
/* Compose the final suffix in PF -> [VF ->] SF order, e.g. "v0", "S88", or "v0S88". */
if (virtfn_suffix && subfunc_suffix) {
combined_suffix = strjoin(virtfn_suffix, subfunc_suffix);
if (!combined_suffix)
return log_oom_debug();
suffix = combined_suffix;
} else
suffix = virtfn_suffix ?: subfunc_suffix;
(void) names_pci_onboard(event, parent, prefix, suffix);
(void) names_pci_slot(event, parent, prefix, suffix);
return 0;
}
+12
View File
@@ -711,6 +711,18 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) {
path_prepend(&path, "serio-%s", sysnum);
parent = skip_subsystem(parent, "serio");
}
} else if (device_in_subsystem(parent, "auxiliary") > 0) {
unsigned sfnum;
/* sfnum is the user-defined sub-function number (devlink port add ...
* sfnum N). Prepend it so an SF leaf device gets an ID_PATH distinct
* from its parent PF/VF; aux devices without 'sfnum' emit no token to
* preserve pre-patch ID_PATH values. */
if (device_get_sysattr_unsigned(parent, "sfnum", &sfnum) >= 0) {
path_prepend(&path, "sf-%u", sfnum);
if (compat_path)
path_prepend(&compat_path, "sf-%u", sfnum);
}
} else if (device_in_subsystem(parent, "pci") > 0) {
path_prepend(&path, "pci-%s", sysname);
if (compat_path)