The preceding patch ("vfio/pci: Virtualize and scrub Device Serial
Number from guests") hides the physical PCIe Device Serial Number from
guests by zeroing it.  Some use cases instead need a stable but
non-host-identifying serial, for example to keep a consistent device
identity across live migration between different physical devices.

Add a VFIO_DEVICE_FEATURE_PCI_DSN device feature that lets the trusted
userspace VMM GET/SET the 64-bit serial number presented in the device's
virtualized config space.  SET writes the value into the virtualized DSN
capability (whose dwords are virtualized by the preceding patch, so guest
reads return it) and GET reads it back; PROBE reports support.  The
feature returns -ENOTTY for devices without a DSN capability.  Guest
writes remain disallowed.

The presented serial is stored only in vconfig: it persists across a
runtime FLR/Secondary Bus Reset (reset does not rebuild vconfig) and is
cleared when the device fd is closed (vfio_config_free()), i.e. it is
per-open.  A feature ioctl can only reach an opened device, whose vconfig
was allocated by vfio_config_init() during vfio_pci_core_enable().

This feature uses index 13, not the next sequential value 12.  Index 12
is already assigned to VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 in
linux-next by commit d7140b5dde45
("vfio: Define uAPI for re-init initial bytes during the PRE_COPY phase")
(https://lore.kernel.org/r/[email protected]).
Using 12 makes the core feature switch route the ioctl to the
migration-precopy handler, which returns -EINVAL; this was observed in
testing, hence index 13.

Signed-off-by: Pranjal Arya <[email protected]>
---
 drivers/vfio/pci/vfio_pci_config.c | 59 ++++++++++++++++++++++++++++++++++++++
 drivers/vfio/pci/vfio_pci_core.c   |  2 ++
 drivers/vfio/pci/vfio_pci_priv.h   |  2 ++
 include/uapi/linux/vfio.h          | 18 ++++++++++++
 4 files changed, 81 insertions(+)

diff --git a/drivers/vfio/pci/vfio_pci_config.c 
b/drivers/vfio/pci/vfio_pci_config.c
index 24dfeb43cb71..60c19129c814 100644
--- a/drivers/vfio/pci/vfio_pci_config.c
+++ b/drivers/vfio/pci/vfio_pci_config.c
@@ -2078,3 +2078,62 @@ bool vfio_pci_core_range_intersect_range(loff_t 
buf_start, size_t buf_cnt,
        return false;
 }
 EXPORT_SYMBOL_GPL(vfio_pci_core_range_intersect_range);
+
+int vfio_pci_core_feature_dsn(struct vfio_pci_core_device *vdev, u32 flags,
+                             void __user *arg, size_t argsz)
+{
+       struct vfio_device_feature_pci_dsn dsn;
+       struct pci_dev *pdev = vdev->pdev;
+       __le32 *vserial;
+       int pos, ret;
+
+       /*
+        * The DSN capability is virtualized in vconfig; locate it on the
+        * physical device only to decide whether the feature is supported.
+        * A feature ioctl can only reach an opened device, and vconfig is
+        * allocated by vfio_config_init() during vfio_pci_core_enable() on
+        * open, so vconfig is valid here.
+        */
+       pos = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_DSN);
+       if (!pos)
+               return -ENOTTY;
+
+       ret = vfio_check_feature(flags, argsz,
+                                VFIO_DEVICE_FEATURE_GET |
+                                VFIO_DEVICE_FEATURE_SET,
+                                sizeof(dsn));
+       if (ret != 1)
+               return ret;
+
+       vserial = (__le32 *)&vdev->vconfig[pos + PCI_DSN_LOW_DW];
+
+       if (flags & VFIO_DEVICE_FEATURE_SET) {
+               if (copy_from_user(&dsn, arg, sizeof(dsn)))
+                       return -EFAULT;
+
+               /*
+                * The config-space read path (vfio_default_config_read())
+                * does not hold a lock, and a guest reads the DSN as two
+                * 32-bit dwords.  Store each dword with WRITE_ONCE() so a
+                * concurrent guest read observes a consistent dword; a guest
+                * reading the two halves around this update may see an
+                * old/new mix, exactly as hardware may tear a 64-bit read of
+                * a register pair.  This matches the DSN's read-only,
+                * advisory nature.  Serializing concurrent SET callers is the
+                * userspace VMM's responsibility.
+                */
+               WRITE_ONCE(vserial[0], 
cpu_to_le32(lower_32_bits(dsn.serial_number)));
+               WRITE_ONCE(vserial[1], 
cpu_to_le32(upper_32_bits(dsn.serial_number)));
+               return 0;
+       }
+
+       /* VFIO_DEVICE_FEATURE_GET */
+       dsn.serial_number =
+               ((u64)le32_to_cpu(READ_ONCE(vserial[1])) << 32) |
+               le32_to_cpu(READ_ONCE(vserial[0]));
+
+       if (copy_to_user(arg, &dsn, sizeof(dsn)))
+               return -EFAULT;
+
+       return 0;
+}
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index a28f1e99362c..08a98a796717 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -1572,6 +1572,8 @@ int vfio_pci_core_ioctl_feature(struct vfio_device 
*device, u32 flags,
                return vfio_pci_core_feature_token(vdev, flags, arg, argsz);
        case VFIO_DEVICE_FEATURE_DMA_BUF:
                return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz);
+       case VFIO_DEVICE_FEATURE_PCI_DSN:
+               return vfio_pci_core_feature_dsn(vdev, flags, arg, argsz);
        default:
                return -ENOTTY;
        }
diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h
index fca9d0dfac90..dfb0c800e6f1 100644
--- a/drivers/vfio/pci/vfio_pci_priv.h
+++ b/drivers/vfio/pci/vfio_pci_priv.h
@@ -64,6 +64,8 @@ void vfio_pci_uninit_perm_bits(void);
 
 int vfio_config_init(struct vfio_pci_core_device *vdev);
 void vfio_config_free(struct vfio_pci_core_device *vdev);
+int vfio_pci_core_feature_dsn(struct vfio_pci_core_device *vdev, u32 flags,
+                             void __user *arg, size_t argsz);
 
 int vfio_pci_set_power_state(struct vfio_pci_core_device *vdev,
                             pci_power_t state);
diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h
index 5de618a3a5ee..e5b8dfd3833f 100644
--- a/include/uapi/linux/vfio.h
+++ b/include/uapi/linux/vfio.h
@@ -1511,6 +1511,24 @@ struct vfio_device_feature_bus_master {
  */
 #define VFIO_DEVICE_FEATURE_DMA_BUF 11
 
+/**
+ * Upon VFIO_DEVICE_FEATURE_SET, set the PCIe Device Serial Number (DSN)
+ * presented to the user (guest) in the device's virtualized config space.
+ * By default vfio-pci scrubs the physical DSN to zero so the host device's
+ * unique identifier is not leaked; this feature lets the hypervisor present
+ * a chosen, per-VM synthetic serial instead (for example a stable but
+ * non-host-identifying value for migration).
+ *
+ * Upon VFIO_DEVICE_FEATURE_GET, read back the currently presented serial.
+ *
+ * The feature is only available for devices that expose a DSN capability.
+ * serial_number is the 64-bit serial in little-endian wire order.
+ */
+struct vfio_device_feature_pci_dsn {
+       __aligned_u64 serial_number;
+};
+#define VFIO_DEVICE_FEATURE_PCI_DSN 13
+
 struct vfio_region_dma_range {
        __u64 offset;
        __u64 length;

-- 
2.34.1


Reply via email to