Move the BDF parsing functions from nic_uio into a separate header which
allows us to unit test them as independent functions, verifying that
both BSD and Linux format PCI addresses are supported.

Signed-off-by: Bruce Richardson <[email protected]>
---
 app/test/meson.build               |   1 +
 app/test/test_kmod_pci_bdf.c       | 142 +++++++++++++++++++++++++++++
 kernel/freebsd/nic_uio/meson.build |   2 +-
 kernel/freebsd/nic_uio/nic_uio.c   | 112 +----------------------
 kernel/freebsd/nic_uio/pci_bdf.h   | 129 ++++++++++++++++++++++++++
 5 files changed, 277 insertions(+), 109 deletions(-)
 create mode 100644 app/test/test_kmod_pci_bdf.c
 create mode 100644 kernel/freebsd/nic_uio/pci_bdf.h

diff --git a/app/test/meson.build b/app/test/meson.build
index 51abeeb732..a5d4716650 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -104,6 +104,7 @@ source_file_deps = {
     'test_ipsec.c': ['bus_vdev', 'net', 'cryptodev', 'ipsec', 'security'],
     'test_ipsec_perf.c': ['net', 'ipsec'],
     'test_ipsec_sad.c': ['ipsec'],
+    'test_kmod_pci_bdf.c': [],
     'test_kvargs.c': ['kvargs'],
     'test_latencystats.c': ['ethdev', 'latencystats', 'metrics'] + 
sample_packet_forward_deps,
     'test_lcore_var.c': [],
diff --git a/app/test/test_kmod_pci_bdf.c b/app/test/test_kmod_pci_bdf.c
new file mode 100644
index 0000000000..41d5c9e442
--- /dev/null
+++ b/app/test/test_kmod_pci_bdf.c
@@ -0,0 +1,142 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <rte_common.h>
+
+#include "../../kernel/freebsd/nic_uio/pci_bdf.h"
+#include "test.h"
+
+static int
+test_kmod_pci_bdf_valid(void)
+{
+       static const struct {
+               const char *address;
+               struct pci_bdf expected;
+       } cases[] = {
+               {"0:0:0", {0, 0, 0, 0}},
+               {"42:0:0", {0, 42, 0, 0}},
+               {"177:31:7", {0, 177, 31, 7}},
+               {"255:31:7", {0, 255, 31, 7}},
+               {"pci0:0:0:0", {0, 0, 0, 0}},
+               {"pci1:177:31:7", {1, 177, 31, 7}},
+               {"pci65535:255:31:7", {65535, 255, 31, 7}},
+               {"00:00.0", {0, 0, 0, 0}},
+               {"42:00.0", {0, 0x42, 0, 0}},
+               {"ff:1f.7", {0, 255, 31, 7}},
+               {"FF:1F.7", {0, 255, 31, 7}},
+               {"0000:00:00.0", {0, 0, 0, 0}},
+               {"0000:98:00.1", {0, 0x98, 0, 1}},
+               {"0001:b1:1f.7", {1, 0xb1, 31, 7}},
+               {"aBcD:Ab:0F.3", {0xabcd, 0xab, 15, 3}},
+               {"ffff:ff:1f.7", {65535, 255, 31, 7}},
+       };
+
+       for (size_t i = 0; i < RTE_DIM(cases); i++) {
+               struct pci_bdf actual = {UINT32_MAX, UINT32_MAX, UINT32_MAX, 
UINT32_MAX};
+               const struct pci_bdf *expected = &cases[i].expected;
+               const char *address = cases[i].address;
+
+               TEST_ASSERT_SUCCESS(pci_bdf_parse(address, &actual),
+                               "Rejected valid address '%s'", address);
+               TEST_ASSERT_EQUAL(actual.domain, expected->domain,
+                               "Incorrect domain for '%s'", address);
+               TEST_ASSERT_EQUAL(actual.bus, expected->bus,
+                               "Incorrect bus for '%s'", address);
+               TEST_ASSERT_EQUAL(actual.devid, expected->devid,
+                               "Incorrect device for '%s'", address);
+               TEST_ASSERT_EQUAL(actual.function, expected->function,
+                               "Incorrect function for '%s'", address);
+       }
+       return TEST_SUCCESS;
+}
+
+static int
+test_kmod_pci_bdf_invalid(void)
+{
+       static const char * const cases[] = {
+               "",
+               "0",
+               "0:0",
+               ":0:00",
+               "42::0",
+               "42:0:",
+               "42:0:0:0",
+               "42:0:0,43:0:0",
+               "pci",
+               "pci:42:0:0",
+               "pci0::0:0",
+               "pci0:42::0",
+               "pci0:42:0:",
+               "pci0:42:0:0:0",
+               "+42:0:0",
+               "-0:0:0",
+               "42:+0:0",
+               "42:0:-0",
+               "pci+1:42:0:0",
+               "pci-0:42:0:0",
+               " 42:0:0",
+               "42: 0:0",
+               "42:0:0 ",
+               "42:0:0\n",
+               "42:0:0junk",
+               "pci0:af:0:0",
+               "af:0:0",
+               "0x42:0:0",
+               "256:0:0",
+               "0:32:0",
+               "0:0:8",
+               "pci65536:0:0:0",
+               "65578:0:0",
+               "pci65536:42:0:0",
+               "99999999999999999999999999999999999999:0:0",
+               "0000:42:0:0",
+               "pci0:42:00.0",
+               "0000:152:00.0",
+               "0000:42:000.0",
+               "0000:42:00.000",
+               "00000:42:00.0",
+               "0:42:00.0",
+               "42:0.0",
+               "42:00.",
+               "42.00:0",
+               "42.00.0",
+               "0000:gg:00.0",
+               "0000:00:20.0",
+               "0000:00:00.8",
+               "0000:00:00.f",
+               "0000:00:00.0junk",
+               "\xff:00.0",
+       };
+       const struct pci_bdf sentinel = {1, 2, 3, 4};
+
+       for (size_t i = 0; i < RTE_DIM(cases); i++) {
+               struct pci_bdf actual = sentinel;
+               const char *address = cases[i];
+
+               TEST_ASSERT_EQUAL(pci_bdf_parse(address, &actual), EINVAL,
+                               "Did not reject invalid address '%s' with 
EINVAL", address);
+               TEST_ASSERT(actual.domain == sentinel.domain && actual.bus == 
sentinel.bus &&
+                               actual.devid == sentinel.devid &&
+                               actual.function == sentinel.function,
+                               "Modified output for invalid address '%s'", 
address);
+       }
+       return TEST_SUCCESS;
+}
+
+static struct unit_test_suite kmod_pci_bdf_testsuite = {
+       .suite_name = "Kernel module PCI BDF parsing",
+       .unit_test_cases = {
+               TEST_CASE(test_kmod_pci_bdf_valid),
+               TEST_CASE(test_kmod_pci_bdf_invalid),
+               TEST_CASES_END(),
+       },
+};
+
+static int
+test_kmod_pci_bdf(void)
+{
+       return unit_test_suite_runner(&kmod_pci_bdf_testsuite);
+}
+
+REGISTER_FAST_TEST(kmod_pci_bdf_autotest, NOHUGE_OK, ASAN_OK, 
test_kmod_pci_bdf);
diff --git a/kernel/freebsd/nic_uio/meson.build 
b/kernel/freebsd/nic_uio/meson.build
index 4bdaf96944..f10a8a7913 100644
--- a/kernel/freebsd/nic_uio/meson.build
+++ b/kernel/freebsd/nic_uio/meson.build
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2017 Intel Corporation
 
-sources = files('nic_uio.c')
+sources = files('nic_uio.c', 'pci_bdf.h')
diff --git a/kernel/freebsd/nic_uio/nic_uio.c b/kernel/freebsd/nic_uio/nic_uio.c
index 7260a2a2f6..92865e296d 100644
--- a/kernel/freebsd/nic_uio/nic_uio.c
+++ b/kernel/freebsd/nic_uio/nic_uio.c
@@ -9,7 +9,6 @@ __FBSDID("$FreeBSD$");
 #include <sys/kernel.h> /* types used in module initialization */
 #include <sys/conf.h> /* cdevsw struct */
 #include <sys/bus.h> /* structs, prototypes for pci bus stuff and DEVMETHOD */
-#include <sys/ctype.h>
 #include <sys/rman.h>
 #include <sys/systm.h>
 #include <sys/lock.h>
@@ -25,6 +24,8 @@ __FBSDID("$FreeBSD$");
 #include <vm/vm_page.h>
 #include <vm/vm_pager.h>
 
+/* the PCI BDF parsing functions are kept separate so we can unit test them 
from userspace */
+#include "pci_bdf.h"
 
 #define MAX_BARS (PCIR_MAX_BAR_0 + 1)
 
@@ -73,111 +74,6 @@ struct device {
     int dev;
 };
 
-struct pci_bdf {
-       uint32_t domain;
-       uint32_t bus;
-       uint32_t devid;
-       uint32_t function;
-};
-
-
-static int
-nic_uio_parse_bdf_linux(const char *token, const size_t token_len, unsigned 
long *values)
-{
-       static const char *long_fmt = "FFFF:FF:FF.F";
-       static const char *short_fmt = "FF:FF.F";
-       const char *fmt = NULL;
-       int bus_ofs = 0;
-
-       if (token_len == strlen(long_fmt))
-               fmt = long_fmt;
-       else if (token_len == strlen(short_fmt))
-               fmt = short_fmt;
-       else
-               return EINVAL;
-
-       /* validate the token against the expected format */
-       for (size_t i = 0; i < token_len; i++)
-               if (fmt[i] == 'F') {
-                       if (!isxdigit(token[i]))
-                               return EINVAL;
-               } else if (token[i] != fmt[i])
-                       return EINVAL;
-
-       if (fmt == long_fmt) {
-               values[0] = strtoul(token, NULL, 16);
-               bus_ofs = 5;
-       }
-       values[1] = strtoul(token + bus_ofs, NULL, 16);
-       values[2] = strtoul(token + bus_ofs + 3, NULL, 16);
-       values[3] = strtoul(token + bus_ofs + 6, NULL, 16);
-       return 0;
-}
-
-static int
-nic_uio_parse_bdf_bsd(const char *token, const size_t token_len, unsigned long 
*values)
-{
-       char *endptr;
-
-       /* skip the "pci" prefix if present */
-       if (strncmp(token, "pci", 3) == 0) {
-               token += 3;
-               if (!isdigit(*token))
-                       return EINVAL;
-
-               values[0] = strtoul(token, &endptr, 10);
-               if (*endptr != ':' || !isdigit(*(endptr + 1)))
-                       return EINVAL;
-               token = endptr + 1;
-       } else if (!isdigit(*token))
-               return EINVAL;
-
-       /* parse bus */
-       values[1] = strtoul(token, &endptr, 10);
-       if (*endptr != ':' || !isdigit(*(endptr + 1)))
-               return EINVAL;
-       token = endptr + 1;
-       /* parse device */
-       values[2] = strtoul(token, &endptr, 10);
-       if (*endptr != ':' || !isdigit(*(endptr + 1)))
-               return EINVAL;
-       token = endptr + 1;
-       /* parse function */
-       values[3] = strtoul(token, &endptr, 10);
-       if (*endptr != '\0')
-               return EINVAL;
-
-       return 0;
-}
-
-static int
-nic_uio_parse_bdf(char *token, struct pci_bdf *bdf)
-{
-       static const unsigned long bdf_field_limits[4] = {UINT16_MAX, 255, 31, 
7};
-       const size_t token_len = strlen(token);
-       unsigned long values[4] = {0};
-
-       if (token_len < 5)
-               return EINVAL;
-
-       /* linux uses '.' for final separator */
-       int ret = token[token_len - 2] == '.' ?
-                       nic_uio_parse_bdf_linux(token, token_len, values) :
-                       nic_uio_parse_bdf_bsd(token, token_len, values);
-       if (ret != 0)
-               return ret;
-
-       for (int i = 0; i < 4; i++)
-               if (values[i] > bdf_field_limits[i])
-                       return EINVAL;
-
-       bdf->domain = values[0];
-       bdf->bus = values[1];
-       bdf->devid = values[2];
-       bdf->function = values[3];
-       return 0;
-}
-
 DEFINE_CLASS_0(nic_uio, nic_uio_driver, nic_uio_methods, sizeof(struct 
nic_uio_softc));
 
 #if __FreeBSD_version < 1400000
@@ -278,7 +174,7 @@ nic_uio_probe (device_t dev)
                struct pci_bdf bdf;
 
                token = strsep(&remaining, ",");
-               if (nic_uio_parse_bdf(token, &bdf) != 0) {
+               if (pci_bdf_parse(token, &bdf) != 0) {
                        device_printf(dev, "Invalid PCI address in 
hw.nic_uio.bdfs\n");
                        return ENXIO;
                }
@@ -365,7 +261,7 @@ nic_uio_load(void)
        printf("nic_uio: hw.nic_uio.bdfs = '%s'\n", bdf_str);
        while (remaining != NULL && remaining[0] != '\0') {
                token = strsep(&remaining, ",");
-               if (count == nitems(bdfs) || nic_uio_parse_bdf(token, 
&bdfs[count]) != 0 ||
+               if (count == nitems(bdfs) || pci_bdf_parse(token, &bdfs[count]) 
!= 0 ||
                                (remaining != NULL && remaining[0] == '\0')) {
                        printf("nic_uio: invalid PCI address list in 
hw.nic_uio.bdfs\n");
                        return EINVAL;
diff --git a/kernel/freebsd/nic_uio/pci_bdf.h b/kernel/freebsd/nic_uio/pci_bdf.h
new file mode 100644
index 0000000000..8115c0f435
--- /dev/null
+++ b/kernel/freebsd/nic_uio/pci_bdf.h
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#ifndef _PCI_BDF_H_
+#define _PCI_BDF_H_
+
+#ifdef _KERNEL
+#include <sys/param.h>
+#include <sys/ctype.h>
+#include <sys/errno.h>
+#include <sys/systm.h>
+#else
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#endif
+
+struct pci_bdf {
+       uint32_t domain;
+       uint32_t bus;
+       uint32_t devid;
+       uint32_t function;
+};
+
+static inline int
+pci_bdf_parse_linux(const char *token, const size_t token_len, unsigned long 
*values)
+{
+       static const char *long_fmt = "FFFF:FF:FF.F";
+       static const char *short_fmt = "FF:FF.F";
+       const char *fmt = NULL;
+       int bus_ofs = 0;
+
+       if (token_len == strlen(long_fmt))
+               fmt = long_fmt;
+       else if (token_len == strlen(short_fmt))
+               fmt = short_fmt;
+       else
+               return EINVAL;
+
+       /* validate the token against the expected format */
+       for (size_t i = 0; i < token_len; i++)
+               if (fmt[i] == 'F') {
+                       if (!isxdigit((unsigned char)token[i]))
+                               return EINVAL;
+               } else if (token[i] != fmt[i])
+                       return EINVAL;
+
+       if (fmt == long_fmt) {
+               values[0] = strtoul(token, NULL, 16);
+               bus_ofs = 5;
+       }
+       values[1] = strtoul(token + bus_ofs, NULL, 16);
+       values[2] = strtoul(token + bus_ofs + 3, NULL, 16);
+       values[3] = strtoul(token + bus_ofs + 6, NULL, 16);
+       return 0;
+}
+
+static inline int
+pci_bdf_parse_bsd(const char *token, unsigned long *values)
+{
+       char *endptr;
+
+       /* skip the "pci" prefix if present */
+       if (strncmp(token, "pci", 3) == 0) {
+               token += 3;
+               if (!isdigit((unsigned char)*token))
+                       return EINVAL;
+
+               values[0] = strtoul(token, &endptr, 10);
+               if (*endptr != ':' || !isdigit((unsigned char)*(endptr + 1)))
+                       return EINVAL;
+               token = endptr + 1;
+       } else if (!isdigit((unsigned char)*token))
+               return EINVAL;
+
+       /* parse bus */
+       values[1] = strtoul(token, &endptr, 10);
+       if (*endptr != ':' || !isdigit((unsigned char)*(endptr + 1)))
+               return EINVAL;
+       token = endptr + 1;
+       /* parse device */
+       values[2] = strtoul(token, &endptr, 10);
+       if (*endptr != ':' || !isdigit((unsigned char)*(endptr + 1)))
+               return EINVAL;
+       token = endptr + 1;
+       /* parse function */
+       values[3] = strtoul(token, &endptr, 10);
+       if (*endptr != '\0')
+               return EINVAL;
+
+       return 0;
+}
+
+/*
+ * Parse a hexadecimal DPDK or decimal FreeBSD PCI address.
+ * Return zero on success or EINVAL on failure, leaving bdf unchanged on 
failure.
+ */
+static inline int
+pci_bdf_parse(const char *token, struct pci_bdf *bdf)
+{
+       static const unsigned long bdf_field_limits[4] = {UINT16_MAX, 255, 31, 
7};
+       const size_t token_len = strlen(token);
+       unsigned long values[4] = {0};
+
+       if (token_len < 5)
+               return EINVAL;
+
+       /* linux uses '.' for final separator */
+       int ret = token[token_len - 2] == '.' ?
+                       pci_bdf_parse_linux(token, token_len, values) :
+                       pci_bdf_parse_bsd(token, values);
+       if (ret != 0)
+               return ret;
+
+       for (int i = 0; i < 4; i++)
+               if (values[i] > bdf_field_limits[i])
+                       return EINVAL;
+
+       bdf->domain = values[0];
+       bdf->bus = values[1];
+       bdf->devid = values[2];
+       bdf->function = values[3];
+       return 0;
+}
+
+#endif /* _PCI_BDF_H_ */
-- 
2.55.0

Reply via email to