* [PATCH 00/14] Add Rust PCI SR-IOV support
@ 2026-09-15 20:56 Zhi Wang
2026-09-15 20:56 ` [PATCH 01/14] PCI: add driver flag to opt into disabling SR-IOV on remove() Zhi Wang
` (14 more replies)
0 siblings, 15 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
Rust PCI drivers need to enable and disable SR-IOV and share selected
PF-owned functionality with their VF drivers. The shared data must remain
valid while a VF driver is bound, including when the VF driver is in C.
This series builds on Peter Colberg's Rust PCI SR-IOV v3 series [1],
extending it with typed PF registration data and C FFI support. The
registration design follows Danilo Krummrich's Rust vGPU/VFIO PoC [2].
As the discussion on Rust/VFIO support is still ongoing [3], this series
supports both C and Rust sample VF drivers.
Add the PCI SR-IOV operations and callback, managed VF removal during PF
unbind, and typed PF registration data. A pinned registration publishes
the PF payload. Rust VFs borrow the typed data directly; C VFs use an
ABI-checked descriptor and generated C-to-Rust trampolines.
Include Rust and C VF samples and integrate the registration into
nova-core. Nova implements sriov_configure() and publishes PF-readiness
data before VFs are enabled. Document the sharing interface, its borrow
lifetimes and teardown ordering.
[1] https://lore.kernel.org/rust-for-linux/20260303-rust-pci-sriov-v3-0-4443c35f0c88@redhat.com/
[2] https://lore.kernel.org/nova-gpu/DLCRZLO06SIO.LS7TWQXIPZSQ@kernel.org/
[3] https://lore.kernel.org/nova-gpu/20260914121217.70fa0d93@shazbot.org/
John Hubbard (1):
rust: pci: add is_virtfn(), to check for VFs
Peter Colberg (7):
PCI: add driver flag to opt into disabling SR-IOV on remove()
rust: pci: add {enable,disable}_sriov(), to control SR-IOV capability
rust: pci: add vtable attribute to pci::Driver trait
rust: pci: add bus callback sriov_configure(), to control SR-IOV from
sysfs
rust: pci: add is_physfn(), to check for PFs
rust: pci: add num_vf(), to return number of VFs
samples: rust: add Rust SR-IOV VF driver sample
Zhi Wang (6):
rust: pci: add typed SR-IOV PF registration data
rust: add C-to-Rust FFI descriptors and trampolines
rust: pci: add C FFI support to typed SR-IOV PF registration data
samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver
gpu: nova-core: publish typed SR-IOV PF data for VF drivers
Documentation: rust: explain SR-IOV PF data sharing with VFs
Documentation/rust/index.rst | 1 +
Documentation/rust/pci-sriov-pf-data.rst | 330 +++++++++++++++++++++
MAINTAINERS | 4 +
drivers/gpu/nova-core/driver.rs | 34 ++-
drivers/pci/iov.c | 103 ++++++-
drivers/pci/pci-driver.c | 3 +-
drivers/pci/pci.h | 2 +
include/linux/pci.h | 44 +++
include/linux/rust_ffi.h | 88 ++++++
rust/bindings/bindings_helper.h | 5 +
rust/kernel/interop.rs | 5 +-
rust/kernel/interop/ffi.rs | 252 ++++++++++++++++
rust/kernel/pci.rs | 126 ++++++++
rust/kernel/pci/sriov.rs | 355 +++++++++++++++++++++++
rust/macros/ffi_vtable.rs | 148 ++++++++++
rust/macros/lib.rs | 90 ++++++
samples/rust/Kconfig | 26 ++
samples/rust/Makefile | 2 +
samples/rust/rust_dma.rs | 1 +
samples/rust/rust_driver_auxiliary.rs | 1 +
samples/rust/rust_driver_pci.rs | 1 +
samples/rust/rust_driver_sriov.h | 27 ++
samples/rust/rust_driver_sriov.rs | 270 +++++++++++++++++
samples/rust/rust_driver_sriov_c_vf.c | 61 ++++
24 files changed, 1972 insertions(+), 7 deletions(-)
create mode 100644 Documentation/rust/pci-sriov-pf-data.rst
create mode 100644 include/linux/rust_ffi.h
create mode 100644 rust/kernel/interop/ffi.rs
create mode 100644 rust/kernel/pci/sriov.rs
create mode 100644 rust/macros/ffi_vtable.rs
create mode 100644 samples/rust/rust_driver_sriov.h
create mode 100644 samples/rust/rust_driver_sriov.rs
create mode 100644 samples/rust/rust_driver_sriov_c_vf.c
base-commit: 34a8ecc902e907d7b0596a8d38b437e90eac0701
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 01/14] PCI: add driver flag to opt into disabling SR-IOV on remove()
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 02/14] rust: pci: add {enable,disable}_sriov(), to control SR-IOV capability Zhi Wang
` (13 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add a flag managed_sriov to the pci_driver structure that allows a
driver to opt into disabling the Single Root I/O Virtualization (SR-IOV)
capability of the device when the driver is unbound.
Add a new function pci_iov_disable() that is invoked before the remove()
callback of a PCI driver and checks for the presence of the new flag.
If the flag is set, invoke the sriov_configure() callback to allow the
driver to gracefully disable SR-IOV. Warn if the driver fails to do so
and forcibly disable SR-IOV using sriov_disable().
Since a (broken) driver may theoretically re-enable SR-IOV during its
remove() callback, extend pci_iov_remove() to forcibly disable SR-IOV
after remove() if needed and only if the flag managed_sriov is set.
Create a persistent managed device link from each VF to its PF before the
VF can probe when managed_sriov is set. The driver core therefore waits
for an in-progress VF probe and unbinds a bound VF before unbinding the PF
driver.
Altogether the flag ensures that when a Virtual Function (VF) is bound
to a driver, the corresponding Physical Function (PF) is bound to a
driver, too, since the VF devices are destroyed when the PF driver is
unbound. This guarantee is a prerequisite for exposing a safe Rust API
that allows a VF driver to obtain the PF device for a VF device and
subsequently access the device private data of the PF device.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
drivers/pci/iov.c | 52 +++++++++++++++++++++++++++++++++++++++-
drivers/pci/pci-driver.c | 3 ++-
drivers/pci/pci.h | 2 ++
include/linux/pci.h | 10 ++++++++
4 files changed, 65 insertions(+), 2 deletions(-)
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index 9d408fb8ac25..ee5eff209e15 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -382,6 +382,17 @@ int pci_iov_add_virtfn(struct pci_dev *dev, int id)
}
pci_device_add(virtfn, virtfn->bus);
+ /*
+ * managed_sriov makes the VF driver binding depend on the PF driver
+ * binding. Add the device link before the VF can probe so supplier
+ * unbind waits for an in-progress probe and unbinds the VF first.
+ */
+ if (dev->driver && dev->driver->managed_sriov &&
+ !device_link_add(&virtfn->dev, &dev->dev, 0)) {
+ pci_err(virtfn, "failed to link VF to managed SR-IOV PF\n");
+ rc = -EINVAL;
+ goto failed1;
+ }
rc = pci_iov_sysfs_link(dev, virtfn, id);
if (rc)
goto failed1;
@@ -1015,20 +1026,59 @@ void pci_iov_release(struct pci_dev *dev)
sriov_release(dev);
}
+/**
+ * pci_iov_disable - disable SR-IOV before PF driver is detached
+ * @dev: the PCI device
+ *
+ * Invoke sriov_configure() callback to allow the driver to gracefully disable
+ * SR-IOV. Warn if the driver fails to do so and forcibly disable SR-IOV.
+ */
+void pci_iov_disable(struct pci_dev *dev)
+{
+ struct pci_driver *drv = dev->driver;
+ struct pci_sriov *iov = dev->sriov;
+
+ if (WARN_ON(!drv))
+ return;
+
+ if (!dev->is_physfn || !iov->num_VFs || !drv->managed_sriov)
+ return;
+
+ if (!drv->sriov_configure) {
+ sriov_disable(dev);
+ return;
+ }
+
+ drv->sriov_configure(dev, 0);
+
+ if (WARN_ON(iov->num_VFs))
+ sriov_disable(dev);
+}
+
/**
* pci_iov_remove - clean up SR-IOV state after PF driver is detached
* @dev: the PCI device
*/
void pci_iov_remove(struct pci_dev *dev)
{
+ struct pci_driver *drv = dev->driver;
struct pci_sriov *iov = dev->sriov;
+ if (WARN_ON(!drv))
+ return;
+
if (!dev->is_physfn)
return;
iov->driver_max_VFs = iov->total_VFs;
- if (iov->num_VFs)
+
+ if (iov->num_VFs && !drv->managed_sriov) {
pci_warn(dev, "driver left SR-IOV enabled after remove\n");
+ return;
+ }
+
+ if (WARN_ON(iov->num_VFs))
+ sriov_disable(dev);
}
/**
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index e16aa59dd7ac..d71d72aeefd2 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -523,6 +523,7 @@ static void pci_device_remove(struct device *dev)
struct pci_dev *pci_dev = to_pci_dev(dev);
struct pci_driver *drv = pci_dev->driver;
+ pci_iov_disable(pci_dev);
if (drv->remove) {
pm_runtime_get_sync(dev);
/*
@@ -536,8 +537,8 @@ static void pci_device_remove(struct device *dev)
pm_runtime_put_noidle(dev);
}
pcibios_free_irq(pci_dev);
- pci_dev->driver = NULL;
pci_iov_remove(pci_dev);
+ pci_dev->driver = NULL;
/* Undo the runtime PM settings in local_pci_probe() */
pm_runtime_put_sync(dev);
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index ba3c3fddddc2..6392da39a5e4 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -986,6 +986,7 @@ static inline void pci_restore_pasid_state(struct pci_dev *pdev) { }
#ifdef CONFIG_PCI_IOV
int pci_iov_init(struct pci_dev *dev);
void pci_iov_release(struct pci_dev *dev);
+void pci_iov_disable(struct pci_dev *dev);
void pci_iov_remove(struct pci_dev *dev);
void pci_iov_update_resource(struct pci_dev *dev, int resno);
resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev,
@@ -1021,6 +1022,7 @@ static inline int pci_iov_init(struct pci_dev *dev)
return -ENODEV;
}
static inline void pci_iov_release(struct pci_dev *dev) { }
+static inline void pci_iov_disable(struct pci_dev *dev) { }
static inline void pci_iov_remove(struct pci_dev *dev) { }
static inline void pci_iov_update_resource(struct pci_dev *dev, int resno) { }
static inline resource_size_t pci_sriov_resource_alignment(const struct pci_dev *dev,
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..bc0d36204940 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -1025,6 +1025,15 @@ struct module;
* how to manage the DMA themselves and set this flag so that
* the IOMMU layer will allow them to setup and manage their
* own I/O address space.
+ * @managed_sriov: Disable SR-IOV on remove().
+ * If set, the Single Root I/O Virtualization (SR-IOV)
+ * capability of the device is disabled when the driver is
+ * unbound from the device, by calling sriov_configure()
+ * before remove(). The PCI core creates a managed device link
+ * from each Virtual Function (VF) to its Physical Function (PF)
+ * before the VF is probed. The presence of this flag therefore
+ * guarantees that when a VF is bound to a driver, the PF is bound
+ * to a driver, too, and that the VF is unbound first.
*/
struct pci_driver {
const char *name;
@@ -1043,6 +1052,7 @@ struct pci_driver {
struct device_driver driver;
struct pci_dynids dynids;
bool driver_managed_dma;
+ bool managed_sriov;
};
#define to_pci_driver(__drv) \
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 02/14] rust: pci: add {enable,disable}_sriov(), to control SR-IOV capability
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
2026-09-15 20:56 ` [PATCH 01/14] PCI: add driver flag to opt into disabling SR-IOV on remove() Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 03/14] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
` (12 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add methods to enable and disable the Single Root I/O Virtualization
(SR-IOV) capability for a PCI device. The wrapped C methods take care
of validating whether the device is a Physical Function (PF), whether
SR-IOV is currently disabled (or enabled), and whether the number of
requested VFs does not exceed the total number of supported VFs.
Set the flag managed_sriov to always disable SR-IOV when a Rust PCI
driver is unbound from a PF device. This ensures that when a Virtual
Function (VF) is bound to a driver, the corresponding Physical Function
(PF) is bound to a driver, too, which is a prerequisite for exposing a
safe Rust API that allows a VF driver to obtain the PF device for a VF
device and subsequently access the private data of the PF driver.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 33 +++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 641f2000eb52..63fd8167e4ac 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -93,6 +93,7 @@ unsafe fn register(
(*pdrv.get()).probe = Some(Self::probe_callback);
(*pdrv.get()).remove = Some(Self::remove_callback);
(*pdrv.get()).id_table = T::ID_TABLE.as_ptr();
+ (*pdrv.get()).managed_sriov = true;
}
// SAFETY: `pdrv` is guaranteed to be a valid `DriverType`.
@@ -506,6 +507,38 @@ pub fn set_master(&self) {
// SAFETY: `self.as_raw` is guaranteed to be a pointer to a valid `struct pci_dev`.
unsafe { bindings::pci_set_master(self.as_raw()) };
}
+
+ /// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
+ /// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
+ #[cfg(CONFIG_PCI_IOV)]
+ pub fn enable_sriov(&self, nr_virtfn: i32) -> Result {
+ // SAFETY:
+ // `self.as_raw` returns a valid pointer to a `struct pci_dev`.
+ //
+ // `pci_enable_sriov()` checks that the enable operation is valid:
+ // - the device is a Physical Function (PF),
+ // - SR-IOV is currently disabled, and
+ // - `nr_virtfn` does not exceed the total number of supported VFs.
+ //
+ // The Core device context inherits from the Bound device context,
+ // which guarantees that the PF device is bound to a driver.
+ to_result(unsafe { bindings::pci_enable_sriov(self.as_raw(), nr_virtfn) })
+ }
+
+ /// Disable the Single Root I/O Virtualization (SR-IOV) capability for this device.
+ #[cfg(CONFIG_PCI_IOV)]
+ pub fn disable_sriov(&self) {
+ // SAFETY:
+ // `self.as_raw` returns a valid pointer to a `struct pci_dev`.
+ //
+ // `pci_disable_sriov()` checks that the disable operation is valid:
+ // - the device is a Physical Function (PF), and
+ // - SR-IOV is currently enabled.
+ //
+ // The Core device context inherits from the Bound device context,
+ // which guarantees that the PF device is bound to a driver.
+ unsafe { bindings::pci_disable_sriov(self.as_raw()) };
+ }
}
// SAFETY: `pci::Device` is a transparent wrapper of `struct pci_dev`.
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 03/14] rust: pci: add vtable attribute to pci::Driver trait
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
2026-09-15 20:56 ` [PATCH 01/14] PCI: add driver flag to opt into disabling SR-IOV on remove() Zhi Wang
2026-09-15 20:56 ` [PATCH 02/14] rust: pci: add {enable,disable}_sriov(), to control SR-IOV capability Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 04/14] rust: pci: add bus callback sriov_configure(), to control SR-IOV from sysfs Zhi Wang
` (11 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add the #[vtable] attribute to pci::Driver trait and implementations,
to prepare a subsequent patch that adds an optional bus callback
sriov_configure() to enable or disable the SR-IOV capability.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
drivers/gpu/nova-core/driver.rs | 1 +
rust/kernel/pci.rs | 2 ++
samples/rust/rust_dma.rs | 1 +
samples/rust/rust_driver_auxiliary.rs | 1 +
samples/rust/rust_driver_pci.rs | 1 +
5 files changed, 6 insertions(+)
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 1f6fe2d97cab..a5b9a50cbaca 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -94,6 +94,7 @@ pub(crate) fn bar1_resource_index(pdev: &pci::Device<Bound>) -> Result<u32> {
]
);
+#[vtable]
impl pci::Driver for NovaCoreDriver {
type IdInfo = ();
type Data<'bound> = NovaCore<'bound>;
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 63fd8167e4ac..24b682d4d6da 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -282,6 +282,7 @@ macro_rules! pci_device_table {
/// ]
/// );
///
+/// #[vtable]
/// impl pci::Driver for MyDriver {
/// type IdInfo = ();
/// type Data<'bound> = Self;
@@ -297,6 +298,7 @@ macro_rules! pci_device_table {
///```
/// Drivers must implement this trait in order to get a PCI driver registered. Please refer to the
/// `Adapter` documentation for an example.
+#[vtable]
pub trait Driver {
/// The type holding information about each device id supported by the driver.
// TODO: Use `associated_type_defaults` once stabilized:
diff --git a/samples/rust/rust_dma.rs b/samples/rust/rust_dma.rs
index ffb693544673..804d55a63005 100644
--- a/samples/rust/rust_dma.rs
+++ b/samples/rust/rust_dma.rs
@@ -69,6 +69,7 @@ unsafe impl kernel::transmute::FromBytes for MyStruct {}
[(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())]
);
+#[vtable]
impl pci::Driver for DmaSampleDriver {
type IdInfo = ();
type Data<'bound> = DmaSampleData<'bound>;
diff --git a/samples/rust/rust_driver_auxiliary.rs b/samples/rust/rust_driver_auxiliary.rs
index 0bee16faecc6..6cff9bf8c80c 100644
--- a/samples/rust/rust_driver_auxiliary.rs
+++ b/samples/rust/rust_driver_auxiliary.rs
@@ -90,6 +90,7 @@ struct ParentData<'bound> {
[(pci::DeviceId::from_id(pci::Vendor::REDHAT, 0x5), ())]
);
+#[vtable]
impl pci::Driver for ParentDriver {
type IdInfo = ();
type Data<'bound> = ParentData<'bound>;
diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs
index 13b035a95756..50c8709739c6 100644
--- a/samples/rust/rust_driver_pci.rs
+++ b/samples/rust/rust_driver_pci.rs
@@ -139,6 +139,7 @@ fn config_space(pdev: &pci::Device<Bound>) {
}
}
+#[vtable]
impl pci::Driver for SampleDriver {
type IdInfo = TestIndex;
type Data<'bound> = SampleDriverData<'bound>;
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 04/14] rust: pci: add bus callback sriov_configure(), to control SR-IOV from sysfs
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (2 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 03/14] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 05/14] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
` (10 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add an optional bus callback sriov_configure() to pci::Driver trait,
using the vtable attribute to query if the driver implements the
callback. The callback is invoked when a user-space application
writes the number of VFs to the sysfs file `sriov_numvfs` to
enable SR-IOV, or zero to disable SR-IOV for a PCI device.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 24b682d4d6da..7f50c1c96a4f 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -94,6 +94,10 @@ unsafe fn register(
(*pdrv.get()).remove = Some(Self::remove_callback);
(*pdrv.get()).id_table = T::ID_TABLE.as_ptr();
(*pdrv.get()).managed_sriov = true;
+ #[cfg(CONFIG_PCI_IOV)]
+ if T::HAS_SRIOV_CONFIGURE {
+ (*pdrv.get()).sriov_configure = Some(Self::sriov_configure_callback);
+ }
}
// SAFETY: `pdrv` is guaranteed to be a valid `DriverType`.
@@ -150,6 +154,20 @@ extern "C" fn remove_callback(pdev: *mut bindings::pci_dev) {
T::unbind(pdev, data);
}
+
+ #[cfg(CONFIG_PCI_IOV)]
+ extern "C" fn sriov_configure_callback(
+ pdev: *mut bindings::pci_dev,
+ nr_virtfn: c_int,
+ ) -> c_int {
+ // SAFETY: The PCI bus only ever calls the sriov_configure callback with a valid pointer to
+ // a `struct pci_dev`.
+ //
+ // INVARIANT: `pdev` is valid for the duration of `sriov_configure_callback()`.
+ let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal<'_>>>() };
+
+ from_result(|| T::sriov_configure(pdev, nr_virtfn))
+ }
}
/// Declares a kernel module that exposes a single PCI driver.
@@ -336,6 +354,44 @@ fn probe<'bound>(
fn unbind<'bound>(dev: &'bound Device<device::Core<'_>>, this: Pin<&Self::Data<'bound>>) {
let _ = (dev, this);
}
+
+ /// Single Root I/O Virtualization (SR-IOV) configure.
+ ///
+ /// Called when a user-space application enables or disables the SR-IOV capability for a
+ /// [`Device`] by writing the number of Virtual Functions (VF), `nr_virtfn` or zero to the
+ /// sysfs file `sriov_numvfs` for this device. Implementing this callback is optional.
+ ///
+ /// Further, and unlike for a PCI driver written in C, when a PF device with enabled VFs is
+ /// unbound from its bound [`Driver`], the `sriov_configure()` callback is invoked to disable
+ /// SR-IOV before the `unbind()` callback. This guarantees that when a VF device is bound to a
+ /// driver, the underlying PF device is bound to a driver, too.
+ ///
+ /// Upon success, this callback must return the number of VFs that were enabled, or zero if
+ /// SR-IOV was disabled.
+ ///
+ /// See [PCI Express I/O Virtualization].
+ ///
+ /// [PCI Express I/O Virtualization]: https://docs.kernel.org/PCI/pci-iov-howto.html
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::{device::Core, pci, prelude::*};
+ /// #[cfg(CONFIG_PCI_IOV)]
+ /// fn sriov_configure(dev: &pci::Device<Core<'_>>, nr_virtfn: i32) -> Result<i32> {
+ /// if nr_virtfn == 0 {
+ /// dev.disable_sriov();
+ /// } else {
+ /// dev.enable_sriov(nr_virtfn)?;
+ /// }
+ /// Ok(nr_virtfn)
+ /// }
+ /// ```
+ #[cfg(CONFIG_PCI_IOV)]
+ fn sriov_configure(dev: &Device<device::Core<'_>>, nr_virtfn: i32) -> Result<i32> {
+ let _ = (dev, nr_virtfn);
+ build_error!(crate::error::VTABLE_DEFAULT_ERROR)
+ }
}
/// The PCI device representation.
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 05/14] rust: pci: add is_virtfn(), to check for VFs
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (3 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 04/14] rust: pci: add bus callback sriov_configure(), to control SR-IOV from sysfs Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 06/14] rust: pci: add is_physfn(), to check for PFs Zhi Wang
` (9 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Alistair Popple,
Joel Fernandes, Peter Colberg, Zhi Wang
From: John Hubbard <jhubbard@nvidia.com>
Add a method to check if a PCI device is a Virtual Function (VF) created
through Single Root I/O Virtualization (SR-IOV).
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Reviewed-by: Alistair Popple <apopple@nvidia.com>
Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 7f50c1c96a4f..844d73a83670 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -507,6 +507,14 @@ pub fn resource_start(&self, bar: u32) -> Result<bindings::resource_size_t> {
Ok(unsafe { bindings::pci_resource_start(self.as_raw(), bar.try_into()?) })
}
+ /// Returns `true` if this device is a Virtual Function (VF).
+ #[inline]
+ #[expect(dead_code)]
+ pub(crate) fn is_virtfn(&self) -> bool {
+ // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
+ unsafe { (*self.as_raw()).is_virtfn() != 0 }
+ }
+
/// Returns the size of the given PCI BAR resource.
pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
if !Bar::index_is_valid(bar) {
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 06/14] rust: pci: add is_physfn(), to check for PFs
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (4 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 05/14] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 07/14] rust: pci: add num_vf(), to return number of VFs Zhi Wang
` (8 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Joel Fernandes, Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add a method to check if a PCI device is a Physical Function (PF).
zhiw: Keep this role check private to the PCI SR-IOV implementation
instead of extending the public `Device` API. Add a dead-code
expectation because this standalone API commit has no in-tree caller.
Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com>
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 844d73a83670..ed42511911d7 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -507,6 +507,14 @@ pub fn resource_start(&self, bar: u32) -> Result<bindings::resource_size_t> {
Ok(unsafe { bindings::pci_resource_start(self.as_raw(), bar.try_into()?) })
}
+ /// Returns `true` if this device is a Physical Function (PF).
+ #[inline]
+ #[expect(dead_code)]
+ pub(crate) fn is_physfn(&self) -> bool {
+ // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
+ unsafe { (*self.as_raw()).is_physfn() != 0 }
+ }
+
/// Returns `true` if this device is a Virtual Function (VF).
#[inline]
#[expect(dead_code)]
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 07/14] rust: pci: add num_vf(), to return number of VFs
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (5 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 06/14] rust: pci: add is_physfn(), to check for PFs Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 08/14] rust: pci: add typed SR-IOV PF registration data Zhi Wang
` (7 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add a method to return the number of Virtual Functions (VF) enabled for
a Physical Function (PF).
zhiw: Keep this helper private to the PCI SR-IOV implementation instead
of extending the public `Device` API. Add a dead-code expectation
because this standalone API commit has no in-tree caller.
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index ed42511911d7..f2d71daef83a 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -523,6 +523,14 @@ pub(crate) fn is_virtfn(&self) -> bool {
unsafe { (*self.as_raw()).is_virtfn() != 0 }
}
+ /// Returns the number of Virtual Functions (VF) enabled for a Physical Function (PF).
+ #[cfg(CONFIG_PCI_IOV)]
+ #[expect(dead_code)]
+ pub(crate) fn num_vf(&self) -> i32 {
+ // SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
+ unsafe { bindings::pci_num_vf(self.as_raw()) }
+ }
+
/// Returns the size of the given PCI BAR resource.
pub fn resource_len(&self, bar: u32) -> Result<bindings::resource_size_t> {
if !Bar::index_is_valid(bar) {
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 08/14] rust: pci: add typed SR-IOV PF registration data
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (6 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 07/14] rust: pci: add num_vf(), to return number of VFs Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 09/14] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
` (6 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
A VF driver may need to invoke PF-owned functionality without gaining
access to all private data belonging to the PF driver. The PCI device
alone cannot identify the restricted data type or express how long a
borrow remains valid.
Add a Rust registration slot to `struct pci_dev` and `VfRegistration`
to publish a `ForLt`-encoded payload. The registration is initialized
and pinned inline in the PF driver data, then publishes its address as
the final step of its own initialization. It remains inactive on
conventional PCI functions and rejects VFs.
Check that no VFs or other registration exist before publication. Pinned
drop disables SR-IOV before withdrawing the pointer and dropping the
payload. Managed SR-IOV ordering additionally removes every VF before
PF unbind.
Add `vf_registration_data_with()` for higher-ranked access and
`vf_registration_data()` for covariant data. `TypeId` rejects mismatched
Rust types without exposing the PF device or its complete driver data.
Keep SR-IOV operations behind a verified `pci::sriov::Device` view and
pass a pinned reference to the PCI driver data into `sriov_configure()`.
PF and VF consumers continue to use the ordinary `pci::Driver`
abstraction.
Expose `is_virtfn()` so PF drivers can reject VFs before initializing
hardware and publishing their registration.
Co-developed-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
include/linux/pci.h | 6 +
rust/kernel/pci.rs | 69 +++++----
rust/kernel/pci/sriov.rs | 299 +++++++++++++++++++++++++++++++++++++++
3 files changed, 345 insertions(+), 29 deletions(-)
create mode 100644 rust/kernel/pci/sriov.rs
diff --git a/include/linux/pci.h b/include/linux/pci.h
index bc0d36204940..1ccc7fee7495 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -352,6 +352,9 @@ struct rcec_ea;
* Such bridges are allocated additional MMIO and bus
* number resources to allow for hierarchy expansion.
* @is_pciehp: PCIe Hot-Plug Capable bridge.
+ * @vf_registration_data_rust: Rust registration data published by the PF
+ * before enabling VFs and retained until all VFs are
+ * removed. The PF driver must use managed_sriov.
*/
struct pci_dev {
struct list_head bus_list; /* Node in per-bus list */
@@ -551,6 +554,9 @@ struct pci_dev {
u16 ats_cap; /* ATS Capability offset */
u8 ats_stu; /* ATS Smallest Translation Unit */
#endif
+#if defined(CONFIG_PCI_IOV) && defined(CONFIG_RUST)
+ void *vf_registration_data_rust;
+#endif
#ifdef CONFIG_PCI_PRI
u16 pri_cap; /* PRI Capability offset */
u32 pri_reqs_alloc; /* Number of PRI requests allocated */
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index f2d71daef83a..46edc8007d78 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -37,6 +37,8 @@
mod id;
mod io;
mod irq;
+#[cfg(CONFIG_PCI_IOV)]
+pub mod sriov;
pub use self::cap::{
ExtCapId,
@@ -64,6 +66,8 @@
IrqVector,
IrqVectorRegistration, //
};
+#[cfg(CONFIG_PCI_IOV)]
+pub use self::sriov::VfRegistration;
/// An adapter for the registration of PCI drivers.
pub struct Adapter<T: Driver>(T);
@@ -160,13 +164,18 @@ extern "C" fn sriov_configure_callback(
pdev: *mut bindings::pci_dev,
nr_virtfn: c_int,
) -> c_int {
- // SAFETY: The PCI bus only ever calls the sriov_configure callback with a valid pointer to
- // a `struct pci_dev`.
- //
- // INVARIANT: `pdev` is valid for the duration of `sriov_configure_callback()`.
+ // SAFETY: The PCI bus invokes this callback with a valid device bound to this driver. The
+ // `CoreInternal` context is valid for the callback's duration.
let pdev = unsafe { &*pdev.cast::<Device<device::CoreInternal<'_>>>() };
- from_result(|| T::sriov_configure(pdev, nr_virtfn))
+ // SAFETY: `sriov_configure` is called only after a successful probe and before unbind, so
+ // the stored pointer has type `T::Data<'_>` and remains valid throughout this callback.
+ let data = unsafe { pdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
+
+ from_result(|| {
+ let dev = sriov::Device::try_from_pci(pdev)?;
+ T::sriov_configure(dev, data, nr_virtfn)
+ })
}
}
@@ -355,41 +364,44 @@ fn unbind<'bound>(dev: &'bound Device<device::Core<'_>>, this: Pin<&Self::Data<'
let _ = (dev, this);
}
- /// Single Root I/O Virtualization (SR-IOV) configure.
+ /// Configures Single Root I/O Virtualization (SR-IOV) for a Physical Function (PF).
///
- /// Called when a user-space application enables or disables the SR-IOV capability for a
- /// [`Device`] by writing the number of Virtual Functions (VF), `nr_virtfn` or zero to the
- /// sysfs file `sriov_numvfs` for this device. Implementing this callback is optional.
+ /// The PCI core invokes this callback when userspace writes the number of Virtual Functions
+ /// (VFs), or zero, to the PF's `sriov_numvfs` sysfs file. For managed SR-IOV it is also called
+ /// with zero before [`Self::unbind`] when the PF still has enabled VFs.
///
- /// Further, and unlike for a PCI driver written in C, when a PF device with enabled VFs is
- /// unbound from its bound [`Driver`], the `sriov_configure()` callback is invoked to disable
- /// SR-IOV before the `unbind()` callback. This guarantees that when a VF device is bound to a
- /// driver, the underlying PF device is bound to a driver, too.
+ /// `dev` is a verified SR-IOV PF in the [`device::Core`] callback context. It can be converted
+ /// to the underlying PCI device through [`sriov::Device::as_pci`]. `this` is the private data
+ /// returned by [`Self::probe`]. Both remain valid for the duration of the callback.
///
- /// Upon success, this callback must return the number of VFs that were enabled, or zero if
- /// SR-IOV was disabled.
- ///
- /// See [PCI Express I/O Virtualization].
- ///
- /// [PCI Express I/O Virtualization]: https://docs.kernel.org/PCI/pci-iov-howto.html
+ /// Upon success, return the number of VFs that were enabled, or zero if SR-IOV was disabled.
///
/// # Examples
///
/// ```
/// # use kernel::{device::Core, pci, prelude::*};
- /// #[cfg(CONFIG_PCI_IOV)]
- /// fn sriov_configure(dev: &pci::Device<Core<'_>>, nr_virtfn: i32) -> Result<i32> {
+ /// # struct Data;
+ /// fn sriov_configure(
+ /// dev: &pci::sriov::Device<Core<'_>>,
+ /// _this: Pin<&Data>,
+ /// nr_virtfn: i32,
+ /// ) -> Result<i32> {
/// if nr_virtfn == 0 {
/// dev.disable_sriov();
/// } else {
/// dev.enable_sriov(nr_virtfn)?;
/// }
+ ///
/// Ok(nr_virtfn)
/// }
/// ```
#[cfg(CONFIG_PCI_IOV)]
- fn sriov_configure(dev: &Device<device::Core<'_>>, nr_virtfn: i32) -> Result<i32> {
- let _ = (dev, nr_virtfn);
+ fn sriov_configure<'bound>(
+ dev: &'bound sriov::Device<device::Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ nr_virtfn: i32,
+ ) -> Result<i32> {
+ let _ = (dev, this, nr_virtfn);
build_error!(crate::error::VTABLE_DEFAULT_ERROR)
}
}
@@ -508,24 +520,23 @@ pub fn resource_start(&self, bar: u32) -> Result<bindings::resource_size_t> {
}
/// Returns `true` if this device is a Physical Function (PF).
+ #[cfg(CONFIG_PCI_IOV)]
#[inline]
- #[expect(dead_code)]
pub(crate) fn is_physfn(&self) -> bool {
// SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
unsafe { (*self.as_raw()).is_physfn() != 0 }
}
/// Returns `true` if this device is a Virtual Function (VF).
+ #[cfg(CONFIG_PCI_IOV)]
#[inline]
- #[expect(dead_code)]
- pub(crate) fn is_virtfn(&self) -> bool {
+ pub fn is_virtfn(&self) -> bool {
// SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
unsafe { (*self.as_raw()).is_virtfn() != 0 }
}
/// Returns the number of Virtual Functions (VF) enabled for a Physical Function (PF).
#[cfg(CONFIG_PCI_IOV)]
- #[expect(dead_code)]
pub(crate) fn num_vf(&self) -> i32 {
// SAFETY: `self.as_raw` is a valid pointer to a `struct pci_dev`.
unsafe { bindings::pci_num_vf(self.as_raw()) }
@@ -593,7 +604,7 @@ pub fn set_master(&self) {
/// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
/// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
#[cfg(CONFIG_PCI_IOV)]
- pub fn enable_sriov(&self, nr_virtfn: i32) -> Result {
+ pub(crate) fn enable_sriov(&self, nr_virtfn: i32) -> Result {
// SAFETY:
// `self.as_raw` returns a valid pointer to a `struct pci_dev`.
//
@@ -609,7 +620,7 @@ pub fn enable_sriov(&self, nr_virtfn: i32) -> Result {
/// Disable the Single Root I/O Virtualization (SR-IOV) capability for this device.
#[cfg(CONFIG_PCI_IOV)]
- pub fn disable_sriov(&self) {
+ pub(crate) fn disable_sriov(&self) {
// SAFETY:
// `self.as_raw` returns a valid pointer to a `struct pci_dev`.
//
diff --git a/rust/kernel/pci/sriov.rs b/rust/kernel/pci/sriov.rs
new file mode 100644
index 000000000000..efbe444e0733
--- /dev/null
+++ b/rust/kernel/pci/sriov.rs
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for PCI Single Root I/O Virtualization (SR-IOV) drivers.
+
+use super::Device as PciDevice;
+use crate::{
+ bindings,
+ device, //
+ prelude::*,
+ types::{
+ CovariantForLt,
+ ForLt, //
+ },
+};
+use core::{
+ any::TypeId,
+ marker::PhantomPinned,
+ num::NonZero, //
+};
+
+/// A PCI Physical Function (PF) with an SR-IOV capability.
+///
+/// This capability view is created only after the PCI abstraction verifies that the device is an
+/// SR-IOV PF. Its device context follows the same hierarchy as [`PciDevice`].
+#[repr(transparent)]
+pub struct Device<Ctx: device::DeviceContext = device::Normal>(PciDevice<Ctx>);
+
+impl<Ctx: device::DeviceContext> Device<Ctx> {
+ pub(super) fn try_from_pci(pdev: &PciDevice<Ctx>) -> Result<&Self> {
+ // SAFETY: `pdev.as_raw()` is a valid pointer to a `struct pci_dev`.
+ if unsafe { (*pdev.as_raw()).is_physfn() == 0 } {
+ return Err(ENODEV);
+ }
+
+ // CAST: `Device` is a transparent capability view of `PciDevice` with the same context.
+ // SAFETY: The check above establishes the PF invariant, and the returned reference cannot
+ // outlive `pdev`.
+ Ok(unsafe { &*core::ptr::from_ref(pdev).cast() })
+ }
+
+ /// Returns the underlying PCI device with the same device context.
+ #[inline]
+ pub fn as_pci(&self) -> &PciDevice<Ctx> {
+ &self.0
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<PciDevice<Ctx>> for Device<Ctx> {
+ #[inline]
+ fn as_ref(&self) -> &PciDevice<Ctx> {
+ self.as_pci()
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<device::Device<Ctx>> for Device<Ctx> {
+ #[inline]
+ fn as_ref(&self) -> &device::Device<Ctx> {
+ self.as_pci().as_ref()
+ }
+}
+
+impl<'a> Device<device::Core<'a>> {
+ /// Returns the total number of VFs, or [`None`] if SR-IOV is unavailable.
+ #[inline]
+ pub fn total_vfs(&self) -> Option<NonZero<u16>> {
+ self.as_pci().sriov_get_totalvfs()
+ }
+
+ /// Enables `nr_virtfn` Virtual Functions (VFs).
+ #[inline]
+ pub fn enable_sriov(&self, nr_virtfn: i32) -> Result {
+ self.as_pci().enable_sriov(nr_virtfn)
+ }
+
+ /// Disables all Virtual Functions (VFs).
+ #[inline]
+ pub fn disable_sriov(&self) {
+ self.as_pci().disable_sriov();
+ }
+}
+
+impl Device<device::Bound> {
+ /// Returns the number of currently enabled Virtual Functions (VFs).
+ #[inline]
+ pub fn num_vfs(&self) -> i32 {
+ self.as_pci().num_vf()
+ }
+}
+
+// SAFETY: `Device` is a transparent wrapper around `PciDevice`, and neither type's layout
+// depends on its device context.
+kernel::impl_device_context_deref!(unsafe { Device });
+
+#[repr(C)]
+#[pin_data]
+struct VfRegistrationData<'a, F: ForLt + 'static> {
+ type_id: TypeId,
+ #[pin]
+ data: F::Of<'a>,
+}
+
+static_assert!(
+ core::mem::offset_of!(VfRegistrationData<'static, CovariantForLt!(())>, type_id) == 0
+);
+
+impl<'a, F: ForLt + 'static> VfRegistrationData<'a, F> {
+ fn new<D>(data: D) -> impl PinInit<Self, Error> + use<'a, D, F>
+ where
+ D: PinInit<F::Of<'a>, Error> + 'a,
+ {
+ try_pin_init!(Self {
+ type_id: TypeId::of::<F>(),
+ data <- data,
+ })
+ }
+}
+
+/// Typed data published by a Physical Function (PF) for its Virtual Functions (VFs).
+///
+/// The registration is initialized in place as part of the PF driver's pinned data. On an SR-IOV
+/// PF it publishes the inline payload after initialization; on a conventional PCI function it is
+/// inactive. A VF is rejected. Bound VFs access the payload through
+/// [`PciDevice::vf_registration_data()`] or [`PciDevice::vf_registration_data_with()`].
+///
+/// Managed SR-IOV removes all VFs before the PF driver is unbound. As a fallback, pinned drop also
+/// disables SR-IOV before withdrawing the payload.
+#[pin_data(PinnedDrop)]
+pub struct VfRegistration<'a, F: ForLt + 'static> {
+ pdev: &'a PciDevice<device::Bound>,
+ #[pin]
+ inner: VfRegistrationData<'a, F>,
+ published: bool,
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+impl<'a, F: ForLt + 'static> VfRegistration<'a, F>
+where
+ for<'b> F::Of<'b>: Send + Sync,
+{
+ /// Publishes typed PF data for bound VF drivers.
+ ///
+ /// This returns a pin-initializer so the registration and payload can be embedded directly in
+ /// the PF driver's pinned data.
+ ///
+ /// Initialization returns [`ENODEV`] for a VF and [`EBUSY`] if the PF has enabled VFs or
+ /// already has a registration.
+ ///
+ /// # Safety
+ ///
+ /// The caller must invoke this during the PCI driver's probe and embed the result in the driver
+ /// data. On an SR-IOV PF, no VF may be enabled before probe successfully installs the complete
+ /// driver data, and the driver must use managed SR-IOV. The registration must be dropped before
+ /// anything its payload borrows and must not be forgotten. Probe must have exclusive access to
+ /// the PF registration slot. On a conventional PCI function, the registration remains
+ /// inactive.
+ pub unsafe fn new<'core, D>(
+ pdev: &'a PciDevice<device::Core<'core>>,
+ data: D,
+ ) -> impl PinInit<Self, Error> + use<'a, 'core, D, F>
+ where
+ D: PinInit<F::Of<'a>, Error> + 'a,
+ {
+ pin_init::pin_init_scope(move || {
+ if pdev.is_virtfn() {
+ return Err(ENODEV);
+ }
+
+ let published = pdev.is_physfn();
+ if published {
+ if pdev.num_vf() != 0 {
+ return Err(EBUSY);
+ }
+
+ if !pdev.vf_registration_data_rust().is_null() {
+ return Err(EBUSY);
+ }
+ }
+
+ Ok(try_pin_init!(Self {
+ pdev,
+ inner <- VfRegistrationData::new(data),
+ published,
+ _pin: PhantomPinned,
+ _: {
+ if *published {
+ pdev.set_vf_registration_data_rust(
+ core::ptr::from_ref(inner.as_ref().get_ref()).cast_mut().cast(),
+ );
+ }
+ },
+ }))
+ })
+ }
+}
+
+#[pinned_drop]
+impl<F: ForLt + 'static> PinnedDrop for VfRegistration<'_, F> {
+ fn drop(self: Pin<&mut Self>) {
+ if !self.published {
+ return;
+ }
+
+ // SAFETY: `self.pdev` is the PF on which this registration was published. The call is a
+ // no-op on the normal managed-SR-IOV teardown path, where all VFs are already disabled.
+ unsafe { bindings::pci_disable_sriov(self.pdev.as_raw()) };
+ self.pdev
+ .set_vf_registration_data_rust(core::ptr::null_mut());
+ }
+}
+
+// SAFETY: The registration and its inline payload may be released from another thread after the
+// PCI core has removed all VFs.
+unsafe impl<F: ForLt> Send for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send {}
+
+// SAFETY: VF consumers receive shared references only, and the payload supports shared access.
+unsafe impl<F: ForLt> Sync for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send + Sync {}
+
+impl<Ctx: device::DeviceContext> PciDevice<Ctx> {
+ fn vf_registration_data_rust(&self) -> *mut core::ffi::c_void {
+ // SAFETY: `self.as_raw()` is a valid pointer to a `struct pci_dev`.
+ unsafe { (*self.as_raw()).vf_registration_data_rust }
+ }
+
+ fn set_vf_registration_data_rust(&self, data: *mut core::ffi::c_void) {
+ // SAFETY: Publication and withdrawal are serialized by PCI probe and managed teardown.
+ unsafe { (*self.as_raw()).vf_registration_data_rust = data };
+ }
+}
+
+impl PciDevice<device::Bound> {
+ /// # Safety
+ ///
+ /// The returned borrow must be confined by a closure higher-ranked independently over its
+ /// borrow and data lifetimes, or `F` must be covariant in its encoded lifetime.
+ unsafe fn vf_registration_data_pinned<F: ForLt + 'static>(&self) -> Result<Pin<&F::Of<'_>>> {
+ if !self.is_virtfn() {
+ return Err(ENODEV);
+ }
+
+ // SAFETY: A VF's `physfn` pointer remains valid for the VF's lifetime. Managed SR-IOV also
+ // keeps the PF driver bound until this VF is unbound.
+ let pf_dev = unsafe { (*self.as_raw()).__bindgen_anon_1.physfn };
+ if pf_dev.is_null() {
+ return Err(ENODEV);
+ }
+
+ // SAFETY: The PF cannot withdraw the pointer until managed teardown has removed this VF.
+ let ptr = unsafe { (*pf_dev).vf_registration_data_rust };
+ if ptr.is_null() {
+ return Err(ENOENT);
+ }
+
+ // SAFETY: The published pointer addresses a `VfRegistrationData`, whose first field is a
+ // `TypeId`.
+ let type_id = unsafe { ptr.cast::<TypeId>().read() };
+ if type_id != TypeId::of::<F>() {
+ return Err(EINVAL);
+ }
+
+ // SAFETY: The type check identifies `F`; lifetime parameters do not affect layout, and the
+ // inline data remains pinned for this VF borrow.
+ let data = unsafe {
+ let registration = ptr.cast::<VfRegistrationData<'_, F>>();
+ &raw const (*registration).data
+ };
+
+ // SAFETY: `data` is structurally pinned in the PF driver's pinned registration.
+ Ok(unsafe { Pin::new_unchecked(&*data) })
+ }
+
+ /// Accesses typed data published by this VF's PF through a closure.
+ ///
+ /// Returns [`ENODEV`] if this device is not a VF, [`ENOENT`] if its PF has not published
+ /// data, or [`EINVAL`] if the registered type does not match `F`.
+ ///
+ /// The closure's borrow and the registration data's lifetime are independent, so a borrow of
+ /// the context cannot be stored in invariant registration data.
+ pub fn vf_registration_data_with<F: ForLt + 'static, R>(
+ &self,
+ f: impl for<'borrow, 'data> FnOnce(Pin<&'borrow F::Of<'data>>) -> R,
+ ) -> Result<R> {
+ // SAFETY: The higher-ranked closure prevents the borrow from escaping or being stored in
+ // invariant data by keeping its lifetime independent of the erased data lifetime.
+ let data = unsafe { self.vf_registration_data_pinned::<F>()? };
+ Ok(f(data))
+ }
+
+ /// Returns typed data published by this VF's PF.
+ ///
+ /// This direct accessor is available only when the encoded data is covariant in its lifetime.
+ /// Use [`Self::vf_registration_data_with()`] for invariant data.
+ ///
+ /// It returns the same errors as [`Self::vf_registration_data_with()`].
+ pub fn vf_registration_data<F: CovariantForLt + 'static>(&self) -> Result<Pin<&F::Of<'_>>> {
+ // SAFETY: `CovariantForLt` permits shortening the encoded lifetime to this borrow.
+ unsafe { self.vf_registration_data_pinned::<F>() }
+ }
+}
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 09/14] samples: rust: add Rust SR-IOV VF driver sample
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (7 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 08/14] rust: pci: add typed SR-IOV PF registration data Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 10/14] rust: add C-to-Rust FFI descriptors and trampolines Zhi Wang
` (5 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Peter Colberg,
Zhi Wang
From: Peter Colberg <pcolberg@redhat.com>
Add a new SR-IOV driver sample that demonstrates how to enable and
disable the Single Root I/O Virtualization capability for a PCI device.
The sample may be exercised using QEMU's 82576 (igb) emulation.
Implement the PF and VF as ordinary PCI drivers. Publish pinned PF data
with a mutex-protected counter inline in the PF driver data, then borrow
and invoke it explicitly from VF probe. Register the VF driver first so
it is ready before the PF can enable VFs.
Link: https://www.qemu.org/docs/master/system/devices/igb.html
Signed-off-by: Peter Colberg <pcolberg@redhat.com>
Co-developed-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
MAINTAINERS | 1 +
samples/rust/Kconfig | 11 ++
samples/rust/Makefile | 1 +
samples/rust/rust_driver_sriov.rs | 241 ++++++++++++++++++++++++++++++
4 files changed, 254 insertions(+)
create mode 100644 samples/rust/rust_driver_sriov.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 2389df80ba50..5e168398e963 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21141,6 +21141,7 @@ F: rust/helpers/pci.c
F: rust/kernel/pci.rs
F: rust/kernel/pci/
F: samples/rust/rust_driver_pci.rs
+F: samples/rust/rust_driver_sriov.rs
PCIE BANDWIDTH CONTROLLER
M: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 31d62533ef25..737670fd68f8 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -128,6 +128,17 @@ config SAMPLE_RUST_DRIVER_PLATFORM
If unsure, say N.
+config SAMPLE_RUST_DRIVER_SRIOV
+ tristate "SR-IOV Driver"
+ depends on PCI_IOV
+ help
+ This option builds the Rust SR-IOV driver sample.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_driver_sriov.
+
+ If unsure, say N.
+
config SAMPLE_RUST_DRIVER_USB
tristate "USB Driver"
depends on USB = y
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index b986b681cde5..238a11d5ec39 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C) += rust_driver_i2c.o
obj-$(CONFIG_SAMPLE_RUST_I2C_CLIENT) += rust_i2c_client.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_SRIOV) += rust_driver_sriov.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) += rust_driver_auxiliary.o
diff --git a/samples/rust/rust_driver_sriov.rs b/samples/rust/rust_driver_sriov.rs
new file mode 100644
index 000000000000..ae1e1babb66b
--- /dev/null
+++ b/samples/rust/rust_driver_sriov.rs
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust SR-IOV driver sample based on QEMU's 82576 ([igb]) emulation.
+//!
+//! To make this driver probe, QEMU must be run with `-device igb`.
+//!
+//! Further, enable [vIOMMU] with interrupt remapping using, e.g.,
+//!
+//! `-M q35,accel=kvm,kernel-irqchip=split -device intel-iommu,intremap=on,caching-mode=on`
+//!
+//! and append `intel_iommu=on` to the guest kernel arguments.
+//!
+//! [igb]: https://www.qemu.org/docs/master/system/devices/igb.html
+//! [vIOMMU]: https://wiki.qemu.org/Features/VT-d
+
+use kernel::{
+ device::{
+ Bound,
+ Core, //
+ },
+ driver,
+ new_mutex,
+ pci,
+ prelude::*,
+ sync::{
+ aref::ARef,
+ Mutex, //
+ },
+ types::CovariantForLt,
+ InPlaceModule, //
+};
+
+const PF_DRIVER_NAME: &CStr = c"rust_driver_sriov_pf";
+const VF_DRIVER_NAME: &CStr = c"rust_driver_sriov_vf";
+
+struct SamplePfDriver;
+struct SampleVfDriver;
+
+#[pin_data]
+struct PfApi<'bound> {
+ pdev: &'bound pci::Device<Bound>,
+ #[pin]
+ requests: Mutex<u64>,
+}
+
+type PfApiForLt = CovariantForLt!(PfApi<'_>);
+
+impl PfApi<'_> {
+ fn submit(self: Pin<&Self>, vf: &pci::Device<Bound>) -> Result<u64> {
+ let mut requests = self.requests.lock();
+ let request = (*requests).checked_add(1).ok_or(EOVERFLOW)?;
+ *requests = request;
+ drop(requests);
+
+ dev_info!(
+ self.pdev,
+ "Handle PF request {} from VF devfn {:#x}.\n",
+ request,
+ vf.dev_id()
+ );
+
+ Ok(request)
+ }
+}
+
+#[pin_data(PinnedDrop)]
+struct PfDriverData<'bound> {
+ // Keep the device alive until the registration stops exposing `PfApi::pdev`.
+ #[pin]
+ _registration: pci::VfRegistration<'bound, PfApiForLt>,
+ pdev: ARef<pci::Device>,
+}
+
+#[pin_data(PinnedDrop)]
+struct VfDriverData {
+ pdev: ARef<pci::Device>,
+}
+
+kernel::pci_device_table!(
+ PF_TABLE,
+ <SamplePfDriver as pci::Driver>::IdInfo,
+ [(
+ // E1000_DEV_ID_82576
+ pci::DeviceId::from_id(pci::Vendor::INTEL, 0x10c9),
+ ()
+ )]
+);
+
+kernel::pci_device_table!(
+ VF_TABLE,
+ <SampleVfDriver as pci::Driver>::IdInfo,
+ [(
+ // E1000_DEV_ID_82576_VF
+ pci::DeviceId::from_id(pci::Vendor::INTEL, 0x10ca),
+ ()
+ )]
+);
+
+#[vtable]
+impl pci::Driver for SamplePfDriver {
+ type IdInfo = ();
+ type Data<'bound> = PfDriverData<'bound>;
+
+ const ID_TABLE: pci::IdTable<Self::IdInfo> = &PF_TABLE;
+
+ fn probe<'bound>(
+ pdev: &'bound pci::Device<Core<'_>>,
+ _info: Option<&'bound Self::IdInfo>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ pin_init::pin_init_scope(move || {
+ dev_info!(
+ pdev,
+ "Probe Rust SR-IOV PF sample (PCI ID: {}, 0x{:x}).\n",
+ pdev.vendor_id(),
+ pdev.device_id()
+ );
+
+ pdev.enable_device_mem()?;
+ pdev.set_master();
+
+ Ok(try_pin_init!(PfDriverData {
+ // SAFETY:
+ // - probe has exclusive access to this PF before SR-IOV is enabled;
+ // - the registration is pinned in the PF driver data and dropped before `pdev`;
+ // - no other registration is created for this PF; and
+ // - VFs are enabled only after probe by `sriov_configure`.
+ _registration <- unsafe {
+ pci::VfRegistration::new(
+ pdev,
+ try_pin_init!(PfApi {
+ pdev,
+ requests <- new_mutex!(0),
+ }),
+ )
+ },
+ pdev: pdev.into(),
+ }))
+ })
+ }
+
+ fn sriov_configure<'bound>(
+ dev: &'bound pci::sriov::Device<Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ nr_virtfn: i32,
+ ) -> Result<i32> {
+ if nr_virtfn == 0 {
+ dev_info!(
+ this.pdev,
+ "Disable SR-IOV (PCI ID: {}, 0x{:x}).\n",
+ this.pdev.vendor_id(),
+ this.pdev.device_id()
+ );
+ dev.disable_sriov();
+ } else {
+ dev_info!(
+ this.pdev,
+ "Enable SR-IOV (PCI ID: {}, 0x{:x}).\n",
+ this.pdev.vendor_id(),
+ this.pdev.device_id()
+ );
+ dev.enable_sriov(nr_virtfn)?;
+ }
+
+ assert_eq!(dev.num_vfs(), nr_virtfn);
+ Ok(nr_virtfn)
+ }
+}
+
+#[vtable]
+impl pci::Driver for SampleVfDriver {
+ type IdInfo = ();
+ type Data<'bound> = VfDriverData;
+
+ const ID_TABLE: pci::IdTable<Self::IdInfo> = &VF_TABLE;
+
+ fn probe<'bound>(
+ pdev: &'bound pci::Device<Core<'_>>,
+ _info: Option<&'bound Self::IdInfo>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ pin_init::pin_init_scope(move || {
+ dev_info!(
+ pdev,
+ "Probe Rust SR-IOV VF sample (PCI ID: {}, 0x{:x}).\n",
+ pdev.vendor_id(),
+ pdev.device_id()
+ );
+
+ let pdev_bound: &'bound pci::Device<Bound> = pdev;
+ let pf_api = pdev_bound.vf_registration_data::<PfApiForLt>()?;
+
+ pdev.enable_device_mem()?;
+ pdev.set_master();
+
+ let request = pf_api.submit(pdev)?;
+ dev_info!(pdev, "Submitted request {} through PF data.\n", request);
+
+ Ok(try_pin_init!(VfDriverData { pdev: pdev.into() }))
+ })
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for PfDriverData<'_> {
+ fn drop(self: Pin<&mut Self>) {
+ dev_info!(self.pdev, "Remove Rust SR-IOV PF sample.\n");
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for VfDriverData {
+ fn drop(self: Pin<&mut Self>) {
+ dev_info!(self.pdev, "Remove Rust SR-IOV VF sample.\n");
+ }
+}
+
+#[pin_data]
+struct SampleModule {
+ // Keep the VF driver registered while PF removal tears down its VFs.
+ #[pin]
+ _pf: driver::Registration<pci::Adapter<SamplePfDriver>>,
+ #[pin]
+ _vf: driver::Registration<pci::Adapter<SampleVfDriver>>,
+}
+
+impl InPlaceModule for SampleModule {
+ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ // The VF driver must be ready before the PF can enable VFs.
+ _vf <- driver::Registration::new(VF_DRIVER_NAME, module),
+ _pf <- driver::Registration::new(PF_DRIVER_NAME, module),
+ })
+ }
+}
+
+module! {
+ type: SampleModule,
+ name: "rust_driver_sriov",
+ authors: ["Peter Colberg"],
+ description: "Rust SR-IOV driver",
+ license: "GPL v2",
+}
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 10/14] rust: add C-to-Rust FFI descriptors and trampolines
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (8 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 09/14] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 11/14] rust: pci: add C FFI support to typed SR-IOV PF registration data Zhi Wang
` (4 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
Rust drivers may need to expose a restricted operations table to C
consumers. Passing an opaque Rust pointer alone neither identifies the
expected operations-table ABI nor provides type-checked C-compatible
trampolines.
Add `struct rust_ffi` with a stable 128-bit ABI token, version fields, an
operations-table size, and an opaque pinned context. Add
`rust_ffi_borrow()` to validate those fields independently of the
transport that publishes the descriptor. The token is an ABI type tag
and does not provide device identity, authorization, or lifetime
management.
Add matching Rust `Token`, `Abi`, and `Descriptor` abstractions and an
`ffi_vtable` procedural macro. The macro checks the complete bindgen
operations-table layout while generating private C ABI trampolines that
recover a `Pin<&T>`. A sealed return conversion keeps raw return values
unchanged and maps `Result<()>` and `Result<c_int>` to conventional C
integer results. The publishing transport remains responsible for
keeping the descriptor and context alive and pinned until every consumer
has stopped calling it.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
MAINTAINERS | 2 +
include/linux/rust_ffi.h | 88 ++++++++++++
rust/bindings/bindings_helper.h | 1 +
rust/kernel/interop.rs | 5 +-
rust/kernel/interop/ffi.rs | 239 ++++++++++++++++++++++++++++++++
rust/macros/ffi_vtable.rs | 148 ++++++++++++++++++++
rust/macros/lib.rs | 90 ++++++++++++
7 files changed, 571 insertions(+), 2 deletions(-)
create mode 100644 include/linux/rust_ffi.h
create mode 100644 rust/kernel/interop/ffi.rs
create mode 100644 rust/macros/ffi_vtable.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 5e168398e963..9f70dc14bf78 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24003,8 +24003,10 @@ M: Alexandre Courbot <acourbot@nvidia.com>
L: rust-for-linux@vger.kernel.org
S: Maintained
T: git https://github.com/Rust-for-Linux/linux.git interop-next
+F: include/linux/rust_ffi.h
F: rust/kernel/interop.rs
F: rust/kernel/interop/
+F: rust/macros/ffi_vtable.rs
RUST [NUM]
M: Alexandre Courbot <acourbot@nvidia.com>
diff --git a/include/linux/rust_ffi.h b/include/linux/rust_ffi.h
new file mode 100644
index 000000000000..b3bdcb70ed72
--- /dev/null
+++ b/include/linux/rust_ffi.h
@@ -0,0 +1,88 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_RUST_FFI_H
+#define _LINUX_RUST_FFI_H
+
+#include <linux/err.h>
+#include <linux/types.h>
+
+/**
+ * struct rust_ffi_token - Stable identifier for a Rust FFI ABI
+ * @high: Most significant half of the identifier
+ * @low: Least significant half of the identifier
+ *
+ * A token is the ABI type tag for the opaque operations table. It tells a
+ * consumer which C type and semantics may be used to access @ops. It is not a
+ * device identifier, secret, permission check, or lifetime handle. Providers
+ * and consumers must use the same pair of constants.
+ */
+struct rust_ffi_token {
+ u64 high;
+ u64 low;
+};
+
+/**
+ * struct rust_ffi - C ABI descriptor for calls into Rust
+ * @token: Stable identifier for the FFI ABI
+ * @abi_major: ABI major version
+ * @abi_minor: ABI minor version
+ * @ops_size: Size of the operations table in bytes
+ * @ops: C ABI operations table
+ * @context: Immutable provider context passed to operations
+ *
+ * Providers must fully initialize this descriptor before publishing it and
+ * must keep the descriptor, operations table, and context alive and immutable
+ * while it is published. A published callable descriptor has non-NULL @ops
+ * and @context pointers. A NULL @ops indicates that no C-callable FFI is
+ * available.
+ *
+ * Minor versions may only append operations to the table. Consumers request
+ * an ABI major version, a minimum ABI minor version, and the size of the table
+ * prefix they use.
+ */
+struct rust_ffi {
+ struct rust_ffi_token token;
+ u16 abi_major;
+ u16 abi_minor;
+ size_t ops_size;
+ const void *ops;
+ const void *context;
+};
+
+/**
+ * rust_ffi_borrow - Validate and borrow a Rust FFI descriptor
+ * @ffi: Descriptor to borrow
+ * @token: Required FFI ABI token
+ * @abi_major: Required ABI major version
+ * @min_abi_minor: Minimum required ABI minor version
+ * @required_ops_size: Minimum required size of the operations table
+ *
+ * This validates only the descriptor contents. The caller must arrange for
+ * @ffi, its operations table, and its context to remain alive and immutable
+ * for the entire borrow.
+ *
+ * Return: @ffi on success, or an ERR_PTR() value on failure.
+ */
+static inline const struct rust_ffi *
+rust_ffi_borrow(const struct rust_ffi *ffi,
+ const struct rust_ffi_token *token,
+ u16 abi_major, u16 min_abi_minor, size_t required_ops_size)
+{
+ if (!token)
+ return ERR_PTR(-EINVAL);
+
+ if (!ffi || !ffi->ops || !ffi->context)
+ return ERR_PTR(-ENOENT);
+
+ if (ffi->token.high != token->high || ffi->token.low != token->low)
+ return ERR_PTR(-ENOENT);
+
+ if (ffi->abi_major != abi_major || ffi->abi_minor < min_abi_minor)
+ return ERR_PTR(-EPROTONOSUPPORT);
+
+ if (ffi->ops_size < required_ops_size)
+ return ERR_PTR(-EMSGSIZE);
+
+ return ffi;
+}
+
+#endif /* _LINUX_RUST_FFI_H */
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 930e63290cdd..6a30455768b4 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -85,6 +85,7 @@
#include <linux/random.h>
#include <linux/refcount.h>
#include <linux/regulator/consumer.h>
+#include <linux/rust_ffi.h>
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/serdev.h>
diff --git a/rust/kernel/interop.rs b/rust/kernel/interop.rs
index 3b371d782a59..9241f3650468 100644
--- a/rust/kernel/interop.rs
+++ b/rust/kernel/interop.rs
@@ -3,7 +3,8 @@
//! Infrastructure for interfacing Rust code with C kernel subsystems.
//!
//! This module is intended for low-level, unsafe Rust infrastructure code
-//! that interoperates between Rust and C. It is *not* for use directly in
-//! Rust drivers.
+//! that interoperates between Rust and C. Drivers should normally use the
+//! generated adapters and safe subsystem abstractions built on top of it.
+pub mod ffi;
pub mod list;
diff --git a/rust/kernel/interop/ffi.rs b/rust/kernel/interop/ffi.rs
new file mode 100644
index 000000000000..a8c16a29110a
--- /dev/null
+++ b/rust/kernel/interop/ffi.rs
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! C-compatible descriptors for calls into Rust.
+//!
+//! An FFI descriptor contains an operations table plus an opaque, pinned Rust context. This
+//! module deliberately does not publish the descriptor or manage its lifetime. A transport, such
+//! as PCI SR-IOV, must keep the context alive and pinned for as long as a consumer can call through
+//! the descriptor.
+
+use crate::{
+ bindings,
+ types::ForLt, //
+};
+use core::{
+ ffi::c_void,
+ pin::Pin, //
+};
+
+/// A transport-independent token identifying an FFI ABI.
+///
+/// Consumers compare this token before interpreting an opaque operations-table pointer. It is an
+/// ABI type tag, not a device identifier, secret, authorization capability, or lifetime handle.
+///
+/// This is a transparent wrapper around
+/// [`struct rust_ffi_token`](srctree/include/linux/rust_ffi.h).
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+pub struct Token(bindings::rust_ffi_token);
+
+impl Token {
+ /// Creates an FFI token from its most and least significant halves.
+ pub const fn new(high: u64, low: u64) -> Self {
+ Self(bindings::rust_ffi_token { high, low })
+ }
+
+ /// Returns the most significant half of the token.
+ pub const fn high(self) -> u64 {
+ self.0.high
+ }
+
+ /// Returns the least significant half of the token.
+ pub const fn low(self) -> u64 {
+ self.0.low
+ }
+}
+
+impl PartialEq for Token {
+ fn eq(&self, other: &Self) -> bool {
+ self.high() == other.high() && self.low() == other.low()
+ }
+}
+
+impl Eq for Token {}
+
+/// Defines the identity, Rust context, and raw operations-table type of an FFI ABI.
+///
+/// Implementations are normally paired with an operations table generated by [`ffi_vtable`]. The
+/// Rust provider and every C consumer must share the corresponding C definition.
+///
+/// [`ffi_vtable`]: crate::macros::ffi_vtable
+///
+/// # Safety
+///
+/// Implementers must ensure that:
+///
+/// - [`RawOps`](Self::RawOps) has a stable C-compatible layout and [`OPS`](Self::OPS) is a fully
+/// initialized instance of that layout;
+/// - for every possible data lifetime, every callback in `OPS` interprets its context as a pinned
+/// [`ForLt::Of`] value with that lifetime, only borrows it for the duration of the callback, and
+/// does not leak or otherwise extend references derived from it;
+/// - [`TOKEN`](Self::TOKEN) and the ABI version uniquely identify that layout and its semantics;
+/// and
+/// - changing `RawOps` incompatibly also changes the ABI major version.
+pub unsafe trait Abi: 'static {
+ /// Rust context type expected by the operations-table callbacks.
+ ///
+ /// The context may be invariant in its encoded data lifetime.
+ type Context: ForLt + 'static;
+
+ /// Raw C-compatible operations-table type.
+ type RawOps: Sync + 'static;
+
+ /// Static operations table published for this ABI.
+ const OPS: &'static Self::RawOps;
+
+ /// Token shared by providers and consumers of this FFI ABI.
+ const TOKEN: Token;
+
+ /// ABI major version, incremented for incompatible changes.
+ const ABI_MAJOR: u16;
+
+ /// ABI minor version, incremented for compatible extensions.
+ const ABI_MINOR: u16;
+}
+
+/// A C-compatible descriptor for an opaque Rust context and operations table.
+///
+/// This is a transparent wrapper around
+/// [`struct rust_ffi`](srctree/include/linux/rust_ffi.h). It neither owns nor borrows the
+/// operations table or Rust context at the type level. The transport that publishes it must ensure
+/// that `ops` remains valid and that `context` remains alive at a stable address until all
+/// consumers have stopped using the descriptor.
+#[repr(transparent)]
+pub struct Descriptor(bindings::rust_ffi);
+
+impl Descriptor {
+ /// Creates a descriptor for a pinned Rust context.
+ ///
+ /// The descriptor does not retain the provider's Rust type or lifetime. A transport must not
+ /// publish it for longer than `context` remains alive and pinned.
+ pub fn new<'borrow, 'data, A>(context: Pin<&'borrow <A::Context as ForLt>::Of<'data>>) -> Self
+ where
+ A: Abi,
+ for<'b> <A::Context as ForLt>::Of<'b>: Send + Sync,
+ {
+ Self(bindings::rust_ffi {
+ token: A::TOKEN.0,
+ abi_major: A::ABI_MAJOR,
+ abi_minor: A::ABI_MINOR,
+ ops_size: core::mem::size_of::<A::RawOps>(),
+ ops: core::ptr::from_ref(A::OPS).cast(),
+ context: core::ptr::from_ref(context.get_ref()).cast(),
+ })
+ }
+
+ /// Returns the FFI ABI token.
+ pub const fn token(&self) -> Token {
+ Token(self.0.token)
+ }
+
+ /// Returns the FFI ABI major version.
+ pub const fn abi_major(&self) -> u16 {
+ self.0.abi_major
+ }
+
+ /// Returns the FFI ABI minor version.
+ pub const fn abi_minor(&self) -> u16 {
+ self.0.abi_minor
+ }
+
+ /// Returns the size in bytes of the raw operations table.
+ pub const fn ops_size(&self) -> usize {
+ self.0.ops_size
+ }
+
+ /// Returns the operations-table pointer.
+ pub const fn ops(&self) -> *const c_void {
+ self.0.ops
+ }
+
+ /// Returns the provider-context pointer.
+ pub const fn context(&self) -> *const c_void {
+ self.0.context
+ }
+
+ /// Returns a raw pointer to the underlying C descriptor.
+ pub const fn as_raw(&self) -> *const bindings::rust_ffi {
+ core::ptr::from_ref(&self.0)
+ }
+}
+
+/// Implementation details for generated FFI adapters.
+#[doc(hidden)]
+pub mod __private {
+ use crate::{
+ error::{
+ from_result,
+ Result, //
+ },
+ ffi::c_int,
+ types::ForLt, //
+ };
+ use core::{
+ ffi::c_void,
+ pin::Pin, //
+ };
+
+ mod sealed {
+ use super::{
+ c_int,
+ Result, //
+ };
+
+ pub trait Sealed<C> {}
+
+ impl<T> Sealed<T> for T {}
+ impl Sealed<c_int> for Result<()> {}
+ impl Sealed<c_int> for Result<c_int> {}
+ }
+
+ /// Converts a Rust operation return value into the return type of its C callback.
+ ///
+ /// The C return type is supplied by the raw operations-table field. This trait is sealed so
+ /// generated adapters can select only the conversions defined by this module.
+ pub trait FfiReturn<C>: sealed::Sealed<C> {
+ /// Performs the return-value conversion.
+ fn into_ffi(self) -> C;
+ }
+
+ impl<T> FfiReturn<T> for T {
+ #[inline]
+ fn into_ffi(self) -> T {
+ self
+ }
+ }
+
+ impl FfiReturn<c_int> for Result<()> {
+ #[inline]
+ fn into_ffi(self) -> c_int {
+ from_result(|| self.map(|()| 0))
+ }
+ }
+
+ impl FfiReturn<c_int> for Result<c_int> {
+ #[inline]
+ fn into_ffi(self) -> c_int {
+ from_result(|| self)
+ }
+ }
+
+ /// Accesses a pinned Rust context through a higher-ranked closure.
+ ///
+ /// # Safety
+ ///
+ /// `context` must have been obtained from a `Pin<&F::Of<'data>>` for some data lifetime and
+ /// must point to that live, properly aligned value, which remains pinned and valid for shared
+ /// access throughout this call. The pointed-to value must not be mutated except through
+ /// synchronization-safe interior mutability.
+ pub unsafe fn with_context<F: ForLt + 'static, R>(
+ context: *const c_void,
+ f: impl for<'borrow, 'data> FnOnce(Pin<&'borrow F::Of<'data>>) -> R,
+ ) -> R {
+ // SAFETY: The caller guarantees a live, pinned context of this lifetime family. The
+ // higher-ranked closure keeps the borrow independent of the erased data lifetime, so it
+ // cannot escape or be stored in the context's invariant data.
+ let context = unsafe { Pin::new_unchecked(&*context.cast::<F::Of<'_>>()) };
+ f(context)
+ }
+}
diff --git a/rust/macros/ffi_vtable.rs b/rust/macros/ffi_vtable.rs
new file mode 100644
index 000000000000..ad3176d1f8e1
--- /dev/null
+++ b/rust/macros/ffi_vtable.rs
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use proc_macro2::TokenStream;
+use quote::{
+ format_ident,
+ quote, //
+};
+use syn::{
+ parse::{
+ Parse,
+ ParseStream, //
+ },
+ Attribute,
+ Error,
+ FnArg,
+ Ident,
+ ImplItem,
+ ItemImpl,
+ Path,
+ Result,
+ ReturnType,
+ Token, //
+};
+
+pub(crate) struct FfiVtableArgs {
+ table: Ident,
+ ops: Path,
+}
+
+impl Parse for FfiVtableArgs {
+ fn parse(input: ParseStream<'_>) -> Result<Self> {
+ let table = input.parse()?;
+ let _: Token![:] = input.parse()?;
+ let ops = input.parse()?;
+
+ Ok(Self { table, ops })
+ }
+}
+
+fn has_conditional(attributes: &[Attribute]) -> bool {
+ attributes
+ .iter()
+ .any(|attribute| attribute.path().is_ident("cfg") || attribute.path().is_ident("cfg_attr"))
+}
+
+pub(crate) fn ffi_vtable(args: FfiVtableArgs, item: ItemImpl) -> Result<TokenStream> {
+ if item.trait_.is_some()
+ || !item.generics.params.is_empty()
+ || item.generics.where_clause.is_some()
+ {
+ return Err(Error::new_spanned(
+ &item,
+ "`#[ffi_vtable]` requires a concrete, non-generic inherent impl",
+ ));
+ }
+ if has_conditional(&item.attrs) {
+ return Err(Error::new_spanned(
+ &item,
+ "`#[ffi_vtable]` does not support conditionally compiled impls",
+ ));
+ }
+
+ let ops = &args.ops;
+ let table = &args.table;
+ let self_ty = &item.self_ty;
+ let private = quote!(::kernel::interop::ffi::__private);
+ let mut fields = Vec::new();
+
+ for impl_item in &item.items {
+ let ImplItem::Fn(method) = impl_item else {
+ continue;
+ };
+ let signature = &method.sig;
+ if has_conditional(&method.attrs)
+ || !signature.generics.params.is_empty()
+ || signature.generics.where_clause.is_some()
+ {
+ return Err(Error::new_spanned(
+ method,
+ "`#[ffi_vtable]` requires unconditional, non-generic methods",
+ ));
+ }
+
+ let mut argument_names = Vec::new();
+ let mut argument_types = Vec::new();
+ for argument in &signature.inputs {
+ let FnArg::Typed(argument) = argument else {
+ continue;
+ };
+
+ let index = argument_names.len();
+ argument_names.push(format_ident!("__ffi_vtable_arg_{index}"));
+ argument_types.push(&argument.ty);
+ }
+
+ let method_name = &signature.ident;
+ let rust_output = match &signature.output {
+ ReturnType::Default => quote!(()),
+ ReturnType::Type(_, ty) => quote!(#ty),
+ };
+
+ fields.push(quote! {
+ #method_name: ::core::option::Option::Some({
+ unsafe extern "C" fn callback<__FfiVtableReturn>(
+ __ffi_vtable_context: *const ::core::ffi::c_void,
+ #(#argument_names: #argument_types),*
+ ) -> __FfiVtableReturn
+ where
+ #rust_output: #private::FfiReturn<__FfiVtableReturn>,
+ {
+ let __ffi_vtable_call = |__ffi_vtable_this: ::core::pin::Pin<&#self_ty>| {
+ // Infer the receiver within the closure's context lifetime.
+ let __ffi_vtable_method: unsafe fn(
+ ::core::pin::Pin<&_>,
+ #(#argument_types),*
+ ) -> #rust_output = <#self_ty>::#method_name;
+
+ #private::FfiReturn::<__FfiVtableReturn>::into_ffi(
+ // SAFETY: An unsafe method relies on the C caller satisfying its
+ // argument contract.
+ unsafe {
+ __ffi_vtable_method(__ffi_vtable_this, #(#argument_names),*)
+ },
+ )
+ };
+
+ // SAFETY: The publisher keeps the context live and pinned while callbacks run.
+ unsafe {
+ #private::with_context::<::kernel::types::ForLt!(#self_ty), _>(
+ __ffi_vtable_context,
+ __ffi_vtable_call,
+ )
+ }
+ }
+
+ callback::<_>
+ })
+ });
+ }
+
+ Ok(quote! {
+ #item
+
+ static #table: #ops = #ops {
+ #(#fields),*
+ };
+ })
+}
diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 9b76efe1476f..c9bd09148301 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -16,6 +16,7 @@
mod concat_idents;
mod export;
+mod ffi_vtable;
mod fmt;
mod for_lt;
mod helpers;
@@ -263,6 +264,95 @@ pub fn export(attr: TokenStream, input: TokenStream) -> TokenStream {
export::export(parse_macro_input!(input)).into()
}
+/// Generates a C-compatible operations table for a concrete Rust implementation.
+///
+/// The attribute declares the name of the table to generate and its bindgen-generated raw
+/// operations type:
+///
+/// ```
+/// use core::{
+/// ffi::{
+/// c_int,
+/// c_void, //
+/// },
+/// pin::Pin, //
+/// };
+/// use kernel::{
+/// macros::ffi_vtable,
+/// prelude::*, //
+/// };
+///
+/// #[repr(C)]
+/// struct ExampleOps {
+/// submit: Option<unsafe extern "C" fn(*const c_void, u16) -> c_int>,
+/// reset: Option<unsafe extern "C" fn(*const c_void) -> c_int>,
+/// version: Option<unsafe extern "C" fn(*const c_void) -> u16>,
+/// }
+///
+/// struct Provider;
+///
+/// #[ffi_vtable(EXAMPLE_OPS: ExampleOps)]
+/// impl Provider {
+/// fn submit(self: Pin<&Self>, requester_id: u16) -> Result<c_int> {
+/// Ok(c_int::from(requester_id))
+/// }
+///
+/// fn reset(self: Pin<&Self>) -> Result {
+/// Ok(())
+/// }
+///
+/// fn version(self: Pin<&Self>) -> u16 {
+/// 1
+/// }
+/// }
+///
+/// # fn main() {
+/// assert!(EXAMPLE_OPS.submit.is_some());
+/// assert!(EXAMPLE_OPS.reset.is_some());
+/// assert!(EXAMPLE_OPS.version.is_some());
+/// # }
+/// ```
+///
+/// Each method becomes a field of the same name in `EXAMPLE_OPS`. The generated C callback has an
+/// additional `*const c_void` context as its first argument. It recovers a `Pin<&Provider>` from
+/// that context inside a higher-ranked closure and forwards the remaining arguments. This also
+/// supports implementations such as `impl Provider<'_>` whose data lifetime is invariant. The
+/// closure keeps that data lifetime independent of the callback's borrow. Return values are
+/// forwarded unchanged, except that a [`Result<c_int>`] is converted into a `c_int`, preserving a
+/// successful value, and a [`Result<()>`] is converted into zero on success. Both return a negative
+/// errno on failure.
+/// Initializing the raw bindgen type with a struct literal checks the field names and callback
+/// signatures at compile time.
+///
+/// The attribute supports concrete inherent impls. Methods must otherwise use ABI-shaped argument
+/// and return types. They must be non-async, non-generic Rust methods with a `self: Pin<&Self>`
+/// receiver. A method may be safe when its arguments require no validity assumptions beyond their
+/// Rust types. It must be `unsafe fn` when calling it relies on additional C-side guarantees, such
+/// as the validity of a raw pointer argument. Every field of the raw operations structure must have
+/// a matching method; optional methods and conditionally compiled impls, methods, or arguments are
+/// not supported yet. Argument and return types must spell out concrete types instead of using
+/// `Self`.
+///
+/// [`Result<c_int>`]: ../kernel/error/type.Result.html
+/// [`Result<()>`]: ../kernel/error/type.Result.html
+///
+/// # Safety contract
+///
+/// The code publishing the generated table must pass a non-null context pointer to a valid pinned
+/// instance of the implementation type, with the same lifetime family, and keep that instance alive
+/// and valid for shared access for every callback. Callers must uphold the safety contract of each
+/// unsafe method. The macro emits private function-pointer callbacks and does not export symbols
+/// for them.
+#[proc_macro_attribute]
+pub fn ffi_vtable(attr: TokenStream, input: TokenStream) -> TokenStream {
+ ffi_vtable::ffi_vtable(
+ parse_macro_input!(attr as ffi_vtable::FfiVtableArgs),
+ parse_macro_input!(input as syn::ItemImpl),
+ )
+ .unwrap_or_else(|error| error.into_compile_error())
+ .into()
+}
+
/// Like [`core::format_args!`], but automatically wraps arguments in [`kernel::fmt::Adapter`].
///
/// This macro allows generating `fmt::Arguments` while ensuring that each argument is wrapped with
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 11/14] rust: pci: add C FFI support to typed SR-IOV PF registration data
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (9 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 10/14] rust: add C-to-Rust FFI descriptors and trampolines Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 12/14] samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver Zhi Wang
` (3 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
Rust VF drivers can borrow PF-owned registration data directly, but C VF
drivers need an ABI-checked operations table and a pinned context.
Place an optional struct rust_ffi descriptor at offset zero in the
registration header while retaining the Rust TypeId. Initialize
registrations created with new() with an empty descriptor, and add
new_ffi() to construct a descriptor for the same pinned payload.
Add pci_iov_borrow_rust_pf_data() to locate the PF descriptor and validate
the token, ABI version, and operations-table size. Require managed SR-IOV
so device-link ordering and registration teardown keep the descriptor and
its context alive until the VF driver is unbound.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
drivers/pci/iov.c | 51 +++++++++++++++++
include/linux/pci.h | 34 +++++++++++-
rust/kernel/interop/ffi.rs | 15 ++++-
rust/kernel/pci/sriov.rs | 110 ++++++++++++++++++++++++++++---------
4 files changed, 179 insertions(+), 31 deletions(-)
diff --git a/drivers/pci/iov.c b/drivers/pci/iov.c
index ee5eff209e15..0f3ae75b8a7c 100644
--- a/drivers/pci/iov.c
+++ b/drivers/pci/iov.c
@@ -11,6 +11,7 @@
#include <linux/bits.h>
#include <linux/log2.h>
#include <linux/pci.h>
+#include <linux/rust_ffi.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/export.h>
@@ -80,6 +81,56 @@ void *pci_iov_get_pf_drvdata(struct pci_dev *dev, struct pci_driver *pf_driver)
}
EXPORT_SYMBOL_GPL(pci_iov_get_pf_drvdata);
+#ifdef CONFIG_RUST
+/**
+ * pci_iov_borrow_rust_pf_data - Validate and borrow Rust data from a VF's PF
+ * @dev: VF PCI device
+ * @token: Required FFI ABI token
+ * @abi_major: Required ABI major version
+ * @min_abi_minor: Minimum required ABI minor version
+ * @required_ops_size: Minimum required size of the operations table
+ *
+ * This may be called from a VF driver's probe() callback or from a context in
+ * which the VF driver is known to remain attached. If probe() succeeds, the
+ * returned pointer, its operations table, and its context are borrowed until
+ * the VF driver is fully unbound, including the return of its remove() callback
+ * when present. If probe() fails, the caller must discard the borrow before
+ * returning. The caller must drain all work that can use the FFI before the
+ * borrow ends.
+ *
+ * The PF must publish an immutable descriptor before enabling VFs, use
+ * managed_sriov, and retain the descriptor until its remove() callback.
+ * Managed SR-IOV installs a device link from every VF to its PF before the VF
+ * can probe. The driver core therefore waits for an in-progress VF probe and
+ * unbinds a bound VF before invoking the PF driver's remove() callback.
+ *
+ * Return: A borrowed FFI descriptor, or an ERR_PTR() value on failure.
+ */
+const struct rust_ffi *
+pci_iov_borrow_rust_pf_data(struct pci_dev *dev,
+ const struct rust_ffi_token *token,
+ u16 abi_major, u16 min_abi_minor,
+ size_t required_ops_size)
+{
+ const struct rust_ffi *ffi;
+ struct pci_dev *pf_dev;
+ struct pci_driver *pf_driver;
+
+ if (!dev->is_virtfn)
+ return ERR_PTR(-EINVAL);
+
+ pf_dev = pci_physfn(dev);
+ pf_driver = READ_ONCE(pf_dev->driver);
+ if (!pf_driver || !READ_ONCE(pf_driver->managed_sriov))
+ return ERR_PTR(-ENODEV);
+
+ ffi = READ_ONCE(pf_dev->vf_registration_data_rust);
+ return rust_ffi_borrow(ffi, token, abi_major, min_abi_minor,
+ required_ops_size);
+}
+EXPORT_SYMBOL_GPL(pci_iov_borrow_rust_pf_data);
+#endif
+
/*
* Per SR-IOV spec sec 3.3.10 and 3.3.11, First VF Offset and VF Stride may
* change when NumVFs changes.
diff --git a/include/linux/pci.h b/include/linux/pci.h
index 1ccc7fee7495..4a7189ee247f 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -339,6 +339,8 @@ struct pcie_link_state;
struct pci_sriov;
struct pci_p2pdma;
struct rcec_ea;
+struct rust_ffi;
+struct rust_ffi_token;
/* struct pci_dev - describes a PCI device
*
@@ -352,9 +354,10 @@ struct rcec_ea;
* Such bridges are allocated additional MMIO and bus
* number resources to allow for hierarchy expansion.
* @is_pciehp: PCIe Hot-Plug Capable bridge.
- * @vf_registration_data_rust: Rust registration data published by the PF
- * before enabling VFs and retained until all VFs are
- * removed. The PF driver must use managed_sriov.
+ * @vf_registration_data_rust: Rust registration data beginning with a
+ * struct rust_ffi, published by the PF before
+ * enabling VFs and retained until all VFs are removed.
+ * The PF driver must use managed_sriov.
*/
struct pci_dev {
struct list_head bus_list; /* Node in per-bus list */
@@ -2614,6 +2617,22 @@ int pci_iov_virtfn_bus(struct pci_dev *dev, int id);
int pci_iov_virtfn_devfn(struct pci_dev *dev, int id);
int pci_iov_vf_id(struct pci_dev *dev);
void *pci_iov_get_pf_drvdata(struct pci_dev *dev, struct pci_driver *pf_driver);
+#ifdef CONFIG_RUST
+const struct rust_ffi *
+pci_iov_borrow_rust_pf_data(struct pci_dev *dev,
+ const struct rust_ffi_token *token,
+ u16 abi_major, u16 min_abi_minor,
+ size_t required_ops_size);
+#else
+static inline const struct rust_ffi *
+pci_iov_borrow_rust_pf_data(struct pci_dev *dev,
+ const struct rust_ffi_token *token,
+ u16 abi_major, u16 min_abi_minor,
+ size_t required_ops_size)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+#endif
int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn);
void pci_disable_sriov(struct pci_dev *dev);
@@ -2656,6 +2675,15 @@ static inline void *pci_iov_get_pf_drvdata(struct pci_dev *dev,
return ERR_PTR(-EINVAL);
}
+static inline const struct rust_ffi *
+pci_iov_borrow_rust_pf_data(struct pci_dev *dev,
+ const struct rust_ffi_token *token,
+ u16 abi_major, u16 min_abi_minor,
+ size_t required_ops_size)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
static inline int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn)
{ return -ENODEV; }
diff --git a/rust/kernel/interop/ffi.rs b/rust/kernel/interop/ffi.rs
index a8c16a29110a..ea23bfecad32 100644
--- a/rust/kernel/interop/ffi.rs
+++ b/rust/kernel/interop/ffi.rs
@@ -99,10 +99,23 @@ pub unsafe trait Abi: 'static {
/// [`struct rust_ffi`](srctree/include/linux/rust_ffi.h). It neither owns nor borrows the
/// operations table or Rust context at the type level. The transport that publishes it must ensure
/// that `ops` remains valid and that `context` remains alive at a stable address until all
-/// consumers have stopped using the descriptor.
+/// consumers have stopped using the descriptor. The default value exposes no operations.
#[repr(transparent)]
pub struct Descriptor(bindings::rust_ffi);
+impl Default for Descriptor {
+ fn default() -> Self {
+ Self(bindings::rust_ffi {
+ token: bindings::rust_ffi_token { high: 0, low: 0 },
+ abi_major: 0,
+ abi_minor: 0,
+ ops_size: 0,
+ ops: core::ptr::null(),
+ context: core::ptr::null(),
+ })
+ }
+}
+
impl Descriptor {
/// Creates a descriptor for a pinned Rust context.
///
diff --git a/rust/kernel/pci/sriov.rs b/rust/kernel/pci/sriov.rs
index efbe444e0733..a2590b66f78b 100644
--- a/rust/kernel/pci/sriov.rs
+++ b/rust/kernel/pci/sriov.rs
@@ -6,6 +6,10 @@
use crate::{
bindings,
device, //
+ interop::ffi::{
+ Abi,
+ Descriptor, //
+ },
prelude::*,
types::{
CovariantForLt,
@@ -91,26 +95,45 @@ pub fn num_vfs(&self) -> i32 {
// depends on its device context.
kernel::impl_device_context_deref!(unsafe { Device });
+#[repr(C)]
+struct VfRegistrationHeader {
+ ffi: Descriptor,
+ type_id: TypeId,
+}
+
+static_assert!(core::mem::offset_of!(VfRegistrationHeader, ffi) == 0);
+
#[repr(C)]
#[pin_data]
struct VfRegistrationData<'a, F: ForLt + 'static> {
- type_id: TypeId,
+ header: VfRegistrationHeader,
#[pin]
data: F::Of<'a>,
}
static_assert!(
- core::mem::offset_of!(VfRegistrationData<'static, CovariantForLt!(())>, type_id) == 0
+ core::mem::offset_of!(VfRegistrationData<'static, CovariantForLt!(())>, header) == 0
);
impl<'a, F: ForLt + 'static> VfRegistrationData<'a, F> {
- fn new<D>(data: D) -> impl PinInit<Self, Error> + use<'a, D, F>
+ fn new<D, M>(data: D, make_descriptor: M) -> impl PinInit<Self, Error> + use<'a, D, F, M>
where
D: PinInit<F::Of<'a>, Error> + 'a,
+ M: FnOnce(Pin<&F::Of<'a>>) -> Descriptor + 'a,
{
- try_pin_init!(Self {
- type_id: TypeId::of::<F>(),
+ try_pin_init!(&this in Self {
+ header: VfRegistrationHeader {
+ ffi: Descriptor::default(),
+ type_id: TypeId::of::<F>(),
+ },
data <- data,
+ _: {
+ // SAFETY: `data` has been initialized in place and will remain pinned at this
+ // address.
+ let data = unsafe { Pin::new_unchecked(&(*this.as_ptr()).data) };
+ // SAFETY: `header.ffi` is initialized and exclusively owned during construction.
+ unsafe { (*this.as_ptr()).header.ffi = make_descriptor(data) };
+ },
})
}
}
@@ -138,28 +161,14 @@ impl<'a, F: ForLt + 'static> VfRegistration<'a, F>
where
for<'b> F::Of<'b>: Send + Sync,
{
- /// Publishes typed PF data for bound VF drivers.
- ///
- /// This returns a pin-initializer so the registration and payload can be embedded directly in
- /// the PF driver's pinned data.
- ///
- /// Initialization returns [`ENODEV`] for a VF and [`EBUSY`] if the PF has enabled VFs or
- /// already has a registration.
- ///
- /// # Safety
- ///
- /// The caller must invoke this during the PCI driver's probe and embed the result in the driver
- /// data. On an SR-IOV PF, no VF may be enabled before probe successfully installs the complete
- /// driver data, and the driver must use managed SR-IOV. The registration must be dropped before
- /// anything its payload borrows and must not be forgotten. Probe must have exclusive access to
- /// the PF registration slot. On a conventional PCI function, the registration remains
- /// inactive.
- pub unsafe fn new<'core, D>(
+ fn new_with_descriptor<'core, D, M>(
pdev: &'a PciDevice<device::Core<'core>>,
data: D,
- ) -> impl PinInit<Self, Error> + use<'a, 'core, D, F>
+ make_descriptor: M,
+ ) -> impl PinInit<Self, Error> + use<'a, 'core, D, F, M>
where
D: PinInit<F::Of<'a>, Error> + 'a,
+ M: FnOnce(Pin<&F::Of<'a>>) -> Descriptor + 'a,
{
pin_init::pin_init_scope(move || {
if pdev.is_virtfn() {
@@ -179,7 +188,7 @@ pub unsafe fn new<'core, D>(
Ok(try_pin_init!(Self {
pdev,
- inner <- VfRegistrationData::new(data),
+ inner <- VfRegistrationData::new(data, make_descriptor),
published,
_pin: PhantomPinned,
_: {
@@ -192,6 +201,53 @@ pub unsafe fn new<'core, D>(
}))
})
}
+
+ /// Publishes typed PF data for bound VF drivers.
+ ///
+ /// This returns a pin-initializer so the registration and payload can be embedded directly in
+ /// the PF driver's pinned data.
+ ///
+ /// Initialization returns [`ENODEV`] for a VF and [`EBUSY`] if the PF has enabled VFs or
+ /// already has a registration.
+ ///
+ /// # Safety
+ ///
+ /// The caller must invoke this during the PCI driver's probe and embed the result in the driver
+ /// data. On an SR-IOV PF, no VF may be enabled before probe successfully installs the complete
+ /// driver data, and the driver must use managed SR-IOV. The registration must be dropped before
+ /// anything its payload borrows and must not be forgotten. Probe must have exclusive access to
+ /// the PF registration slot. On a conventional PCI function, the registration remains
+ /// inactive.
+ pub unsafe fn new<'core, D>(
+ pdev: &'a PciDevice<device::Core<'core>>,
+ data: D,
+ ) -> impl PinInit<Self, Error> + use<'a, 'core, D, F>
+ where
+ D: PinInit<F::Of<'a>, Error> + 'a,
+ {
+ Self::new_with_descriptor(pdev, data, |_| Descriptor::default())
+ }
+
+ /// Publishes typed PF data with an FFI operations table for C VF drivers.
+ ///
+ /// Rust VFs access the same payload through [`PciDevice::vf_registration_data()`] or
+ /// [`PciDevice::vf_registration_data_with()`].
+ ///
+ /// # Safety
+ ///
+ /// The caller must uphold the requirements of [`Self::new()`]. A C consumer must stop calling
+ /// and discard the borrow before returning from a failed VF probe, or before its VF remove
+ /// callback returns after a successful probe.
+ pub unsafe fn new_ffi<'core, A, D>(
+ pdev: &'a PciDevice<device::Core<'core>>,
+ data: D,
+ ) -> impl PinInit<Self, Error> + use<'a, 'core, A, D, F>
+ where
+ A: Abi<Context = F>,
+ D: PinInit<F::Of<'a>, Error> + 'a,
+ {
+ Self::new_with_descriptor(pdev, data, Descriptor::new::<A>)
+ }
}
#[pinned_drop]
@@ -251,9 +307,9 @@ unsafe fn vf_registration_data_pinned<F: ForLt + 'static>(&self) -> Result<Pin<&
return Err(ENOENT);
}
- // SAFETY: The published pointer addresses a `VfRegistrationData`, whose first field is a
- // `TypeId`.
- let type_id = unsafe { ptr.cast::<TypeId>().read() };
+ // SAFETY: The published pointer addresses a `VfRegistrationData`, whose first field is
+ // its header.
+ let type_id = unsafe { (&raw const (*ptr.cast::<VfRegistrationHeader>()).type_id).read() };
if type_id != TypeId::of::<F>() {
return Err(EINVAL);
}
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 12/14] samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (10 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 11/14] rust: pci: add C FFI support to typed SR-IOV PF registration data Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 13/14] gpu: nova-core: publish typed SR-IOV PF data for VF drivers Zhi Wang
` (2 subsequent siblings)
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
C drivers need a type-checked way to invoke functionality implemented by
Rust PF drivers without receiving the PF driver's complete private data.
The Rust SR-IOV sample currently demonstrates only a Rust VF consumer.
Add an optional C VF module that borrows the sample's published PF data
through a `struct rust_ffi` descriptor. Validate the ABI token, version,
and operations-table size before calling an automatically generated Rust
trampoline.
Route Rust and C VF requests through the same PF handler. The generated C
callback forwards the requester's PCI ID and translates the Rust `Result`
into the C errno convention. The C driver uses the borrow only while its
VF remains bound, so all users must be drained before its remove callback
returns.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
MAINTAINERS | 2 +-
rust/bindings/bindings_helper.h | 4 ++
samples/rust/Kconfig | 17 +++++++-
samples/rust/Makefile | 1 +
samples/rust/rust_driver_sriov.h | 27 ++++++++++++
samples/rust/rust_driver_sriov.rs | 43 ++++++++++++++++---
samples/rust/rust_driver_sriov_c_vf.c | 61 +++++++++++++++++++++++++++
7 files changed, 146 insertions(+), 9 deletions(-)
create mode 100644 samples/rust/rust_driver_sriov.h
create mode 100644 samples/rust/rust_driver_sriov_c_vf.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 9f70dc14bf78..e03ebe44c341 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21141,7 +21141,7 @@ F: rust/helpers/pci.c
F: rust/kernel/pci.rs
F: rust/kernel/pci/
F: samples/rust/rust_driver_pci.rs
-F: samples/rust/rust_driver_sriov.rs
+F: samples/rust/rust_driver_sriov*
PCIE BANDWIDTH CONTROLLER
M: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6a30455768b4..2467305c4842 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -112,6 +112,10 @@
#include <drm/drm_panic.h>
#endif
+#if IS_ENABLED(CONFIG_SAMPLE_RUST_DRIVER_SRIOV)
+#include "../../samples/rust/rust_driver_sriov.h"
+#endif
+
/* `bindgen` gets confused at certain things. */
const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN;
const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN;
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index 737670fd68f8..3747f3fe23dd 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -132,13 +132,28 @@ config SAMPLE_RUST_DRIVER_SRIOV
tristate "SR-IOV Driver"
depends on PCI_IOV
help
- This option builds the Rust SR-IOV driver sample.
+ This option builds the Rust SR-IOV PF and VF driver sample.
To compile this as a module, choose M here:
the module will be called rust_driver_sriov.
If unsure, say N.
+config SAMPLE_RUST_DRIVER_SRIOV_C_VF
+ tristate "C VF consumer for the Rust SR-IOV driver"
+ depends on SAMPLE_RUST_DRIVER_SRIOV
+ help
+ This option builds a C VF driver that accesses PF data through FFI
+ published by the Rust SR-IOV PF sample.
+
+ To compile this as a module, choose M here: the module will be called
+ rust_driver_sriov_c_vf. Load it before rust_driver_sriov and enable
+ VFs only after both drivers are registered. Ensure that no other VF
+ driver has claimed the device; driver_override may be used for a
+ deterministic test.
+
+ If unsure, say N.
+
config SAMPLE_RUST_DRIVER_USB
tristate "USB Driver"
depends on USB = y
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 238a11d5ec39..b52f07df52de 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C) += rust_driver_i2c.o
obj-$(CONFIG_SAMPLE_RUST_I2C_CLIENT) += rust_i2c_client.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI) += rust_driver_pci.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM) += rust_driver_platform.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_SRIOV_C_VF) += rust_driver_sriov_c_vf.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_SRIOV) += rust_driver_sriov.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) += rust_driver_usb.o
obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) += rust_driver_faux.o
diff --git a/samples/rust/rust_driver_sriov.h b/samples/rust/rust_driver_sriov.h
new file mode 100644
index 000000000000..1d4dff4e3414
--- /dev/null
+++ b/samples/rust/rust_driver_sriov.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SAMPLES_RUST_DRIVER_SRIOV_H
+#define _SAMPLES_RUST_DRIVER_SRIOV_H
+
+#include <linux/rust_ffi.h>
+
+#define RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH 0x6c8686c04f7a4ba1ULL
+#define RUST_DRIVER_SRIOV_FFI_TOKEN_LOW 0x77ab9a3c4d3cfb34ULL
+#define RUST_DRIVER_SRIOV_FFI_ABI_MAJOR 1U
+#define RUST_DRIVER_SRIOV_FFI_ABI_MINOR 0U
+
+/**
+ * struct rust_driver_sriov_ops - Operations published by the Rust PF sample
+ * @submit: Submit one request for a requester ID and return 0 or a negative
+ * errno
+ *
+ * The context passed to each operation must be the context from the borrowed
+ * struct rust_ffi. It remains valid until the VF driver is fully unbound,
+ * including the return of its remove() callback when present.
+ *
+ * @submit may sleep and must not be called from atomic context.
+ */
+struct rust_driver_sriov_ops {
+ int (*submit)(const void *context, u16 requester_id);
+};
+
+#endif /* _SAMPLES_RUST_DRIVER_SRIOV_H */
diff --git a/samples/rust/rust_driver_sriov.rs b/samples/rust/rust_driver_sriov.rs
index ae1e1babb66b..f3e37586b0f4 100644
--- a/samples/rust/rust_driver_sriov.rs
+++ b/samples/rust/rust_driver_sriov.rs
@@ -10,15 +10,24 @@
//!
//! and append `intel_iommu=on` to the guest kernel arguments.
//!
+//! The optional `rust_driver_sriov_c_vf` module demonstrates a C VF calling the same PF data
+//! through an FFI operations table.
+//! Load that module before this one and enable VFs only after both drivers are registered.
+//!
//! [igb]: https://www.qemu.org/docs/master/system/devices/igb.html
//! [vIOMMU]: https://wiki.qemu.org/Features/VT-d
use kernel::{
+ bindings,
device::{
Bound,
Core, //
},
driver,
+ interop::ffi::{
+ Abi,
+ Token, //
+ },
new_mutex,
pci,
prelude::*,
@@ -45,8 +54,9 @@ struct PfApi<'bound> {
type PfApiForLt = CovariantForLt!(PfApi<'_>);
+#[kernel::macros::ffi_vtable(SAMPLE_FFI_OPS: bindings::rust_driver_sriov_ops)]
impl PfApi<'_> {
- fn submit(self: Pin<&Self>, vf: &pci::Device<Bound>) -> Result<u64> {
+ fn submit(self: Pin<&Self>, requester_id: u16) -> Result {
let mut requests = self.requests.lock();
let request = (*requests).checked_add(1).ok_or(EOVERFLOW)?;
*requests = request;
@@ -54,15 +64,34 @@ fn submit(self: Pin<&Self>, vf: &pci::Device<Bound>) -> Result<u64> {
dev_info!(
self.pdev,
- "Handle PF request {} from VF devfn {:#x}.\n",
+ "Handle PF request {} from VF requester ID {:#06x}.\n",
request,
- vf.dev_id()
+ requester_id
);
- Ok(request)
+ Ok(())
}
}
+struct SampleFfiAbi;
+
+// SAFETY:
+// - `RawOps` and the token/version constants come from the C header shared with consumers;
+// - `SAMPLE_FFI_OPS` is initialized as that raw type by `ffi_vtable`; and
+// - every callback recovers the context as the pinned `PfApi` published below.
+unsafe impl Abi for SampleFfiAbi {
+ type Context = PfApiForLt;
+ type RawOps = bindings::rust_driver_sriov_ops;
+
+ const OPS: &'static Self::RawOps = &SAMPLE_FFI_OPS;
+ const TOKEN: Token = Token::new(
+ bindings::RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH,
+ bindings::RUST_DRIVER_SRIOV_FFI_TOKEN_LOW,
+ );
+ const ABI_MAJOR: u16 = bindings::RUST_DRIVER_SRIOV_FFI_ABI_MAJOR as u16;
+ const ABI_MINOR: u16 = bindings::RUST_DRIVER_SRIOV_FFI_ABI_MINOR as u16;
+}
+
#[pin_data(PinnedDrop)]
struct PfDriverData<'bound> {
// Keep the device alive until the registration stops exposing `PfApi::pdev`.
@@ -125,7 +154,7 @@ fn probe<'bound>(
// - no other registration is created for this PF; and
// - VFs are enabled only after probe by `sriov_configure`.
_registration <- unsafe {
- pci::VfRegistration::new(
+ pci::VfRegistration::new_ffi::<SampleFfiAbi, _>(
pdev,
try_pin_init!(PfApi {
pdev,
@@ -191,8 +220,8 @@ fn probe<'bound>(
pdev.enable_device_mem()?;
pdev.set_master();
- let request = pf_api.submit(pdev)?;
- dev_info!(pdev, "Submitted request {} through PF data.\n", request);
+ pf_api.submit(pdev.dev_id())?;
+ dev_info!(pdev, "Submitted request through PF data.\n");
Ok(try_pin_init!(VfDriverData { pdev: pdev.into() }))
})
diff --git a/samples/rust/rust_driver_sriov_c_vf.c b/samples/rust/rust_driver_sriov_c_vf.c
new file mode 100644
index 000000000000..50df98867361
--- /dev/null
+++ b/samples/rust/rust_driver_sriov_c_vf.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+
+#include "rust_driver_sriov.h"
+
+#define E1000_DEV_ID_82576_VF 0x10ca
+
+static const struct rust_ffi_token ffi_token = {
+ .high = RUST_DRIVER_SRIOV_FFI_TOKEN_HIGH,
+ .low = RUST_DRIVER_SRIOV_FFI_TOKEN_LOW,
+};
+
+static int rust_driver_sriov_c_vf_probe(struct pci_dev *pdev,
+ const struct pci_device_id *id)
+{
+ const struct rust_driver_sriov_ops *ops;
+ const struct rust_ffi *ffi;
+ int ret;
+
+ ffi = pci_iov_borrow_rust_pf_data(pdev, &ffi_token,
+ RUST_DRIVER_SRIOV_FFI_ABI_MAJOR,
+ RUST_DRIVER_SRIOV_FFI_ABI_MINOR,
+ sizeof(*ops));
+ if (IS_ERR(ffi))
+ return dev_err_probe(&pdev->dev, PTR_ERR(ffi),
+ "failed to borrow PF FFI\n");
+
+ ops = ffi->ops;
+ if (!ops->submit)
+ return dev_err_probe(&pdev->dev, -EOPNOTSUPP,
+ "PF FFI does not implement submit\n");
+
+ ret = ops->submit(ffi->context, pci_dev_id(pdev));
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to submit through PF FFI\n");
+
+ pci_info(pdev, "submitted request through Rust PF FFI\n");
+
+ return 0;
+}
+
+static const struct pci_device_id rust_driver_sriov_c_vf_id_table[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, E1000_DEV_ID_82576_VF) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, rust_driver_sriov_c_vf_id_table);
+
+static struct pci_driver rust_driver_sriov_c_vf_driver = {
+ .name = "rust_driver_sriov_c_vf",
+ .id_table = rust_driver_sriov_c_vf_id_table,
+ .probe = rust_driver_sriov_c_vf_probe,
+};
+module_pci_driver(rust_driver_sriov_c_vf_driver);
+
+MODULE_AUTHOR("Rust for Linux Contributors");
+MODULE_DESCRIPTION("C VF consumer for the Rust SR-IOV driver sample");
+MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 13/14] gpu: nova-core: publish typed SR-IOV PF data for VF drivers
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (11 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 12/14] samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-15 20:56 ` [PATCH 14/14] Documentation: rust: explain SR-IOV PF data sharing with VFs Zhi Wang
2026-09-16 11:10 ` [PATCH 00/14] Add Rust PCI SR-IOV support Danilo Krummrich
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
Nova-core is the PF-side GPU driver, but it does not implement the PCI
callback for `sriov_numvfs`. Userspace therefore cannot enable or
disable VFs on GPUs bound to the driver, and no typed PF data is
available before a VF is created.
Embed a `VfRegistration` in the pinned `NovaCore` driver data and
publish unit data, `()`, as a typed readiness marker on SR-IOV PFs. The
registration remains inactive on conventional PCI functions and rejects
VFs, so nova-core can keep using the ordinary `pci::Driver` abstraction
with or without `CONFIG_PCI_IOV`.
Implement `sriov_configure()` using the verified PF view and pinned
driver data. Managed SR-IOV removes every VF before the inline
registration and the rest of `NovaCore` are dropped.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
drivers/gpu/nova-core/driver.rs | 33 ++++++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index a5b9a50cbaca..1373417386a2 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -35,6 +35,10 @@
#[pin_data]
pub(crate) struct NovaCore<'bound> {
+ #[cfg(CONFIG_PCI_IOV)]
+ #[allow(clippy::type_complexity)]
+ #[pin]
+ _vf_registration: pci::VfRegistration<'bound, CovariantForLt!(())>,
#[pin]
pub(crate) gpu: Gpu<'bound>,
bar: pci::Bar<'bound, BAR0_SIZE>,
@@ -107,10 +111,18 @@ fn probe<'bound>(
pin_init::pin_init_scope(move || {
dev_dbg!(pdev, "Probe Nova Core GPU driver.\n");
- pdev.enable_device_mem()?;
- pdev.set_master();
-
Ok(try_pin_init!(NovaCore {
+ #[cfg(CONFIG_PCI_IOV)]
+ // SAFETY:
+ // - probe has exclusive access before SR-IOV can be enabled;
+ // - the registration is pinned in driver data and is its first field;
+ // - no other registration is created for this device; and
+ // - the PCI adapter uses managed SR-IOV.
+ _vf_registration <- unsafe { pci::VfRegistration::new(pdev, Ok(())) },
+ _: {
+ pdev.enable_device_mem()?;
+ pdev.set_master();
+ },
bar: pdev.iomap_region_sized::<BAR0_SIZE>(0, c"nova-core/bar0")?,
bar1: {
let bar1_idx = bar1_resource_index(pdev)?;
@@ -149,4 +161,19 @@ fn probe<'bound>(
}))
})
}
+
+ #[cfg(CONFIG_PCI_IOV)]
+ fn sriov_configure<'bound>(
+ dev: &'bound pci::sriov::Device<Core<'_>>,
+ _this: Pin<&Self::Data<'bound>>,
+ nr_virtfn: i32,
+ ) -> Result<i32> {
+ if nr_virtfn == 0 {
+ dev.disable_sriov();
+ } else {
+ dev.enable_sriov(nr_virtfn)?;
+ }
+
+ Ok(nr_virtfn)
+ }
}
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH 14/14] Documentation: rust: explain SR-IOV PF data sharing with VFs
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (12 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 13/14] gpu: nova-core: publish typed SR-IOV PF data for VF drivers Zhi Wang
@ 2026-09-15 20:56 ` Zhi Wang
2026-09-16 11:10 ` [PATCH 00/14] Add Rust PCI SR-IOV support Danilo Krummrich
14 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-15 20:56 UTC (permalink / raw)
To: dakr, acourbot
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, jhubbard, ecourtney, cjia,
smitra, kjaju, alkumar, ankita, aniketa, kwankhede, targupta,
nova-gpu, linux-kernel, rust-for-linux, zhiwang, Zhi Wang
Rust and C VF drivers may need a narrow PF-owned interface without
gaining access to all private data belonging to the PF driver. The
typed PF registration and C FFI abstractions span PCI topology, type
checking, driver lifetime, and synchronization, which need a single
explanation.
The design embeds one pinned PF/VF contract inline in ordinary PCI
driver data. Rust consumers borrow it as `Pin<&T>`; C consumers borrow a
checked operations table and context. Both paths invoke the same Rust
implementation, while managed SR-IOV orders VF teardown before PF data
destruction.
Document the ownership and borrowing model, in-place initialization,
publication and teardown rules, FFI versioning and trampolines, locking
and asynchronous drain requirements, and limitations. Illustrate the
storage, lifetime, and Rust and C call paths with ASCII diagrams.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
Documentation/rust/index.rst | 1 +
Documentation/rust/pci-sriov-pf-data.rst | 330 +++++++++++++++++++++++
MAINTAINERS | 1 +
3 files changed, 332 insertions(+)
create mode 100644 Documentation/rust/pci-sriov-pf-data.rst
diff --git a/Documentation/rust/index.rst b/Documentation/rust/index.rst
index b78ed0efa784..a3e8924d678a 100644
--- a/Documentation/rust/index.rst
+++ b/Documentation/rust/index.rst
@@ -37,6 +37,7 @@ more details.
coding-guidelines
arch-support
testing
+ pci-sriov-pf-data
You can also find learning materials for Rust in its section in
:doc:`../process/kernel-docs`.
diff --git a/Documentation/rust/pci-sriov-pf-data.rst b/Documentation/rust/pci-sriov-pf-data.rst
new file mode 100644
index 000000000000..acc6f3582386
--- /dev/null
+++ b/Documentation/rust/pci-sriov-pf-data.rst
@@ -0,0 +1,330 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+.. _pci_rust_sriov_pf_data:
+
+===========================================
+Sharing Rust PF data with SR-IOV VF drivers
+===========================================
+
+An SR-IOV Physical Function (PF) and its Virtual Functions (VFs) are
+independent PCI devices. Their drivers may live in different modules, and a
+VF driver may be written in either Rust or C. Nevertheless, a VF often needs
+to invoke a small PF-owned interface for coordination with the physical
+device.
+
+This document describes how a Rust PF driver can publish pinned data for its
+VFs without exposing its complete private ``drvdata``. It supplements the
+general SR-IOV description in :doc:`../PCI/pci-iov-howto`.
+
+The idea
+========
+
+The PF publishes one deliberately chosen data object through
+``VfRegistration`` before enabling VFs. The registration and object are
+initialized inline in the PF's pinned driver data. A Rust VF receives a typed
+``Pin<&T>``. A C VF receives a checked ``struct rust_ffi`` descriptor whose
+operations call the same object through generated C ABI trampolines.
+
+The published object is an explicit PF/VF contract, not a replacement for PF
+``drvdata``. PCI supplies the route to the correct PF and orders driver
+teardown; the chosen object supplies only the data and operations that the PF
+intends to share. VFs borrow that object and never own it.
+
+There is no global interface registry. PCI topology selects the provider:
+the consumer is a VF and its ``physfn`` identifies the PF. The consumer path
+then checks that the selected PF published the expected Rust type or C ABI.
+
+::
+
+ PF driver data (pinned)
+ +--------------------------------+
+ | VfRegistration |
+ | +-----------------------------+
+ | | rust_ffi (at offset 0) |<----+
+ | | TypeId | pinned T |<--+ |
+ | +-----------------------------+ | |
+ | remaining PF data | | |
+ +--------------------------------+ | |
+ | |
+ PF struct pci_dev | |
+ +----------------------------+ | |
+ | vf_registration_data_rust -+-------+ |
+ +----------------------------+ |
+ |
+ Rust VF: TypeId check -> Pin<&T> -------+
+ C VF: token/ABI/size -> ops/context ----+
+
+The design separates four concerns:
+
+* PCI topology selects the actual PF for a VF.
+* Rust ``TypeId`` or the C FFI token and version check the requested
+ interface.
+* A managed device link and the inline registration determine the lifetime
+ of the borrow.
+* The published data type supplies any synchronization needed by concurrent
+ callers.
+
+The FFI descriptor does not hold a second copy of the PF data. Its
+``context`` points at the same pinned object that a Rust VF borrows directly.
+For example, both Rust and C VFs in the SR-IOV sample eventually call
+``PfApi::submit()``. The C path adds only an ABI trampoline and return-value
+conversion.
+
+Lifetime foundation
+===================
+
+The published pointer is borrowed; it is not a reference-counted handle.
+Its lifetime is based on managed SR-IOV and a persistent managed device link
+from each VF consumer to its PF supplier.
+
+The PCI core creates that link before the VF is allowed to probe. The link
+remains after a failed probe or a normal driver unbind so it also protects a
+later bind. The driver core consequently waits for an in-progress VF probe
+and unbinds every bound VF consumer before unbinding the PF supplier. On PF
+removal, managed SR-IOV also invokes ``sriov_configure(0)`` before the PF
+driver's remove callback if VFs are still enabled. VF unbind, including
+destruction of its driver data, completes before PF removal proceeds.
+
+::
+
+ PF probe
+ |
+ +-- initialize and pin VfRegistration and its data
+ |
+ +-- publish as the registration's final initialization step
+ |
+ PF probe returns and installs all PF driver data
+ |
+ sriov_configure(n) enables VFs
+ |
+ +-- PCI creates each VF
+ |
+ +-- PCI adds a managed link: VF consumer -> PF supplier
+ |
+ +-- VF probe borrows and uses the PF data
+ |
+ PF unbind is requested
+ |
+ +-- driver core unbinds every VF consumer
+ | |
+ | +-- VF remove stops and drains all PF calls
+ | |
+ | +-- VF driver data is destroyed
+ |
+ +-- if VFs remain, PCI invokes sriov_configure(0)
+ |
+ +-- disabling SR-IOV destroys VF devices and links
+ |
+ +-- PF remove runs
+ |
+ +-- PF driver data is destroyed
+ |
+ +-- registration disables any remaining VFs
+ |
+ +-- registration withdraws and drops the data
+
+Disabling VFs through ``sriov_numvfs`` follows the shorter part of the same
+ordering: VF drivers are removed before their VF devices disappear, while the
+PF driver remains bound and its registration remains published.
+
+If ``sriov_configure(0)`` does not disable all VFs during PF unbind, the PCI
+core warns and forcibly disables SR-IOV. The lifetime guarantee therefore
+does not depend on a successful driver callback.
+
+A successful VF probe may retain the borrow in its driver data for the
+duration of that binding. A failed probe must discard the borrow before
+returning. A VF remove callback must stop and drain all work that could use
+the PF data before the callback returns. These rules also apply to raw
+descriptor and context pointers retained by a C VF.
+
+Publishing PF data
+==================
+
+PF and VF drivers use the ordinary ``pci::Driver`` abstraction.
+``VfRegistration::new()`` publishes ``ForLt``-encoded data for Rust
+consumers. ``VfRegistration::new_ffi()`` publishes the same data and adds a
+C-callable FFI descriptor. Both return a pin-initializer rather than an
+allocated registration handle. The PF embeds it with ``<-`` in a
+``#[pin]`` field of its driver data::
+
+ #[pin_data]
+ struct PfData<'a> {
+ #[pin]
+ vf_registration: pci::VfRegistration<'a, MyApiForLt>,
+ // Fields borrowed by MyApi follow the registration.
+ }
+
+The constructor rejects a VF. On a conventional PCI function without an
+SR-IOV capability it creates an inactive registration, allowing one PF-side
+driver to continue supporting devices with and without SR-IOV.
+
+The constructors are unsafe because the PF driver establishes conditions
+that cannot be expressed entirely in the type system. A provider must:
+
+* Call the constructor during PCI probe, before any VF can be enabled. It
+ publishes only when that function is an SR-IOV PF.
+* Publish at most one registration for a PF.
+* Initialize it in the pinned PF driver data and do not forget that data.
+* Enable VFs only after PF probe has returned and installed that driver data.
+* Use managed SR-IOV so VF consumers are unbound before the registration is
+ dropped.
+* Declare it before any PF driver fields borrowed by the published object, so
+ the registration is dropped first.
+
+The published type must be ``Send + Sync`` for every lifetime because VFs may
+call it from different threads.
+
+The Rust PCI adapter opts drivers into managed SR-IOV. Its
+``sriov_configure`` callback receives a checked ``pci::sriov::Device`` and a
+pinned reference to the PF driver data. It enables or disables VFs with
+``enable_sriov()`` and ``disable_sriov()``.
+
+Rust VF consumers
+=================
+
+A Rust VF implements ``pci::Driver`` and explicitly requests PF data during
+probe. The accessor verifies that the PCI device is a VF, follows its PF
+relationship, checks that data was published, compares its ``TypeId``, and
+returns a pinned shared reference. The VF does not receive the PF's
+``pci::Device``, the PF driver object, or an untyped pointer.
+
+PF and VF drivers may be registered by separate modules. They must share the
+exact ``ForLt`` type that identifies the PF data. Defining look-alike types
+independently does not work because they have different ``TypeId`` values.
+When separate Rust crates are used, put the shared definition in a crate that
+both can import.
+
+``vf_registration_data()`` is the direct accessor for data encoded by
+``CovariantForLt``. ``vf_registration_data_with()`` supports invariant
+data; its higher-ranked closure prevents that data from escaping with a
+shortened lifetime. A domain-specific VF handle may store only the VF device
+and use the closure accessor for each operation, rather than retaining a
+separate raw PF pointer.
+
+If one module registers both drivers, register the VF driver first. This
+ensures that it is ready before the PF can enable VFs. PF-only and VF-only
+modules register their ordinary PCI drivers independently.
+
+C VF consumers
+==============
+
+The common C descriptor is declared in ``include/linux/rust_ffi.h``::
+
+ struct rust_ffi
+ +---------------------------------------------------+
+ | token | ABI version | ops size | ops | context |
+ +---------------------------------------------------+
+
+The token identifies the type and semantics of an operations table. It is
+not a PCI device identifier, a secret, an authorization check, a registry
+key, or a lifetime handle. PCI locates the PF before comparing the token.
+
+A driver-specific header defines the stable token, ABI version, and C
+operations structure shared by the Rust provider and C consumers. ABI
+compatibility follows these rules:
+
+* The major version must match exactly.
+* A provider's minor version must be at least the consumer's requested minor
+ version.
+* A minor-version update may only append operations to the table.
+* ``ops_size`` must cover the table prefix used by the consumer.
+
+The Rust provider implements ``interop::ffi::Abi`` and applies
+``#[ffi_vtable]`` to methods on its PF data. The macro verifies the complete
+bindgen operations-table layout and generates a private static operations
+table and private C ABI trampolines. It does not generate the C header. A
+trampoline recovers ``Pin<&T>`` from ``context`` and invokes the same Rust
+method used by Rust VFs. It converts ``Result<()>`` into zero or a negative
+errno, and ``Result<c_int>`` into its successful value or a negative errno.
+
+::
+
+ Rust VF C VF
+ vf_registration_data() borrow + ABI checks
+ + TypeId check |
+ | v
+ v ops->submit(context, id)
+ Pin<&PfApi> |
+ | generated trampoline
+ +------------------+-------------------+
+ |
+ v
+ PfApi::submit() -> Result
+ | |
+ Rust error C 0 or -errno
+
+A C VF includes ``linux/rust_ffi.h`` directly or through its driver-specific
+header and borrows the interface during probe. The essential call sequence
+is::
+
+ const struct my_pf_ops *ops;
+ const struct rust_ffi *ffi;
+ int ret;
+
+ ffi = pci_iov_borrow_rust_pf_data(vf, &my_token,
+ MY_ABI_MAJOR,
+ MY_ABI_MINOR,
+ sizeof(*ops));
+ if (IS_ERR(ffi))
+ return PTR_ERR(ffi);
+
+ ops = ffi->ops;
+ if (!ops->submit)
+ return -EOPNOTSUPP;
+
+ ret = ops->submit(ffi->context, pci_dev_id(vf));
+ if (ret)
+ return ret;
+
+The PCI helper verifies that the device is a VF, that its PF is bound to a
+managed SR-IOV driver, and that the descriptor satisfies the requested token,
+version, and size. It returns a borrow, so there is no matching ``put``
+operation. The size check does not prove that an individual operation is
+implemented, so the consumer must still check each callback it needs. The
+pointers must not be used after VF probe fails or after VF remove returns.
+
+Synchronization and teardown
+============================
+
+All VFs of a PF borrow the same object and may call it concurrently. Pinning
+keeps the object's address stable; it does not serialize access. The PF data
+must use interior synchronization appropriate for each operation, such as a
+mutex for sleepable methods or an atomic for a simple counter.
+
+The sample uses ``Mutex<u64>`` for its request count to demonstrate shared,
+synchronized PF state. The count is an internal implementation detail and
+is not returned through the C ABI. Both consumers see only whether
+``submit()`` succeeded.
+
+Document for every C operation whether it may sleep and which calling
+contexts are permitted. Before VF removal returns, cancel or flush any work
+that could still call an operation. Do not wait for such work while holding
+a lock that the operation itself needs.
+
+Disabling SR-IOV through sysfs removes VFs synchronously while holding the PF
+device lock. A VF remove path must not wait for an FFI operation that must
+acquire that same lock, or the two paths can deadlock.
+
+The managed device link supplies driver-presence and teardown ordering, but
+not runtime-PM integration. Operations that access powered PF hardware must
+arrange runtime PM separately.
+
+Examples
+========
+
+The complete examples are:
+
+* ``samples/rust/rust_driver_sriov.rs``: a Rust PF and Rust VF sharing a
+ pinned PF object with a mutex-protected counter;
+* ``samples/rust/rust_driver_sriov.h``: the C ABI token, version, and
+ operations table;
+* ``samples/rust/rust_driver_sriov_c_vf.c``: a C VF borrowing and calling the
+ Rust PF object; and
+* ``drivers/gpu/nova-core/driver.rs``: a regular Rust PCI driver publishing
+ unit data as a typed PF-readiness marker without a C ABI.
+
+The Rust and C sample VF drivers match the same device ID, so only one can
+bind to a given VF. Use the module ordering described by their Kconfig help
+or ``driver_override`` to select the C path deterministically.
+
+See also :doc:`../driver-api/device_link` for the general device-link model.
diff --git a/MAINTAINERS b/MAINTAINERS
index e03ebe44c341..922cfddcb2dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21137,6 +21137,7 @@ L: linux-pci@vger.kernel.org
S: Maintained
C: irc://irc.oftc.net/linux-pci
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pci/pci.git
+F: Documentation/rust/pci-sriov-pf-data.rst
F: rust/helpers/pci.c
F: rust/kernel/pci.rs
F: rust/kernel/pci/
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH 00/14] Add Rust PCI SR-IOV support
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
` (13 preceding siblings ...)
2026-09-15 20:56 ` [PATCH 14/14] Documentation: rust: explain SR-IOV PF data sharing with VFs Zhi Wang
@ 2026-09-16 11:10 ` Danilo Krummrich
14 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-16 11:10 UTC (permalink / raw)
To: Zhi Wang, alex, jgg, airlied
Cc: acourbot, yishaih, skolothumtho, kevin.tian, simona, ojeda,
alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin, a.hindborg,
aliceryhl, tmgross, jhubbard, ecourtney, cjia, smitra, kjaju,
alkumar, ankita, aniketa, kwankhede, targupta, nova-gpu,
linux-kernel, rust-for-linux, zhiwang
On Tue Sep 15, 2026 at 10:56 PM CEST, Zhi Wang wrote:
> Rust PCI drivers need to enable and disable SR-IOV and share selected
> PF-owned functionality with their VF drivers. The shared data must remain
> valid while a VF driver is bound, including when the VF driver is in C.
>
> This series builds on Peter Colberg's Rust PCI SR-IOV v3 series [1],
> extending it with typed PF registration data and C FFI support. The
> registration design follows Danilo Krummrich's Rust vGPU/VFIO PoC [2].
> As the discussion on Rust/VFIO support is still ongoing [3], this series
> supports both C and Rust sample VF drivers.
[...]
> [1] https://lore.kernel.org/rust-for-linux/20260303-rust-pci-sriov-v3-0-4443c35f0c88@redhat.com/
> [2] https://lore.kernel.org/nova-gpu/DLCRZLO06SIO.LS7TWQXIPZSQ@kernel.org/
> [3] https://lore.kernel.org/nova-gpu/20260914121217.70fa0d93@shazbot.org/
>
> drivers/pci/iov.c | 103 ++++++-
> drivers/pci/pci-driver.c | 3 +-
> drivers/pci/pci.h | 2 +
> include/linux/pci.h | 44 +++
> include/linux/rust_ffi.h | 88 ++++++
> rust/kernel/interop/ffi.rs | 252 ++++++++++++++++
> rust/kernel/pci.rs | 126 ++++++++
> rust/kernel/pci/sriov.rs | 355 +++++++++++++++++++++++
> rust/macros/ffi_vtable.rs | 148 ++++++++++
> rust/macros/lib.rs | 90 ++++++
Thanks for all the effort, Zhi! It is good to have a reference to see how this
turns out.
Unfortunately, this raises the same concerns I already shared in the other
thread in [2], plus additional ones:
(1) This FFI layer is already more code than would be needed to abstract the
vfio-pci driver API surface in Rust. In addition, it is also much more
complex and error prone.
(2) By creating a generic FFI layer to connect PF and VF drivers (which are
technically the same driver project), we incentivise writing cross
language drivers.
When the panthor DRM driver was considering to (re-)write parts in Rust I
very much objected to this, as it would have created an arbitrary FFI
boundary within DRM (which for obvious reasons would be a maintainance
nightmare).
Now, this is not exactly the same, as it is not an arbitrary FFI boundary;
it is well defined. But, it is still a workaround for a single driver
project being split up in a Rust and in a C portion.
The FFI surface is best kept at the subsystem level abstracting the driver
facing APIs, which provides a defined and comparatively stable API
surface.
For those reasons I don't want to add this to the PCI (Rust and C) core code,
i.e. I don't want to support a generic FFI layer between PF and VF drivers.
I really hope that we can find a way forward not having to end up doing this;
but if we really have to, we should stick to a nova project internal FFI layer
for this.
Thanks,
Danilo
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-09-16 11:10 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-15 20:56 [PATCH 00/14] Add Rust PCI SR-IOV support Zhi Wang
2026-09-15 20:56 ` [PATCH 01/14] PCI: add driver flag to opt into disabling SR-IOV on remove() Zhi Wang
2026-09-15 20:56 ` [PATCH 02/14] rust: pci: add {enable,disable}_sriov(), to control SR-IOV capability Zhi Wang
2026-09-15 20:56 ` [PATCH 03/14] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
2026-09-15 20:56 ` [PATCH 04/14] rust: pci: add bus callback sriov_configure(), to control SR-IOV from sysfs Zhi Wang
2026-09-15 20:56 ` [PATCH 05/14] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
2026-09-15 20:56 ` [PATCH 06/14] rust: pci: add is_physfn(), to check for PFs Zhi Wang
2026-09-15 20:56 ` [PATCH 07/14] rust: pci: add num_vf(), to return number of VFs Zhi Wang
2026-09-15 20:56 ` [PATCH 08/14] rust: pci: add typed SR-IOV PF registration data Zhi Wang
2026-09-15 20:56 ` [PATCH 09/14] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
2026-09-15 20:56 ` [PATCH 10/14] rust: add C-to-Rust FFI descriptors and trampolines Zhi Wang
2026-09-15 20:56 ` [PATCH 11/14] rust: pci: add C FFI support to typed SR-IOV PF registration data Zhi Wang
2026-09-15 20:56 ` [PATCH 12/14] samples: rust: add C SR-IOV VF driver that calls into a Rust PF driver Zhi Wang
2026-09-15 20:56 ` [PATCH 13/14] gpu: nova-core: publish typed SR-IOV PF data for VF drivers Zhi Wang
2026-09-15 20:56 ` [PATCH 14/14] Documentation: rust: explain SR-IOV PF data sharing with VFs Zhi Wang
2026-09-16 11:10 ` [PATCH 00/14] Add Rust PCI SR-IOV support Danilo Krummrich
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®