+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2012 Kay Sievers <kay@vrfy.org>
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
/*
*
* Type of names:
* b<number> — BCMA bus core number
- * c<bus_id> — CCW bus group name, without leading zeros [s390]
+ * c<bus_id> — bus id of a grouped CCW or CCW device,
+ * with all leading zeros stripped [s390]
* o<index>[n<phys_port_name>|d<dev_port>]
* — on-board device index number
* s<slot>[f<function>][n<phys_port_name>|d<dev_port>]
* — PCI geographical location
* [P<domain>]p<bus>s<slot>[f<function>][u<port>][..][c<config>][i<interface>]
* — USB port number chain
+ * v<slot> - VIO slot number (IBM PowerVM)
+ * a<vendor><model>i<instance> — Platform bus ACPI instance id
*
* All multi-function PCI devices will carry the [f<function>] number in the
* device name, including the function 0 device.
* /sys/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.0/net/enp0s29u1u2
* ID_NET_NAME_MAC=enxd626b3450fb5
* ID_NET_NAME_PATH=enp0s29u1u2
+ *
+ * s390 grouped CCW interface:
+ * /sys/devices/css0/0.0.0007/0.0.f5f0/group_device/net/encf5f0
+ * ID_NET_NAME_MAC=enx026d3c00000a
+ * ID_NET_NAME_PATH=encf5f0
*/
#include <errno.h>
#include "dirent-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "fs-util.h"
+#include "parse-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "udev.h"
+#include "udev-util.h"
#define ONBOARD_INDEX_MAX (16*1024-1)
NET_USB,
NET_BCMA,
NET_VIRTIO,
- NET_CCWGROUP,
+ NET_CCW,
+ NET_VIO,
+ NET_PLATFORM,
};
struct netnames {
char usb_ports[IFNAMSIZ];
char bcma_core[IFNAMSIZ];
- char ccw_group[IFNAMSIZ];
+ char ccw_busid[IFNAMSIZ];
+ char vio_slot[IFNAMSIZ];
+ char platform_path[IFNAMSIZ];
};
+struct virtfn_info {
+ struct udev_device *physfn_pcidev;
+ char suffix[IFNAMSIZ];
+};
+
+/* skip intermediate virtio devices */
+static struct udev_device *skip_virtio(struct udev_device *dev) {
+ struct udev_device *parent = dev;
+
+ /* there can only ever be one virtio bus per parent device, so we can
+ safely ignore any virtio buses. see
+ <http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html> */
+ while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent)))
+ parent = udev_device_get_parent(parent);
+ return parent;
+}
+
+static int get_virtfn_info(struct udev_device *dev, struct netnames *names, struct virtfn_info *vf_info) {
+ struct udev *udev;
+ const char *physfn_link_file;
+ _cleanup_free_ char *physfn_pci_syspath = NULL;
+ _cleanup_free_ char *virtfn_pci_syspath = NULL;
+ struct dirent *dent;
+ _cleanup_closedir_ DIR *dir = NULL;
+ struct virtfn_info vf_info_local = {};
+ int r;
+
+ udev = udev_device_get_udev(names->pcidev);
+ if (!udev)
+ return -ENOENT;
+ /* Check if this is a virtual function. */
+ physfn_link_file = strjoina(udev_device_get_syspath(names->pcidev), "/physfn");
+ r = chase_symlinks(physfn_link_file, NULL, 0, &physfn_pci_syspath);
+ if (r < 0)
+ return r;
+
+ /* Get physical function's pci device. */
+ vf_info_local.physfn_pcidev = udev_device_new_from_syspath(udev, physfn_pci_syspath);
+ if (!vf_info_local.physfn_pcidev)
+ return -ENOENT;
+
+ /* Find the virtual function number by finding the right virtfn link. */
+ dir = opendir(physfn_pci_syspath);
+ if (!dir) {
+ r = -errno;
+ goto out_unref;
+ }
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ _cleanup_free_ char *virtfn_link_file = NULL;
+ if (!startswith(dent->d_name, "virtfn"))
+ continue;
+ virtfn_link_file = strjoin(physfn_pci_syspath, "/", dent->d_name);
+ if (!virtfn_link_file) {
+ r = -ENOMEM;
+ goto out_unref;
+ }
+ if (chase_symlinks(virtfn_link_file, NULL, 0, &virtfn_pci_syspath) < 0)
+ continue;
+ if (streq(udev_device_get_syspath(names->pcidev), virtfn_pci_syspath)) {
+ if (!snprintf_ok(vf_info_local.suffix, sizeof(vf_info_local.suffix), "v%s", &dent->d_name[6])) {
+ r = -ENOENT;
+ goto out_unref;
+ }
+ break;
+ }
+ }
+ if (isempty(vf_info_local.suffix)) {
+ r = -ENOENT;
+ goto out_unref;
+ }
+ *vf_info = vf_info_local;
+ return 0;
+
+out_unref:
+ udev_device_unref(vf_info_local.physfn_pcidev);
+ return r;
+}
+
/* retrieve on-board index number and label from firmware */
static int dev_pci_onboard(struct udev_device *dev, struct netnames *names) {
unsigned dev_port = 0;
return false;
}
+static bool is_pci_ari_enabled(struct udev_device *dev) {
+ return !!udev_device_get_sysattr_value(dev, "ari_enabled");
+}
+
static int dev_pci_slot(struct udev_device *dev, struct netnames *names) {
struct udev *udev = udev_device_get_udev(names->pcidev);
- unsigned domain, bus, slot, func, dev_port = 0;
+ unsigned domain, bus, slot, func, dev_port = 0, hotplug_slot = 0;
size_t l;
char *s;
const char *attr, *port_name;
- struct udev_device *pci = NULL;
+ _cleanup_udev_device_unref_ struct udev_device *pci = NULL;
+ struct udev_device *hotplug_slot_dev;
char slots[PATH_MAX];
_cleanup_closedir_ DIR *dir = NULL;
struct dirent *dent;
- int hotplug_slot = 0, err = 0;
if (sscanf(udev_device_get_sysname(names->pcidev), "%x:%x:%x.%u", &domain, &bus, &slot, &func) != 4)
return -ENOENT;
+ if (is_pci_ari_enabled(names->pcidev))
+ /* ARI devices support up to 256 functions on a single device ("slot"), and interpret the
+ * traditional 5-bit slot and 3-bit function number as a single 8-bit function number,
+ * where the slot makes up the upper 5 bits. */
+ func += slot * 8;
/* kernel provided port index for multiple ports on a single PCI function */
attr = udev_device_get_sysattr_value(dev, "dev_port");
/* ACPI _SUN — slot user number */
pci = udev_device_new_from_subsystem_sysname(udev, "subsystem", "pci");
- if (!pci) {
- err = -ENOENT;
- goto out;
- }
-
- snprintf(slots, sizeof slots, "%s/slots", udev_device_get_syspath(pci));
- dir = opendir(slots);
- if (!dir) {
- err = -errno;
- goto out;
- }
-
- FOREACH_DIRENT_ALL(dent, dir, break) {
- int i;
- char *rest, *address, str[PATH_MAX];
+ if (!pci)
+ return -ENOENT;
- if (dent->d_name[0] == '.')
- continue;
- i = strtol(dent->d_name, &rest, 10);
- if (rest[0] != '\0')
- continue;
- if (i < 1)
- continue;
+ if (!snprintf_ok(slots, sizeof slots, "%s/slots", udev_device_get_syspath(pci)))
+ return -ENAMETOOLONG;
- snprintf(str, sizeof str, "%s/%s/address", slots, dent->d_name);
- if (read_one_line_file(str, &address) >= 0) {
- /* match slot address with device by stripping the function */
- if (strneq(address, udev_device_get_sysname(names->pcidev), strlen(address)))
- hotplug_slot = i;
- free(address);
+ dir = opendir(slots);
+ if (!dir)
+ return -errno;
+
+ hotplug_slot_dev = names->pcidev;
+ while (hotplug_slot_dev) {
+ FOREACH_DIRENT_ALL(dent, dir, break) {
+ unsigned i;
+ int r;
+ char str[PATH_MAX];
+ _cleanup_free_ char *address = NULL;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ r = safe_atou_full(dent->d_name, 10, &i);
+ if (i < 1 || r < 0)
+ continue;
+
+ if (snprintf_ok(str, sizeof str, "%s/%s/address", slots, dent->d_name) &&
+ read_one_line_file(str, &address) >= 0)
+ /* match slot address with device by stripping the function */
+ if (startswith(udev_device_get_sysname(hotplug_slot_dev), address))
+ hotplug_slot = i;
+
+ if (hotplug_slot > 0)
+ break;
}
-
if (hotplug_slot > 0)
break;
+ rewinddir(dir);
+ hotplug_slot_dev = udev_device_get_parent_with_subsystem_devtype(hotplug_slot_dev, "pci", NULL);
}
if (hotplug_slot > 0) {
if (l == 0)
names->pci_slot[0] = '\0';
}
-out:
- udev_device_unref(pci);
- return err;
+
+ return 0;
+}
+
+static int names_vio(struct udev_device *dev, struct netnames *names) {
+ struct udev_device *parent;
+ unsigned busid, slotid, ethid;
+ const char *syspath;
+
+ /* check if our direct parent is a VIO device with no other bus in-between */
+ parent = udev_device_get_parent(dev);
+ if (!parent)
+ return -ENOENT;
+
+ if (!streq_ptr("vio", udev_device_get_subsystem(parent)))
+ return -ENOENT;
+
+ /* The devices' $DEVPATH number is tied to (virtual) hardware (slot id
+ * selected in the HMC), thus this provides a reliable naming (e.g.
+ * "/devices/vio/30000002/net/eth1"); we ignore the bus number, as
+ * there should only ever be one bus, and then remove leading zeros. */
+ syspath = udev_device_get_syspath(dev);
+
+ if (sscanf(syspath, "/sys/devices/vio/%4x%4x/net/eth%u", &busid, &slotid, ðid) != 3)
+ return -EINVAL;
+
+ xsprintf(names->vio_slot, "v%u", slotid);
+ names->type = NET_VIO;
+ return 0;
+}
+
+#define _PLATFORM_TEST "/sys/devices/platform/vvvvPPPP"
+#define _PLATFORM_PATTERN4 "/sys/devices/platform/%4s%4x:%2x/net/eth%u"
+#define _PLATFORM_PATTERN3 "/sys/devices/platform/%3s%4x:%2x/net/eth%u"
+
+static int names_platform(struct udev_device *dev, struct netnames *names, bool test) {
+ struct udev_device *parent;
+ char vendor[5];
+ unsigned model, instance, ethid;
+ const char *syspath, *pattern, *validchars;
+
+ /* check if our direct parent is a platform device with no other bus in-between */
+ parent = udev_device_get_parent(dev);
+ if (!parent)
+ return -ENOENT;
+
+ if (!streq_ptr("platform", udev_device_get_subsystem(parent)))
+ return -ENOENT;
+
+ syspath = udev_device_get_syspath(dev);
+
+ /* syspath is too short, to have a valid ACPI instance */
+ if (strlen(syspath) < sizeof _PLATFORM_TEST)
+ return -EINVAL;
+
+ /* Vendor ID can be either PNP ID (3 chars A-Z) or ACPI ID (4 chars A-Z and numerals) */
+ if (syspath[sizeof _PLATFORM_TEST - 1] == ':') {
+ pattern = _PLATFORM_PATTERN4;
+ validchars = UPPERCASE_LETTERS DIGITS;
+ } else {
+ pattern = _PLATFORM_PATTERN3;
+ validchars = UPPERCASE_LETTERS;
+ }
+
+ /* Platform devices are named after ACPI table match, and instance id
+ * eg. "/sys/devices/platform/HISI00C2:00");
+ * The Vendor (3 or 4 char), followed by hexdecimal model number : instance id.
+ */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ if (sscanf(syspath, pattern, vendor, &model, &instance, ðid) != 4)
+ return -EINVAL;
+#pragma GCC diagnostic pop
+
+ if (!in_charset(vendor, validchars))
+ return -ENOENT;
+
+ ascii_strlower(vendor);
+
+ xsprintf(names->platform_path, "a%s%xi%u", vendor, model, instance);
+ names->type = NET_PLATFORM;
+ return 0;
}
static int names_pci(struct udev_device *dev, struct netnames *names) {
struct udev_device *parent;
+ struct netnames vf_names = {};
+ struct virtfn_info vf_info = {};
assert(dev);
assert(names);
parent = udev_device_get_parent(dev);
-
- /* there can only ever be one virtio bus per parent device, so we can
- safely ignore any virtio buses. see
- <http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html> */
- while (parent && streq_ptr("virtio", udev_device_get_subsystem(parent)))
- parent = udev_device_get_parent(parent);
+ /* skip virtio subsystem if present */
+ parent = skip_virtio(parent);
if (!parent)
return -ENOENT;
if (!names->pcidev)
return -ENOENT;
}
- dev_pci_onboard(dev, names);
- dev_pci_slot(dev, names);
+
+ if (get_virtfn_info(dev, names, &vf_info) >= 0) {
+ /* If this is an SR-IOV virtual device, get base name using physical device and add virtfn suffix. */
+ vf_names.pcidev = vf_info.physfn_pcidev;
+ dev_pci_onboard(dev, &vf_names);
+ dev_pci_slot(dev, &vf_names);
+ if (vf_names.pci_onboard[0])
+ if (strlen(vf_names.pci_onboard) + strlen(vf_info.suffix) < sizeof(names->pci_onboard))
+ strscpyl(names->pci_onboard, sizeof(names->pci_onboard),
+ vf_names.pci_onboard, vf_info.suffix, NULL);
+ if (vf_names.pci_slot[0])
+ if (strlen(vf_names.pci_slot) + strlen(vf_info.suffix) < sizeof(names->pci_slot))
+ strscpyl(names->pci_slot, sizeof(names->pci_slot),
+ vf_names.pci_slot, vf_info.suffix, NULL);
+ if (vf_names.pci_path[0])
+ if (strlen(vf_names.pci_path) + strlen(vf_info.suffix) < sizeof(names->pci_path))
+ strscpyl(names->pci_path, sizeof(names->pci_path),
+ vf_names.pci_path, vf_info.suffix, NULL);
+ udev_device_unref(vf_info.physfn_pcidev);
+ } else {
+ dev_pci_onboard(dev, names);
+ dev_pci_slot(dev, names);
+ }
return 0;
}
static int names_ccw(struct udev_device *dev, struct netnames *names) {
struct udev_device *cdev;
- const char *bus_id;
+ const char *bus_id, *subsys;
size_t bus_id_len;
- int rc;
+ size_t bus_id_start;
assert(dev);
assert(names);
/* Retrieve the associated CCW device */
cdev = udev_device_get_parent(dev);
+ /* skip virtio subsystem if present */
+ cdev = skip_virtio(cdev);
if (!cdev)
return -ENOENT;
- /* Network devices are always grouped CCW devices */
- if (!streq_ptr("ccwgroup", udev_device_get_subsystem(cdev)))
+ /* Network devices are either single or grouped CCW devices */
+ subsys = udev_device_get_subsystem(cdev);
+ if (!STRPTR_IN_SET(subsys, "ccwgroup", "ccw"))
return -ENOENT;
- /* Retrieve bus-ID of the grouped CCW device. The bus-ID uniquely
+ /* Retrieve bus-ID of the CCW device. The bus-ID uniquely
* identifies the network device on the Linux on System z channel
* subsystem. Note that the bus-ID contains lowercase characters.
*/
* verify each bus-ID part...
*/
bus_id_len = strlen(bus_id);
- if (!bus_id_len || bus_id_len < 8 || bus_id_len > 9)
+ if (!IN_SET(bus_id_len, 8, 9))
return -EINVAL;
/* Strip leading zeros from the bus id for aesthetic purposes. This
* keeps the ccw names stable, yet much shorter in general case of
* bus_id 0.0.0600 -> 600. This is similar to e.g. how PCI domain is
- * not prepended when it is zero.
+ * not prepended when it is zero. Preserve the last 0 for 0.0.0000.
*/
- bus_id += strspn(bus_id, ".0");
+ bus_id_start = strspn(bus_id, ".0");
+ bus_id += bus_id_start < bus_id_len ? bus_id_start : bus_id_len - 1;
/* Store the CCW bus-ID for use as network device name */
- rc = snprintf(names->ccw_group, sizeof(names->ccw_group), "c%s", bus_id);
- if (rc >= 0 && rc < (int)sizeof(names->ccw_group))
- names->type = NET_CCWGROUP;
+ if (snprintf_ok(names->ccw_busid, sizeof(names->ccw_busid), "c%s", bus_id))
+ names->type = NET_CCW;
+
return 0;
}
/* get path names for Linux on System z network devices */
err = names_ccw(dev, &names);
- if (err >= 0 && names.type == NET_CCWGROUP) {
+ if (err >= 0 && names.type == NET_CCW) {
char str[IFNAMSIZ];
- if (snprintf(str, sizeof(str), "%s%s", prefix, names.ccw_group) < (int)sizeof(str))
+ if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.ccw_busid))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+ goto out;
+ }
+
+ /* get ibmveth/ibmvnic slot-based names. */
+ err = names_vio(dev, &names);
+ if (err >= 0 && names.type == NET_VIO) {
+ char str[IFNAMSIZ];
+
+ if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.vio_slot))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ goto out;
+ }
+
+ /* get ACPI path names for ARM64 platform devices */
+ err = names_platform(dev, &names, test);
+ if (err >= 0 && names.type == NET_PLATFORM) {
+ char str[IFNAMSIZ];
+
+ if (snprintf_ok(str, sizeof str, "%s%s", prefix, names.platform_path))
udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
goto out;
}
if (names.type == NET_PCI) {
char str[IFNAMSIZ];
- if (names.pci_onboard[0])
- if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str);
+ if (names.pci_onboard[0] &&
+ snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_onboard))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str);
- if (names.pci_onboard_label)
- if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_onboard_label) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str);
+ if (names.pci_onboard_label &&
+ snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_onboard_label))
+ udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str);
- if (names.pci_path[0])
- if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_path) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+ if (names.pci_path[0] &&
+ snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_path))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
- if (names.pci_slot[0])
- if (snprintf(str, sizeof(str), "%s%s", prefix, names.pci_slot) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ if (names.pci_slot[0] &&
+ snprintf_ok(str, sizeof str, "%s%s", prefix, names.pci_slot))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
goto out;
}
if (err >= 0 && names.type == NET_USB) {
char str[IFNAMSIZ];
- if (names.pci_path[0])
- if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.usb_ports) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+ if (names.pci_path[0] &&
+ snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_path, names.usb_ports))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
- if (names.pci_slot[0])
- if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.usb_ports) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ if (names.pci_slot[0] &&
+ snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_slot, names.usb_ports))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
goto out;
}
if (err >= 0 && names.type == NET_BCMA) {
char str[IFNAMSIZ];
- if (names.pci_path[0])
- if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_path, names.bcma_core) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
+ if (names.pci_path[0] &&
+ snprintf_ok(str, sizeof str, "%s%s%s", prefix, names.pci_path, names.bcma_core))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str);
- if (names.pci_slot[0])
- if (snprintf(str, sizeof(str), "%s%s%s", prefix, names.pci_slot, names.bcma_core) < (int)sizeof(str))
- udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
+ if (names.pci_slot[0] &&
+ snprintf(str, sizeof str, "%s%s%s", prefix, names.pci_slot, names.bcma_core))
+ udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str);
goto out;
}
out: