Add a selftest exercising the vfio-pci DSN handling:
 - the serial number is scrubbed to zero by default while the DSN
   capability remains present,
 - guest writes to the DSN bytes are rejected (value unchanged),
 - VFIO_DEVICE_FEATURE_PCI_DSN PROBE reflects DSN support,
 - SET/GET round-trips and is reflected in the guest-visible config
   space,
 - SET twice returns the latest value on GET,
 - the presented serial persists across a device reset, and
 - a short argsz is rejected with -EINVAL.

The tests skip when the assigned device has no DSN capability or does
not support reset.

Run with the device assigned to vfio-pci, e.g.:

  VFIO_SELFTESTS_BDF="0000:01:00.0" ./vfio_pci_dsn_test

Exercised under QEMU with full VFIO assignment via IOMMUFD: all of the
test's cases passed on x86_64 (intel-iommu) and arm64 (smmuv3) against an
emulated e1000e (which implements DSN), and on x86_64 against an SR-IOV
VF that implements DSN in an AER -> ARI -> DSN extended-capability chain.
The SR-IOV run used a KASAN + PROVE_LOCKING kernel with no reports.

Signed-off-by: Pranjal Arya <[email protected]>
---
 MAINTAINERS                                      |   6 +
 tools/testing/selftests/vfio/Makefile            |   1 +
 tools/testing/selftests/vfio/vfio_pci_dsn_test.c | 206 +++++++++++++++++++++++
 3 files changed, 213 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 8629ed2aa82f..ed8e7df12021 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28347,6 +28347,12 @@ L:     [email protected]
 S:     Supported
 F:     drivers/vfio/pci/nvgrace-gpu/
 
+VFIO PCI DEVICE SERIAL NUMBER SELFTEST
+M:     Pranjal Arya <[email protected]>
+L:     [email protected]
+S:     Maintained
+F:     tools/testing/selftests/vfio/vfio_pci_dsn_test.c
+
 VFIO PCI DEVICE SPECIFIC DRIVERS
 R:     Jason Gunthorpe <[email protected]>
 R:     Yishai Hadas <[email protected]>
diff --git a/tools/testing/selftests/vfio/Makefile 
b/tools/testing/selftests/vfio/Makefile
index e6e8cb52ab03..06e637573cf7 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -13,6 +13,7 @@ TEST_GEN_PROGS += vfio_pci_device_test
 TEST_GEN_PROGS += vfio_pci_device_init_perf_test
 TEST_GEN_PROGS += vfio_pci_driver_test
 TEST_GEN_PROGS += vfio_pci_sriov_uapi_test
+TEST_GEN_PROGS += vfio_pci_dsn_test
 
 TEST_FILES += scripts/cleanup.sh
 TEST_FILES += scripts/lib.sh
diff --git a/tools/testing/selftests/vfio/vfio_pci_dsn_test.c 
b/tools/testing/selftests/vfio/vfio_pci_dsn_test.c
new file mode 100644
index 000000000000..d7652ad725f4
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_pci_dsn_test.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tests for the PCIe Device Serial Number (DSN) handling in vfio-pci:
+ *  - the physical serial is scrubbed (read as zero) by default,
+ *  - guest writes to the DSN bytes are rejected (no change), and
+ *  - VFIO_DEVICE_FEATURE_PCI_DSN can probe/set/get the presented serial.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/ioctl.h>
+
+#include <linux/limits.h>
+#include <linux/pci_regs.h>
+#include <linux/vfio.h>
+
+#include <libvfio.h>
+
+#include "kselftest_harness.h"
+
+static const char *device_bdf;
+
+/* Walk the extended capability chain and return the DSN cap offset, or 0. */
+static u16 find_dsn_cap(struct vfio_pci_device *device)
+{
+       u16 pos = PCI_CFG_SPACE_SIZE;
+       int loops = (PCI_CFG_SPACE_EXP_SIZE - PCI_CFG_SPACE_SIZE) /
+                   PCI_CAP_SIZEOF;
+
+       while (pos >= PCI_CFG_SPACE_SIZE && loops--) {
+               u32 header = vfio_pci_config_readl(device, pos);
+
+               if (!header)
+                       break;
+
+               if (PCI_EXT_CAP_ID(header) == PCI_EXT_CAP_ID_DSN)
+                       return pos;
+
+               pos = PCI_EXT_CAP_NEXT(header);
+       }
+
+       return 0;
+}
+
+/*
+ * Issue the DSN device feature.  @serial may be NULL for PROBE (no data is
+ * read or written in that case); for GET/SET it is required.
+ */
+static int dsn_feature(struct vfio_pci_device *device, u32 op, u64 *serial)
+{
+       u8 buf[sizeof(struct vfio_device_feature) +
+              sizeof(struct vfio_device_feature_pci_dsn)] = {};
+       struct vfio_device_feature *feature = (void *)buf;
+       struct vfio_device_feature_pci_dsn *dsn = (void *)feature->data;
+
+       feature->argsz = sizeof(buf);
+       feature->flags = op | VFIO_DEVICE_FEATURE_PCI_DSN;
+
+       if ((op & VFIO_DEVICE_FEATURE_SET) && serial)
+               dsn->serial_number = *serial;
+
+       if (ioctl(device->fd, VFIO_DEVICE_FEATURE, feature))
+               return -errno;
+
+       if ((op & VFIO_DEVICE_FEATURE_GET) && serial)
+               *serial = dsn->serial_number;
+
+       return 0;
+}
+
+FIXTURE(vfio_pci_dsn_test) {
+       struct iommu *iommu;
+       struct vfio_pci_device *device;
+       u16 dsn_pos;
+};
+
+FIXTURE_SETUP(vfio_pci_dsn_test)
+{
+       self->iommu = iommu_init(default_iommu_mode);
+       self->device = vfio_pci_device_init(device_bdf, self->iommu);
+       self->dsn_pos = find_dsn_cap(self->device);
+}
+
+FIXTURE_TEARDOWN(vfio_pci_dsn_test)
+{
+       vfio_pci_device_cleanup(self->device);
+       iommu_cleanup(self->iommu);
+}
+
+/* The physical serial number must not be visible; reads must be zero. */
+TEST_F(vfio_pci_dsn_test, serial_scrubbed)
+{
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+
+       ASSERT_EQ(0, vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_LOW_DW));
+       ASSERT_EQ(0, vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_HIGH_DW));
+
+       /* The capability header itself must still be present. */
+       ASSERT_EQ(PCI_EXT_CAP_ID_DSN,
+                 PCI_EXT_CAP_ID(vfio_pci_config_readl(self->device,
+                                                      self->dsn_pos)));
+}
+
+/* Guest writes to the DSN bytes must be ignored (read-only state). */
+TEST_F(vfio_pci_dsn_test, write_rejected)
+{
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+
+       vfio_pci_config_writel(self->device, self->dsn_pos + PCI_DSN_LOW_DW, 
0xdeadbeef);
+       vfio_pci_config_writel(self->device, self->dsn_pos + PCI_DSN_HIGH_DW, 
0xcafef00d);
+
+       ASSERT_EQ(0, vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_LOW_DW));
+       ASSERT_EQ(0, vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_HIGH_DW));
+}
+
+/* PROBE must succeed iff the device has a DSN capability. */
+TEST_F(vfio_pci_dsn_test, probe)
+{
+       /* PROBE must include at least one supported op flag to pass. */
+       int ret = dsn_feature(self->device,
+                             VFIO_DEVICE_FEATURE_PROBE |
+                             VFIO_DEVICE_FEATURE_GET |
+                             VFIO_DEVICE_FEATURE_SET, NULL);
+
+       if (!self->dsn_pos)
+               ASSERT_EQ(-ENOTTY, ret);
+       else
+               ASSERT_EQ(0, ret);
+}
+
+/* SET then GET must round-trip, and the guest-visible bytes must match. */
+TEST_F(vfio_pci_dsn_test, set_get_roundtrip)
+{
+       u64 want = 0x0123456789abcdefULL;
+       u64 got = 0;
+
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_SET, &want));
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_GET, &got));
+       ASSERT_EQ(want, got);
+
+       ASSERT_EQ((u32)want,
+                 vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_LOW_DW));
+       ASSERT_EQ((u32)(want >> 32),
+                 vfio_pci_config_readl(self->device, self->dsn_pos + 
PCI_DSN_HIGH_DW));
+}
+
+/* SET twice; GET must return the latest value. */
+TEST_F(vfio_pci_dsn_test, set_twice)
+{
+       u64 first = 0x1111222233334444ULL;
+       u64 second = 0xaaaabbbbccccddddULL;
+       u64 got = 0;
+
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_SET, 
&first));
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_SET, 
&second));
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_GET, &got));
+       ASSERT_EQ(second, got);
+}
+
+/* The presented serial persists across a device reset (FLR/SBR). */
+TEST_F(vfio_pci_dsn_test, persists_across_reset)
+{
+       u64 want = 0x5555666677778888ULL;
+       u64 got = 0;
+
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+       if (!(self->device->info.flags & VFIO_DEVICE_FLAGS_RESET))
+               SKIP(return, "Device does not support reset\n");
+
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_SET, &want));
+       vfio_pci_device_reset(self->device);
+       ASSERT_EQ(0, dsn_feature(self->device, VFIO_DEVICE_FEATURE_GET, &got));
+       ASSERT_EQ(want, got);
+}
+
+/* A short argsz must be rejected with -EINVAL. */
+TEST_F(vfio_pci_dsn_test, bad_argsz)
+{
+       struct vfio_device_feature feature = {
+               .argsz = sizeof(struct vfio_device_feature),
+               .flags = VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_PCI_DSN,
+       };
+
+       if (!self->dsn_pos)
+               SKIP(return, "Device has no DSN capability\n");
+
+       ASSERT_EQ(-1, ioctl(self->device->fd, VFIO_DEVICE_FEATURE, &feature));
+       ASSERT_EQ(EINVAL, errno);
+}
+
+int main(int argc, char *argv[])
+{
+       device_bdf = vfio_selftests_get_bdf(&argc, argv);
+       return test_harness_run(argc, argv);
+}

-- 
2.34.1


Reply via email to