* [PATCH v3 00/10] Add Rust PCI SR-IOV support
@ 2026-09-30 10:18 Zhi Wang
2026-09-30 10:18 ` [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers Zhi Wang
` (9 more replies)
0 siblings, 10 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
Rust PCI drivers need to configure SR-IOV and let VF drivers borrow
selected PF-owned data. This series provides those operations and a
registration that keeps the shared data available until VF removal
completes.
This builds on Peter Colberg's Rust PCI SR-IOV series [1] and Danilo
Krummrich's typed PF registration design [2]. The PF/VF sample demonstrates
the interface using QEMU's 82576 emulation.
Following Danilo's review, drivers now implement separate sriov_enable()
and sriov_disable() callbacks. Each receives pinned driver data and a
token for the requested operation. Enabling VFs returns a guard that
disables them if later setup fails. Returning the guard successfully
leaves the VFs enabled, with VfRegistration owning their final teardown.
Drivers that do not share data with VFs can register ().
The series is based on driver-core-testing.
Changes since v2 [3]:
- Keep enable_sriov() and disable_sriov() internal to Device<CoreInternal>.
Add the callback tokens and rollback guard in a separate patch.
- Replace sriov_configure() in the Rust Driver trait with paired enable
and disable callbacks.
- Remove explicit SR-IOV cleanup from the PCI remove adapter and leave
it to VfRegistration.
- Move the SR-IOV implementation into CONFIG_PCI_IOV-gated pci/iov.rs.
- Remove the registration's published flag and raw-pointer accessors;
- Restrict num_vf() to the PCI core callback context to serialize it
with VF configuration.
- Remove the separate PF device accessor and match the auxiliary bus's
model.
- Update the sample for the split callbacks and Peter's email address
throughout the series.
[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/rust-for-linux/20260924190556.1620886-1-zhiw@nvidia.com/
John Hubbard (1):
rust: pci: add is_virtfn(), to check for VFs
Peter Colberg (6):
rust: pci: add internal SR-IOV enable and disable helpers
rust: pci: add vtable attribute to pci::Driver trait
rust: pci: add is_physfn(), to check for PFs
rust: pci: add num_vf(), to return number of VFs
rust: pci: add SR-IOV enable and disable callbacks
samples: rust: add Rust SR-IOV VF driver sample
Zhi Wang (3):
rust: pci: drop driver data before remove returns
rust: pci: add typed SR-IOV PF registration data
rust: pci: add SR-IOV enable and disable tokens
MAINTAINERS | 1 +
drivers/gpu/nova-core/driver.rs | 1 +
include/linux/pci.h | 7 +
rust/kernel/device.rs | 4 +-
rust/kernel/pci.rs | 103 +++++++
rust/kernel/pci/iov.rs | 413 ++++++++++++++++++++++++++
samples/rust/Kconfig | 11 +
samples/rust/Makefile | 1 +
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.rs | 249 ++++++++++++++++
12 files changed, 791 insertions(+), 2 deletions(-)
create mode 100644 rust/kernel/pci/iov.rs
create mode 100644 samples/rust/rust_driver_sriov.rs
base-commit: c15c5befd6d6150e92ab7c6c7fde1088e0f543e6
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 10:58 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 02/10] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
` (8 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
From: Peter Colberg <peter@colberg.org>
Wrap pci_enable_sriov() and pci_disable_sriov() on Device<CoreInternal>
for use by the SR-IOV callback tokens and their rollback guard.
Keep these operations internal so drivers can only enable or disable
VFs through the callback-scoped API introduced later in this series.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <peter@colberg.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 2 ++
rust/kernel/pci/iov.rs | 46 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
create mode 100644 rust/kernel/pci/iov.rs
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 3ec897709e89..b11a1d32c5db 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -34,6 +34,8 @@
mod id;
mod io;
+#[cfg(CONFIG_PCI_IOV)]
+mod iov;
mod irq;
pub use self::id::{
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
new file mode 100644
index 000000000000..c37ce5134673
--- /dev/null
+++ b/rust/kernel/pci/iov.rs
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Abstractions for PCI Single Root I/O Virtualization (SR-IOV) drivers.
+
+use super::Device;
+
+use crate::{
+ bindings,
+ device,
+ error::to_result,
+ prelude::*, //
+};
+
+impl Device<device::CoreInternal<'_>> {
+ /// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
+ /// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
+ #[expect(dead_code)]
+ pub(crate) fn enable_sriov(&self, nr_virtfn: c_int) -> 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 CoreInternal 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.
+ #[expect(dead_code)]
+ pub(crate) 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 CoreInternal 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()) };
+ }
+}
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 02/10] rust: pci: add vtable attribute to pci::Driver trait
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
2026-09-30 10:18 ` [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 10:18 ` [PATCH v3 03/10] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
` (7 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
From: Peter Colberg <peter@colberg.org>
Add the #[vtable] attribute to pci::Driver and its implementations to
support the optional sriov_enable() and sriov_disable() callbacks
introduced later in this series.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <peter@colberg.org>
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 cf3534dd47d4..47a599c9ea4f 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -63,6 +63,7 @@ pub(crate) struct NovaCore<'bound> {
]
);
+#[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 b11a1d32c5db..dcb0cdd0d426 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -274,6 +274,7 @@ macro_rules! pci_device_table {
/// ]
/// );
///
+/// #[vtable]
/// impl pci::Driver for MyDriver {
/// type IdInfo = ();
/// type Data<'bound> = Self;
@@ -289,6 +290,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>;
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 03/10] rust: pci: add is_virtfn(), to check for VFs
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
2026-09-30 10:18 ` [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers Zhi Wang
2026-09-30 10:18 ` [PATCH v3 02/10] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 10:18 ` [PATCH v3 04/10] rust: pci: add is_physfn(), to check for PFs Zhi Wang
` (6 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg,
Alistair Popple, Joel Fernandes
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 <peter@colberg.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci/iov.rs | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index c37ce5134673..272ed677f145 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -11,6 +11,15 @@
prelude::*, //
};
+impl Device {
+ /// Returns `true` if this device is a Virtual Function (VF).
+ #[inline]
+ 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 }
+ }
+}
+
impl Device<device::CoreInternal<'_>> {
/// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
/// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 04/10] rust: pci: add is_physfn(), to check for PFs
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (2 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 03/10] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 10:18 ` [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs Zhi Wang
` (5 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg,
Joel Fernandes
From: Peter Colberg <peter@colberg.org>
Add an internal method to check if a PCI device is a Physical Function
(PF). The SR-IOV implementation uses this role check to validate
configuration requests.
Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com>
Signed-off-by: Peter Colberg <peter@colberg.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci/iov.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index 272ed677f145..a4a23a9ea77a 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -12,6 +12,14 @@
};
impl Device {
+ /// 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]
pub fn is_virtfn(&self) -> bool {
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (3 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 04/10] rust: pci: add is_physfn(), to check for PFs Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 11:13 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 06/10] rust: pci: drop driver data before remove returns Zhi Wang
` (4 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
From: Peter Colberg <peter@colberg.org>
Add a method to return the number of Virtual Functions (VF) enabled for
a Physical Function (PF).
Expose the query in the PCI core callback context.
Signed-off-by: Peter Colberg <peter@colberg.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci/iov.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index a4a23a9ea77a..4f611d5d0b89 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -28,6 +28,14 @@ pub fn is_virtfn(&self) -> bool {
}
}
+impl Device<device::Core<'_>> {
+ /// Returns the number of Virtual Functions (VF) enabled for a Physical Function (PF).
+ pub fn num_vf(&self) -> i32 {
+ // SAFETY: `self.as_raw()` is valid and this call runs in the PCI core callback context.
+ unsafe { bindings::pci_num_vf(self.as_raw()) }
+ }
+}
+
impl Device<device::CoreInternal<'_>> {
/// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
/// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 06/10] rust: pci: drop driver data before remove returns
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (4 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 10:18 ` [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data Zhi Wang
` (3 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
Drop PCI private data before the remove callback returns, so its
destructors run within PCI core's removal context.
Call unbind while private data remains installed, then take and release
the data. Taking it clears drvdata, so post_unbind does not release it
again. All borrows must end before the data is removed.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/device.rs | 4 ++--
rust/kernel/pci.rs | 6 ++++++
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index 2291d85b6849..1f6bb58396e2 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -243,8 +243,8 @@ impl<Ctx: InternalBoundContext> Device<Ctx> {
///
/// # Safety
///
- /// - Must only be called after a preceding call to [`Device::set_drvdata`] and before the
- /// device is fully unbound.
+ /// - The data stored by [`Device::set_drvdata`] must still be installed and remain valid
+ /// for the returned borrow.
/// - The type `T` must match the type of the `ForeignOwnable` previously stored by
/// [`Device::set_drvdata`].
pub unsafe fn drvdata_borrow<T>(&self) -> Pin<&T> {
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index dcb0cdd0d426..1599b3a613d7 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -141,6 +141,12 @@ extern "C" fn remove_callback(pdev: *mut bindings::pci_dev) {
let data = unsafe { pdev.as_ref().drvdata_borrow::<T::Data<'_>>() };
T::unbind(pdev, data);
+
+ // SAFETY: The driver's unbind callback has returned, and no callbacks retain a borrow.
+ let data = unsafe { pdev.as_ref().drvdata_obtain::<T::Data<'_>>() };
+
+ // Drop private data before returning to PCI core, while its removal context is valid.
+ drop(data);
}
}
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (5 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 06/10] rust: pci: drop driver data before remove returns Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 13:59 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens Zhi Wang
` (2 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
Rust PF and VF drivers bind to separate PCI devices and may reside
in different modules. A VF driver that calls PF operations needs
typed access to the data exposed by the PF driver. This data must
remain valid until VF driver removal completes.
Add `VfRegistration` to register a pinned Rust object in the PF's
driver data. Add accessors for VF drivers to borrow this object after
checking the requested Rust type. During registration teardown,
disable SR-IOV and wait for VF remove callbacks to finish before
dropping the object.
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 | 7 ++
rust/kernel/pci.rs | 5 +
rust/kernel/pci/iov.rs | 202 ++++++++++++++++++++++++++++++++++++++++-
3 files changed, 213 insertions(+), 1 deletion(-)
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..9b6ae544469e 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -551,6 +551,13 @@ 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)
+ /*
+ * Private data owned by the PF's Rust driver, readable by VF drivers
+ * through the PCI VF registration data Rust abstraction.
+ */
+ 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 1599b3a613d7..8782e9b06e64 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -51,6 +51,8 @@
Extended,
Normal, //
};
+#[cfg(CONFIG_PCI_IOV)]
+pub use self::iov::VfRegistration;
pub use self::irq::{
IrqType,
IrqTypes,
@@ -331,6 +333,9 @@ fn probe<'bound>(
/// operations to gracefully tear down the device.
///
/// Otherwise, release operations for driver resources should be performed in `Drop`.
+ ///
+ /// For a PF with enabled VFs, `VfRegistration` disables SR-IOV when it is dropped. This
+ /// callback must leave resources accessed by VF drivers available until then.
fn unbind<'bound>(dev: &'bound Device<device::Core<'_>>, this: Pin<&Self::Data<'bound>>) {
let _ = (dev, this);
}
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index 4f611d5d0b89..3411a9101564 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -8,7 +8,15 @@
bindings,
device,
error::to_result,
- prelude::*, //
+ prelude::*,
+ types::{
+ CovariantForLt,
+ ForLt, //
+ }, //
+};
+use core::{
+ any::TypeId,
+ marker::PhantomPinned, //
};
impl Device {
@@ -69,3 +77,195 @@ pub(crate) fn disable_sriov(&self) {
unsafe { bindings::pci_disable_sriov(self.as_raw()) };
}
}
+
+/// Wrapper for VF registration data stored inside a [`VfRegistration`].
+///
+/// Stores a [`TypeId`] header (derived from `F`) followed by the pinned data,
+/// so that [`Device::vf_registration_data_with()`] can verify the type at
+/// runtime.
+#[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> {
+ /// Pin-initializer for the registration data.
+ fn new(data: impl PinInit<F::Of<'a>, Error>) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ type_id: TypeId::of::<F>(),
+ data <- data,
+ })
+ }
+}
+
+/// SR-IOV VF registration on a PF device.
+///
+/// Owns the registration data that VF drivers access via
+/// [`Device::vf_registration_data_with()`] and [`Device::vf_registration_data()`].
+///
+/// Drop disables SR-IOV before clearing the registration pointer and releasing the data.
+#[pin_data(PinnedDrop)]
+pub struct VfRegistration<'a, F: ForLt + 'static> {
+ pdev: &'a Device<device::Bound>,
+ #[pin]
+ inner: VfRegistrationData<'a, F>,
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+impl<'a, F: ForLt + 'static> VfRegistration<'a, F>
+where
+ for<'b> F::Of<'b>: Send + Sync,
+{
+ /// Create a new VF registration.
+ ///
+ /// Returns a pin-initializer so the registration can be embedded directly
+ /// in the PF driver's bus device private data.
+ ///
+ /// # Safety
+ ///
+ /// The returned registration must be embedded in the driver's bus device private data.
+ /// The initializer must run as part of the PF driver's probe.
+ /// The driver's `unbind` callback and the enclosing data's destructor must keep resources
+ /// accessed by VF drivers available until this registration has disabled SR-IOV.
+ pub unsafe fn new(
+ pdev: &'a Device<device::Core<'_>>,
+ data: impl PinInit<F::Of<'a>, Error> + 'a,
+ ) -> impl PinInit<Self, Error> + 'a {
+ let pdev: &'a Device<device::Bound> = pdev;
+ try_pin_init!(Self {
+ _: {
+ if pdev.is_virtfn() {
+ return Err(ENODEV);
+ }
+ },
+ pdev,
+ inner <- VfRegistrationData::new(data),
+ _pin: PhantomPinned,
+ _: {
+ // Check after initialization in case it registered another object for this PF.
+ // SAFETY: The caller runs this initializer during PF probing, before VF
+ // configuration callbacks can run.
+ if unsafe { bindings::pci_num_vf(pdev.as_raw()) } != 0 {
+ return Err(EBUSY);
+ }
+
+ // SAFETY: This initializer runs during PF probing, and no VFs are enabled.
+ if !unsafe { (*pdev.as_raw()).vf_registration_data_rust }.is_null() {
+ return Err(EBUSY);
+ }
+
+ // SAFETY: The data is initialized and pinned, the slot is unoccupied, and
+ // no VF can access it yet. No fallible work follows publication.
+ unsafe {
+ (*pdev.as_raw()).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>) {
+ // SAFETY: `pci_disable_sriov()` is safe to call on any `pci_dev`; it
+ // is a no-op if the device has no VFs enabled. When VFs are enabled,
+ // this blocks until all VF `remove()` callbacks complete.
+ unsafe { bindings::pci_disable_sriov(self.pdev.as_raw()) };
+
+ // SAFETY: The device is valid and all VF remove callbacks have completed, so no VF
+ // can access the registration pointer. The data remains alive until this returns.
+ unsafe { (*self.pdev.as_raw()).vf_registration_data_rust = core::ptr::null_mut() };
+ }
+}
+
+// SAFETY: The inner data is `Send` (enforced by the bound), and `&Device` is `Send + Sync`.
+unsafe impl<F: ForLt> Send for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send {}
+
+// SAFETY: The inner data is `Send + Sync`. `VfRegistration` doesn't expose mutable access;
+// VF drivers only read the data through an immutable pinned reference.
+unsafe impl<F: ForLt> Sync for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send + Sync {}
+
+impl Device<device::Bound> {
+ /// Internal helper: reads the `vf_registration_data_rust` pointer from the
+ /// PF, checks the `TypeId`, and returns a pinned reference.
+ ///
+ /// # Safety
+ ///
+ /// The data lifetime must be hidden behind a higher-ranked closure independently of the
+ /// reference lifetime, 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: This bound VF uses the `physfn` union field. PCI retains its PF until
+ // VF removal completes, and the PF keeps the registration installed until then.
+ let ptr = unsafe {
+ let pf = (*self.as_raw()).__bindgen_anon_1.physfn;
+ (*pf).vf_registration_data_rust
+ };
+
+ if ptr.is_null() {
+ return Err(ENOENT);
+ }
+
+ // SAFETY: The registration keeps its data installed until VF removal completes,
+ // including when probe initialization rolls back.
+ // `ptr` points to 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: TypeId check confirms the stored type matches `F`. The data
+ // is pinned inside the PF's driver data struct. Lifetime shortening
+ // from the PF's binding scope to `'_` is layout-compatible.
+ let data_ptr = unsafe {
+ let vfrd = ptr.cast::<VfRegistrationData<'_, F>>();
+ &raw const (*vfrd).data
+ };
+
+ // SAFETY: `data` is structurally pinned inside `VfRegistrationData`.
+ Ok(unsafe { Pin::new_unchecked(&*data_ptr) })
+ }
+
+ /// Access the VF registration data through a closure with an HRTB lifetime.
+ ///
+ /// `F` is the [`ForLt`](trait@ForLt) encoding of the data type. Returns
+ /// [`ENODEV`] if this is not a VF, [`ENOENT`] if no data was registered,
+ /// or [`EINVAL`] if `F` does not match the type registered by the PF.
+ ///
+ /// The reference is borrowed from this VF, while the registration data's lifetime remains
+ /// abstract so the closure cannot store shorter-lived references in invariant data.
+ pub fn vf_registration_data_with<'this, F: ForLt + 'static, R>(
+ &'this self,
+ f: impl for<'a> FnOnce(Pin<&'this F::Of<'a>>) -> R,
+ ) -> Result<R> {
+ // SAFETY: The closure is higher-ranked over the data lifetime, independently of `'this`.
+ // It cannot insert shorter-lived references, and the outer borrow cannot outlive this VF.
+ let pinned = unsafe { self.vf_registration_data_pinned::<F>()? };
+ Ok(f(pinned))
+ }
+
+ /// Returns a pinned reference to the VF registration data.
+ ///
+ /// Available only when `F` implements [`CovariantForLt`](trait@crate::types::CovariantForLt),
+ /// guaranteeing that shortening the PF data lifetime is sound.
+ ///
+ /// For non-covariant types, use [`Self::vf_registration_data_with()`].
+ ///
+ /// 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>() }
+ }
+}
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (6 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 14:25 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks Zhi Wang
2026-09-30 10:18 ` [PATCH v3 10/10] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
9 siblings, 1 reply; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
Enabling VFs can succeed before the driver's remaining setup fails.
Use a callback-scoped token to return an enabled guard that disables
SR-IOV when dropped, rolling back those VFs on a later error.
Add SriovEnable, SriovEnabled and SriovDisable for the split callbacks
introduced next. Tie each token to its PF and callback lifetime, and
limit the enabled count to the user's request. The PCI adapter disarms
the guard when accepting a successful enable callback.
Require a VfRegistration to own final VF teardown after that handoff.
Drivers that do not share data with VFs can register (). A disable
token permits explicit shutdown without forcing it on a rejected
request.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 7 +++-
rust/kernel/pci/iov.rs | 92 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 96 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 8782e9b06e64..8a87e2202a2c 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -52,7 +52,12 @@
Normal, //
};
#[cfg(CONFIG_PCI_IOV)]
-pub use self::iov::VfRegistration;
+pub use self::iov::{
+ SriovDisable,
+ SriovEnable,
+ SriovEnabled,
+ VfRegistration, //
+};
pub use self::irq::{
IrqType,
IrqTypes,
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index 3411a9101564..e608b2f1e70c 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -47,7 +47,6 @@ pub fn num_vf(&self) -> i32 {
impl Device<device::CoreInternal<'_>> {
/// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
/// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
- #[expect(dead_code)]
pub(crate) fn enable_sriov(&self, nr_virtfn: c_int) -> Result {
// SAFETY:
// `self.as_raw` returns a valid pointer to a `struct pci_dev`.
@@ -63,7 +62,6 @@ pub(crate) fn enable_sriov(&self, nr_virtfn: c_int) -> Result {
}
/// Disable the Single Root I/O Virtualization (SR-IOV) capability for this device.
- #[expect(dead_code)]
pub(crate) fn disable_sriov(&self) {
// SAFETY:
// `self.as_raw` returns a valid pointer to a `struct pci_dev`.
@@ -78,6 +76,96 @@ pub(crate) fn disable_sriov(&self) {
}
}
+/// Permission to enable VFs during a driver's `sriov_enable()` callback.
+///
+/// The PCI adapter creates this token for the PF being configured. It cannot be cloned or sent
+/// to another thread, and its lifetime is restricted to the callback. Enabling VFs consumes it.
+///
+/// The callback lifetime cannot be extended:
+///
+/// ```ignore,compile_fail
+/// use kernel::pci::SriovEnable;
+///
+/// fn escape(token: SriovEnable<'_>) -> SriovEnable<'static> {
+/// token
+/// }
+/// ```
+pub struct SriovEnable<'callback> {
+ pdev: &'callback Device<device::CoreInternal<'callback>>,
+ num_vfs: u32,
+}
+
+impl<'callback> SriovEnable<'callback> {
+ /// Returns the number of VFs requested for this callback.
+ pub fn num_vfs(&self) -> u32 {
+ self.num_vfs
+ }
+
+ /// Enables between one and the requested number of VFs.
+ ///
+ /// VF drivers can probe before this method returns, so the PF resources they access must
+ /// already be initialized. The returned guard disables the VFs if subsequent setup fails.
+ /// Return it from `sriov_enable()` to leave the VFs enabled on callback success.
+ pub fn enable(self, num_vfs: u32) -> Result<SriovEnabled<'callback>> {
+ if num_vfs == 0 || num_vfs > self.num_vfs {
+ return Err(EINVAL);
+ }
+ let num_vfs = c_int::try_from(num_vfs).map_err(|_| EOVERFLOW)?;
+
+ // SAFETY: The PF's driver data and registration remain installed throughout this callback.
+ // The registration owns final VF teardown after the enable guard is disarmed.
+ if unsafe { (*self.pdev.as_raw()).vf_registration_data_rust }.is_null() {
+ return Err(ENODEV);
+ }
+
+ self.pdev.enable_sriov(num_vfs)?;
+ Ok(SriovEnabled {
+ pdev: Some(self.pdev),
+ num_vfs,
+ })
+ }
+}
+
+/// Enabled VFs awaiting successful completion of `sriov_enable()`.
+pub struct SriovEnabled<'callback> {
+ pdev: Option<&'callback Device<device::CoreInternal<'callback>>>,
+ num_vfs: c_int,
+}
+
+impl SriovEnabled<'_> {
+ #[expect(dead_code)]
+ fn disarm(mut self) -> c_int {
+ self.pdev = None;
+ self.num_vfs
+ }
+}
+
+impl Drop for SriovEnabled<'_> {
+ fn drop(&mut self) {
+ if let Some(pdev) = self.pdev.take() {
+ pdev.disable_sriov();
+ }
+ }
+}
+
+/// Permission to disable VFs during a driver's `sriov_disable()` callback.
+///
+/// The PCI adapter creates this token for the PF being configured. It cannot be cloned or sent
+/// to another thread, and its lifetime is restricted to the callback. Dropping the token does
+/// not disable VFs, allowing the callback to reject a disable request.
+pub struct SriovDisable<'callback> {
+ pdev: &'callback Device<device::CoreInternal<'callback>>,
+}
+
+impl SriovDisable<'_> {
+ /// Disables all VFs and waits for their drivers to unbind.
+ ///
+ /// The PF resources used by VF drivers must remain available until this method returns.
+ pub fn disable(self) {
+ self.pdev.disable_sriov();
+ }
+}
+
/// Wrapper for VF registration data stored inside a [`VfRegistration`].
///
/// Stores a [`TypeId`] header (derived from `F`) followed by the pinned data,
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (7 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
2026-09-30 14:46 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 10/10] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
9 siblings, 1 reply; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
From: Peter Colberg <peter@colberg.org>
Allow Rust PCI drivers to configure SR-IOV through sriov_numvfs.
Split the C configure callback into sriov_enable() and sriov_disable(),
passing pinned driver data and a token for the requested operation.
Require drivers to implement both callbacks together.
Return the enabled guard's VF count on success and disarm its rollback.
On a disable request, verify that the driver has removed all VFs before
reporting success. VfRegistration handles VF removal during PF teardown;
these callbacks only handle explicit configuration requests.
Suggested-by: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Peter Colberg <peter@colberg.org>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/kernel/pci.rs | 83 ++++++++++++++++++++++++++++++++++++++++++
rust/kernel/pci/iov.rs | 62 +++++++++++++++++++++++++++----
2 files changed, 137 insertions(+), 8 deletions(-)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 8a87e2202a2c..87d2e637d902 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -87,12 +87,22 @@ unsafe fn register(
name: &'static CStr,
module: &'static ThisModule,
) -> Result {
+ #[cfg(CONFIG_PCI_IOV)]
+ build_assert!(
+ T::HAS_SRIOV_ENABLE == T::HAS_SRIOV_DISABLE,
+ "PCI drivers must implement both sriov_enable and sriov_disable"
+ );
+
// SAFETY: It's safe to set the fields of `struct pci_driver` on initialization.
unsafe {
(*pdrv.get()).name = name.as_char_ptr();
(*pdrv.get()).probe = Some(Self::probe_callback);
(*pdrv.get()).remove = Some(Self::remove_callback);
(*pdrv.get()).id_table = T::ID_TABLE.as_ptr();
+ #[cfg(CONFIG_PCI_IOV)]
+ if T::HAS_SRIOV_ENABLE {
+ (*pdrv.get()).sriov_configure = Some(Self::sriov_configure_callback);
+ }
}
// SAFETY: `pdrv` is guaranteed to be a valid `DriverType`.
@@ -344,6 +354,79 @@ fn probe<'bound>(
fn unbind<'bound>(dev: &'bound Device<device::Core<'_>>, this: Pin<&Self::Data<'bound>>) {
let _ = (dev, this);
}
+
+ /// Enables Single Root I/O Virtualization (SR-IOV) for a PF.
+ ///
+ /// Called when userspace writes a nonzero VF count to `sriov_numvfs`. The token carries the
+ /// requested count and permission to enable VFs on this PF. Return the guard obtained from
+ /// [`SriovEnable::enable()`] after completing setup; dropping it rolls back the enable.
+ ///
+ /// The PF must own a [`VfRegistration`] before enabling VFs. That registration disables
+ /// SR-IOV when the driver data is destroyed, while resources needed by VF drivers remain live.
+ /// Implement this callback and [`Self::sriov_disable()`] together, or implement neither.
+ ///
+ /// 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::*};
+ /// # struct Data;
+ /// fn sriov_enable<'callback>(
+ /// _dev: &pci::Device<Core<'_>>,
+ /// _this: Pin<&Data>,
+ /// token: pci::SriovEnable<'callback>,
+ /// ) -> Result<pci::SriovEnabled<'callback>> {
+ /// let num_vfs = token.num_vfs();
+ /// let enabled = token.enable(num_vfs)?;
+ /// // Complete any additional setup before returning the guard.
+ /// Ok(enabled)
+ /// }
+ /// ```
+ #[cfg(CONFIG_PCI_IOV)]
+ fn sriov_enable<'bound, 'callback>(
+ dev: &'bound Device<device::Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ token: SriovEnable<'callback>,
+ ) -> Result<SriovEnabled<'callback>> {
+ let _ = (dev, this, token);
+ build_error!(crate::error::VTABLE_DEFAULT_ERROR)
+ }
+
+ /// Disables all VFs of a PF in response to a userspace request.
+ ///
+ /// Called when userspace writes zero to `sriov_numvfs`. Call [`SriovDisable::disable()`]
+ /// while resources used by VF drivers are still available. Returning an error before calling
+ /// it leaves the VFs enabled. Returning success requires that all VFs have been disabled.
+ ///
+ /// This callback is not invoked during PF unbind; [`VfRegistration`] owns that teardown.
+ /// Implement this callback and [`Self::sriov_enable()`] together, or implement neither.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::{device::Core, pci, prelude::*};
+ /// # struct Data;
+ /// fn sriov_disable(
+ /// _dev: &pci::Device<Core<'_>>,
+ /// _this: Pin<&Data>,
+ /// token: pci::SriovDisable<'_>,
+ /// ) -> Result {
+ /// token.disable();
+ /// Ok(())
+ /// }
+ /// ```
+ #[cfg(CONFIG_PCI_IOV)]
+ fn sriov_disable<'bound>(
+ dev: &'bound Device<device::Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ token: SriovDisable<'_>,
+ ) -> Result {
+ let _ = (dev, this, token);
+ build_error!(crate::error::VTABLE_DEFAULT_ERROR)
+ }
}
/// The PCI device representation.
diff --git a/rust/kernel/pci/iov.rs b/rust/kernel/pci/iov.rs
index e608b2f1e70c..9d9bd3ac7025 100644
--- a/rust/kernel/pci/iov.rs
+++ b/rust/kernel/pci/iov.rs
@@ -2,12 +2,19 @@
//! Abstractions for PCI Single Root I/O Virtualization (SR-IOV) drivers.
-use super::Device;
+use super::{
+ Adapter,
+ Device,
+ Driver, //
+};
use crate::{
bindings,
device,
- error::to_result,
+ error::{
+ from_result,
+ to_result, //
+ },
prelude::*,
types::{
CovariantForLt,
@@ -22,7 +29,6 @@
impl Device {
/// 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 }
@@ -76,7 +82,7 @@ pub(crate) fn disable_sriov(&self) {
}
}
-/// Permission to enable VFs during a driver's `sriov_enable()` callback.
+/// Permission to enable VFs during a driver's [`Driver::sriov_enable()`] callback.
///
/// The PCI adapter creates this token for the PF being configured. It cannot be cloned or sent
/// to another thread, and its lifetime is restricted to the callback. Enabling VFs consumes it.
@@ -105,7 +111,7 @@ pub fn num_vfs(&self) -> u32 {
///
/// VF drivers can probe before this method returns, so the PF resources they access must
/// already be initialized. The returned guard disables the VFs if subsequent setup fails.
- /// Return it from `sriov_enable()` to leave the VFs enabled on callback success.
+ /// Return it from [`Driver::sriov_enable()`] to leave the VFs enabled on callback success.
pub fn enable(self, num_vfs: u32) -> Result<SriovEnabled<'callback>> {
if num_vfs == 0 || num_vfs > self.num_vfs {
return Err(EINVAL);
@@ -126,14 +132,13 @@ pub fn enable(self, num_vfs: u32) -> Result<SriovEnabled<'callback>> {
}
}
-/// Enabled VFs awaiting successful completion of `sriov_enable()`.
+/// Enabled VFs awaiting successful completion of [`Driver::sriov_enable()`].
pub struct SriovEnabled<'callback> {
pdev: Option<&'callback Device<device::CoreInternal<'callback>>>,
num_vfs: c_int,
}
impl SriovEnabled<'_> {
- #[expect(dead_code)]
fn disarm(mut self) -> c_int {
self.pdev = None;
self.num_vfs
@@ -148,7 +153,7 @@ fn drop(&mut self) {
}
}
-/// Permission to disable VFs during a driver's `sriov_disable()` callback.
+/// Permission to disable VFs during a driver's [`Driver::sriov_disable()`] callback.
///
/// The PCI adapter creates this token for the PF being configured. It cannot be cloned or sent
/// to another thread, and its lifetime is restricted to the callback. Dropping the token does
@@ -166,6 +171,47 @@ pub fn disable(self) {
}
}
+impl<T: Driver> Adapter<T> {
+ pub(super) 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<'_>>>() };
+
+ // 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(|| {
+ if !pdev.is_physfn() {
+ return Err(ENODEV);
+ }
+ if nr_virtfn == 0 {
+ T::sriov_disable(pdev, data, SriovDisable { pdev })?;
+ if pdev.num_vf() != 0 {
+ return Err(EBUSY);
+ }
+ Ok(0)
+ } else {
+ let num_vfs = u16::try_from(nr_virtfn).map_err(|_| EINVAL)?;
+ let enabled = T::sriov_enable(
+ pdev,
+ data,
+ SriovEnable {
+ pdev,
+ num_vfs: u32::from(num_vfs),
+ },
+ )?;
+ Ok(enabled.disarm())
+ }
+ })
+ }
+}
+
/// Wrapper for VF registration data stored inside a [`VfRegistration`].
///
/// Stores a [`TypeId`] header (derived from `F`) followed by the pinned data,
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v3 10/10] samples: rust: add Rust SR-IOV VF driver sample
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
` (8 preceding siblings ...)
2026-09-30 10:18 ` [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks Zhi Wang
@ 2026-09-30 10:18 ` Zhi Wang
9 siblings, 0 replies; 16+ messages in thread
From: Zhi Wang @ 2026-09-30 10:18 UTC (permalink / raw)
To: rust-for-linux, linux-pci, linux-kernel
Cc: dakr, aliceryhl, bhelgaas, kwilczynski, ojeda, boqun, gary,
bjorn3_gh, lossin, a.hindborg, tmgross, markus.probst, cjia,
smitra, ankita, aniketa, kwankhede, targupta, kjaju, alkumar,
acourbot, jhubbard, zhiwang, jgg, alex, Zhi Wang, Peter Colberg
From: Peter Colberg <peter@colberg.org>
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 <peter@colberg.org>
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 | 249 ++++++++++++++++++++++++++++++
4 files changed, 262 insertions(+)
create mode 100644 samples/rust/rust_driver_sriov.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index f7a513c4ba24..da6c34bc3dcd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21129,6 +21129,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..3f1809d7bc7b
--- /dev/null
+++ b/samples/rust/rust_driver_sriov.rs
@@ -0,0 +1,249 @@
+// 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_enable`.
+ _registration <- unsafe {
+ pci::VfRegistration::new(
+ pdev,
+ try_pin_init!(PfApi {
+ pdev,
+ requests <- new_mutex!(0),
+ }),
+ )
+ },
+ pdev: pdev.into(),
+ }))
+ })
+ }
+
+ fn sriov_enable<'bound, 'callback>(
+ dev: &'bound pci::Device<Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ token: pci::SriovEnable<'callback>,
+ ) -> Result<pci::SriovEnabled<'callback>> {
+ dev_info!(
+ this.pdev,
+ "Enable SR-IOV (PCI ID: {}, 0x{:x}).\n",
+ this.pdev.vendor_id(),
+ this.pdev.device_id()
+ );
+
+ let num_vfs = token.num_vfs();
+ let enabled = token.enable(num_vfs)?;
+ assert_eq!(dev.num_vf() as u32, num_vfs);
+ Ok(enabled)
+ }
+
+ fn sriov_disable<'bound>(
+ dev: &'bound pci::Device<Core<'_>>,
+ this: Pin<&Self::Data<'bound>>,
+ token: pci::SriovDisable<'_>,
+ ) -> Result {
+ dev_info!(
+ this.pdev,
+ "Disable SR-IOV (PCI ID: {}, 0x{:x}).\n",
+ this.pdev.vendor_id(),
+ this.pdev.device_id()
+ );
+
+ token.disable();
+ assert_eq!(dev.num_vf(), 0);
+ Ok(())
+ }
+}
+
+#[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",
+}
--
2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers
2026-09-30 10:18 ` [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers Zhi Wang
@ 2026-09-30 10:58 ` Danilo Krummrich
0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-30 10:58 UTC (permalink / raw)
To: Zhi Wang
Cc: rust-for-linux, linux-pci, linux-kernel, aliceryhl, bhelgaas,
kwilczynski, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg,
tmgross, markus.probst, cjia, smitra, ankita, aniketa, kwankhede,
targupta, kjaju, alkumar, acourbot, jhubbard, zhiwang, jgg, alex,
Peter Colberg
On Wed Sep 30, 2026 at 12:18 PM CEST, Zhi Wang wrote:
> +impl Device<device::CoreInternal<'_>> {
> + /// Enable the Single Root I/O Virtualization (SR-IOV) capability for this device,
> + /// where `nr_virtfn` is number of Virtual Functions (VF) to enable.
> + #[expect(dead_code)]
> + pub(crate) fn enable_sriov(&self, nr_virtfn: c_int) -> Result {
This can just be a u16; there are no implicit type conversions in Rust and the
PCIe SR-IOV spec defines this as 16bit register.
The conversion should be infallible, so you can just pass nr_virtfn.into().
> + // 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 CoreInternal device context inherits from the Bound device context,
> + // which guarantees that the PF device is bound to a driver.
// - `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 [`CoreInternal`] 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) })
> + }
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs
2026-09-30 10:18 ` [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs Zhi Wang
@ 2026-09-30 11:13 ` Danilo Krummrich
0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-30 11:13 UTC (permalink / raw)
To: Zhi Wang
Cc: rust-for-linux, linux-pci, linux-kernel, aliceryhl, bhelgaas,
kwilczynski, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg,
tmgross, markus.probst, cjia, smitra, ankita, aniketa, kwankhede,
targupta, kjaju, alkumar, acourbot, jhubbard, zhiwang, jgg, alex,
Peter Colberg
On Wed Sep 30, 2026 at 12:18 PM CEST, Zhi Wang wrote:
> +impl Device<device::Core<'_>> {
> + /// Returns the number of Virtual Functions (VF) enabled for a Physical Function (PF).
> + pub fn num_vf(&self) -> i32 {
For the reasons mentioned in patch 1, I'd make this return u16.
I suggest to do the converstion with .try_into().expect("pdev->sriov->num_VFs is u16").
> + // SAFETY: `self.as_raw()` is valid and this call runs in the PCI core callback context.
> + unsafe { bindings::pci_num_vf(self.as_raw()) }
> + }
> +}
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data
2026-09-30 10:18 ` [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data Zhi Wang
@ 2026-09-30 13:59 ` Danilo Krummrich
0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-30 13:59 UTC (permalink / raw)
To: Zhi Wang
Cc: rust-for-linux, linux-pci, linux-kernel, aliceryhl, bhelgaas,
kwilczynski, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg,
tmgross, markus.probst, cjia, smitra, ankita, aniketa, kwankhede,
targupta, kjaju, alkumar, acourbot, jhubbard, zhiwang, jgg, alex,
Peter Colberg
On Wed Sep 30, 2026 at 12:18 PM CEST, Zhi Wang wrote:
> diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
> index 1599b3a613d7..8782e9b06e64 100644
> --- a/rust/kernel/pci.rs
> +++ b/rust/kernel/pci.rs
> @@ -51,6 +51,8 @@
> Extended,
> Normal, //
> };
> +#[cfg(CONFIG_PCI_IOV)]
> +pub use self::iov::VfRegistration;
> pub use self::irq::{
> IrqType,
> IrqTypes,
> @@ -331,6 +333,9 @@ fn probe<'bound>(
> /// operations to gracefully tear down the device.
> ///
> /// Otherwise, release operations for driver resources should be performed in `Drop`.
> + ///
> + /// For a PF with enabled VFs, `VfRegistration` disables SR-IOV when it is dropped. This
> + /// callback must leave resources accessed by VF drivers available until then.
I don't think we need this comment, neither this callback (which I plan to
remove anyway) nor T::Data::drop() can remove resources that can still be
accessed by VF drivers in the first place.
You could have something like Mutex<Option<_>> of course, but that would be
intentional then.
> +impl<'a, F: ForLt + 'static> VfRegistrationData<'a, F> {
> + /// Pin-initializer for the registration data.
> + fn new(data: impl PinInit<F::Of<'a>, Error>) -> impl PinInit<Self, Error> {
I think we can accept any error type E?
> + try_pin_init!(Self {
> + type_id: TypeId::of::<F>(),
> + data <- data,
> + })
> + }
> +}
<snip>
> +impl<'a, F: ForLt + 'static> VfRegistration<'a, F>
> +where
> + for<'b> F::Of<'b>: Send + Sync,
> +{
> + /// Create a new VF registration.
> + ///
> + /// Returns a pin-initializer so the registration can be embedded directly
> + /// in the PF driver's bus device private data.
> + ///
> + /// # Safety
> + ///
> + /// The returned registration must be embedded in the driver's bus device private data.
I think this is the only requirement we need.
> + /// The initializer must run as part of the PF driver's probe.
> + /// The driver's `unbind` callback and the enclosing data's destructor must keep resources
> + /// accessed by VF drivers available until this registration has disabled SR-IOV.
This is not a safety requirement; it would require unsafe code to do this.
> + pub unsafe fn new(
> + pdev: &'a Device<device::Core<'_>>,
> + data: impl PinInit<F::Of<'a>, Error> + 'a,
> + ) -> impl PinInit<Self, Error> + 'a {
> + let pdev: &'a Device<device::Bound> = pdev;
> + try_pin_init!(Self {
> + _: {
> + if pdev.is_virtfn() {
> + return Err(ENODEV);
> + }
> + },
> + pdev,
> + inner <- VfRegistrationData::new(data),
> + _pin: PhantomPinned,
> + _: {
> + // Check after initialization in case it registered another object for this PF.
> + // SAFETY: The caller runs this initializer during PF probing, before VF
> + // configuration callbacks can run.
> + if unsafe { bindings::pci_num_vf(pdev.as_raw()) } != 0 {
Don't we have a safe pci::Device::num_vf() method already?
> + return Err(EBUSY);
> + }
> +
> + // SAFETY: This initializer runs during PF probing, and no VFs are enabled.
> + if !unsafe { (*pdev.as_raw()).vf_registration_data_rust }.is_null() {
> + return Err(EBUSY);
> + }
Those two checks can go into the first _: block, no?
> +
> + // SAFETY: The data is initialized and pinned, the slot is unoccupied, and
> + // no VF can access it yet. No fallible work follows publication.
> + unsafe {
> + (*pdev.as_raw()).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>) {
> + // SAFETY: `pci_disable_sriov()` is safe to call on any `pci_dev`; it
> + // is a no-op if the device has no VFs enabled. When VFs are enabled,
> + // this blocks until all VF `remove()` callbacks complete.
> + unsafe { bindings::pci_disable_sriov(self.pdev.as_raw()) };
> +
> + // SAFETY: The device is valid and all VF remove callbacks have completed, so no VF
> + // can access the registration pointer. The data remains alive until this returns.
> + unsafe { (*self.pdev.as_raw()).vf_registration_data_rust = core::ptr::null_mut() };
> + }
> +}
> +
> +// SAFETY: The inner data is `Send` (enforced by the bound), and `&Device` is `Send + Sync`.
> +unsafe impl<F: ForLt> Send for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send {}
> +
> +// SAFETY: The inner data is `Send + Sync`. `VfRegistration` doesn't expose mutable access;
> +// VF drivers only read the data through an immutable pinned reference.
> +unsafe impl<F: ForLt> Sync for VfRegistration<'_, F> where for<'a> F::Of<'a>: Send + Sync {}
> +
> +impl Device<device::Bound> {
> + /// Internal helper: reads the `vf_registration_data_rust` pointer from the
> + /// PF, checks the `TypeId`, and returns a pinned reference.
> + ///
> + /// # Safety
> + ///
> + /// The data lifetime must be hidden behind a higher-ranked closure independently of the
> + /// reference lifetime, 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);
> + }
We don't want to exclude PF drivers to access this. Have a look at
drivers/gpu/nova-core/api.rs, it makes sense for the PF driver to provide a
helper API around this.
> + // SAFETY: This bound VF uses the `physfn` union field. PCI retains its PF until
> + // VF removal completes, and the PF keeps the registration installed until then.
> + let ptr = unsafe {
> + let pf = (*self.as_raw()).__bindgen_anon_1.physfn;
> + (*pf).vf_registration_data_rust
> + };
> +
> + if ptr.is_null() {
> + return Err(ENOENT);
Maybe ENODEV?
> + }
> +
> + // SAFETY: The registration keeps its data installed until VF removal completes,
> + // including when probe initialization rolls back.
> + // `ptr` points to 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: TypeId check confirms the stored type matches `F`. The data
> + // is pinned inside the PF's driver data struct. Lifetime shortening
> + // from the PF's binding scope to `'_` is layout-compatible.
> + let data_ptr = unsafe {
> + let vfrd = ptr.cast::<VfRegistrationData<'_, F>>();
> + &raw const (*vfrd).data
> + };
> +
> + // SAFETY: `data` is structurally pinned inside `VfRegistrationData`.
> + Ok(unsafe { Pin::new_unchecked(&*data_ptr) })
> + }
> +
> + /// Access the VF registration data through a closure with an HRTB lifetime.
> + ///
> + /// `F` is the [`ForLt`](trait@ForLt) encoding of the data type. Returns
> + /// [`ENODEV`] if this is not a VF, [`ENOENT`] if no data was registered,
> + /// or [`EINVAL`] if `F` does not match the type registered by the PF.
> + ///
> + /// The reference is borrowed from this VF, while the registration data's lifetime remains
> + /// abstract so the closure cannot store shorter-lived references in invariant data.
> + pub fn vf_registration_data_with<'this, F: ForLt + 'static, R>(
> + &'this self,
> + f: impl for<'a> FnOnce(Pin<&'this F::Of<'a>>) -> R,
> + ) -> Result<R> {
> + // SAFETY: The closure is higher-ranked over the data lifetime, independently of `'this`.
> + // It cannot insert shorter-lived references, and the outer borrow cannot outlive this VF.
> + let pinned = unsafe { self.vf_registration_data_pinned::<F>()? };
> + Ok(f(pinned))
> + }
> +
> + /// Returns a pinned reference to the VF registration data.
> + ///
> + /// Available only when `F` implements [`CovariantForLt`](trait@crate::types::CovariantForLt),
> + /// guaranteeing that shortening the PF data lifetime is sound.
> + ///
> + /// For non-covariant types, use [`Self::vf_registration_data_with()`].
> + ///
> + /// 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>() }
> + }
> +}
> --
> 2.53.0
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens
2026-09-30 10:18 ` [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens Zhi Wang
@ 2026-09-30 14:25 ` Danilo Krummrich
0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-30 14:25 UTC (permalink / raw)
To: Zhi Wang
Cc: rust-for-linux, linux-pci, linux-kernel, aliceryhl, bhelgaas,
kwilczynski, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg,
tmgross, markus.probst, cjia, smitra, ankita, aniketa, kwankhede,
targupta, kjaju, alkumar, acourbot, jhubbard, zhiwang, jgg, alex,
Peter Colberg
On Wed Sep 30, 2026 at 12:18 PM CEST, Zhi Wang wrote:
> +pub struct SriovEnable<'callback> {
I understand why you chose 'callback, but I think just 'a is good enough.
> + pdev: &'callback Device<device::CoreInternal<'callback>>,
> + num_vfs: u32,
Please see patch 1.
> +}
> +
> +impl<'callback> SriovEnable<'callback> {
> + /// Returns the number of VFs requested for this callback.
> + pub fn num_vfs(&self) -> u32 {
> + self.num_vfs
> + }
> +
> + /// Enables between one and the requested number of VFs.
> + ///
> + /// VF drivers can probe before this method returns, so the PF resources they access must
> + /// already be initialized. The returned guard disables the VFs if subsequent setup fails.
> + /// Return it from `sriov_enable()` to leave the VFs enabled on callback success.
> + pub fn enable(self, num_vfs: u32) -> Result<SriovEnabled<'callback>> {
> + if num_vfs == 0 || num_vfs > self.num_vfs {
> + return Err(EINVAL);
> + }
> + let num_vfs = c_int::try_from(num_vfs).map_err(|_| EOVERFLOW)?;
> +
> + // SAFETY: The PF's driver data and registration remain installed throughout this callback.
> + // The registration owns final VF teardown after the enable guard is disarmed.
> + if unsafe { (*self.pdev.as_raw()).vf_registration_data_rust }.is_null() {
> + return Err(ENODEV);
> + }
> +
> + self.pdev.enable_sriov(num_vfs)?;
> + Ok(SriovEnabled {
> + pdev: Some(self.pdev),
> + num_vfs,
> + })
> + }
> +}
> +
> +/// Enabled VFs awaiting successful completion of `sriov_enable()`.
> +pub struct SriovEnabled<'callback> {
> + pdev: Option<&'callback Device<device::CoreInternal<'callback>>>,
This doesn't need to be an Option.
> + num_vfs: c_int,
> +}
> +
> +impl SriovEnabled<'_> {
> + #[expect(dead_code)]
> + fn disarm(mut self) -> c_int {
> + self.pdev = None;
> + self.num_vfs
Instead of the Option dance, you can use ManuallyDrop to prevent the destructor
from running.
> + }
> +}
> +
> +impl Drop for SriovEnabled<'_> {
> + fn drop(&mut self) {
> + if let Some(pdev) = self.pdev.take() {
> + pdev.disable_sriov();
> + }
> + }
> +}
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks
2026-09-30 10:18 ` [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks Zhi Wang
@ 2026-09-30 14:46 ` Danilo Krummrich
0 siblings, 0 replies; 16+ messages in thread
From: Danilo Krummrich @ 2026-09-30 14:46 UTC (permalink / raw)
To: Zhi Wang
Cc: rust-for-linux, linux-pci, linux-kernel, aliceryhl, bhelgaas,
kwilczynski, ojeda, boqun, gary, bjorn3_gh, lossin, a.hindborg,
tmgross, markus.probst, cjia, smitra, ankita, aniketa, kwankhede,
targupta, kjaju, alkumar, acourbot, jhubbard, zhiwang, jgg, alex,
Peter Colberg
On Wed Sep 30, 2026 at 12:18 PM CEST, Zhi Wang wrote:
> + #[cfg(CONFIG_PCI_IOV)]
> + fn sriov_enable<'bound, 'callback>(
I'd just call it 'a.
> + dev: &'bound Device<device::Core<'_>>,
This is the same lifetime, as the token stores Device<Core<'_>>, so I'd also
make it 'a.
> + this: Pin<&Self::Data<'bound>>,
Let's call this 'data', unbind() has 'this' for historical reasons.
> + token: SriovEnable<'callback>,
> + ) -> Result<SriovEnabled<'callback>> {
> + let _ = (dev, this, token);
> + build_error!(crate::error::VTABLE_DEFAULT_ERROR)
> + }
<snip>
> + #[cfg(CONFIG_PCI_IOV)]
> + fn sriov_disable<'bound>(
> + dev: &'bound Device<device::Core<'_>>,
> + this: Pin<&Self::Data<'bound>>,
> + token: SriovDisable<'_>,
> + ) -> Result {
> + let _ = (dev, this, token);
> + build_error!(crate::error::VTABLE_DEFAULT_ERROR)
> + }
> }
<snip>
> +impl<T: Driver> Adapter<T> {
> + pub(super) 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<'_>>>() };
> +
> + // 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(|| {
> + if !pdev.is_physfn() {
> + return Err(ENODEV);
> + }
I don't think this can ever happen.
> + if nr_virtfn == 0 {
> + T::sriov_disable(pdev, data, SriovDisable { pdev })?;
> + if pdev.num_vf() != 0 {
> + return Err(EBUSY);
> + }
> + Ok(0)
> + } else {
> + let num_vfs = u16::try_from(nr_virtfn).map_err(|_| EINVAL)?;
> + let enabled = T::sriov_enable(
> + pdev,
> + data,
> + SriovEnable {
> + pdev,
> + num_vfs: u32::from(num_vfs),
> + },
> + )?;
> + Ok(enabled.disarm())
> + }
> + })
> + }
> +}
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-09-30 14:46 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 10:18 [PATCH v3 00/10] Add Rust PCI SR-IOV support Zhi Wang
2026-09-30 10:18 ` [PATCH v3 01/10] rust: pci: add internal SR-IOV enable and disable helpers Zhi Wang
2026-09-30 10:58 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 02/10] rust: pci: add vtable attribute to pci::Driver trait Zhi Wang
2026-09-30 10:18 ` [PATCH v3 03/10] rust: pci: add is_virtfn(), to check for VFs Zhi Wang
2026-09-30 10:18 ` [PATCH v3 04/10] rust: pci: add is_physfn(), to check for PFs Zhi Wang
2026-09-30 10:18 ` [PATCH v3 05/10] rust: pci: add num_vf(), to return number of VFs Zhi Wang
2026-09-30 11:13 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 06/10] rust: pci: drop driver data before remove returns Zhi Wang
2026-09-30 10:18 ` [PATCH v3 07/10] rust: pci: add typed SR-IOV PF registration data Zhi Wang
2026-09-30 13:59 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 08/10] rust: pci: add SR-IOV enable and disable tokens Zhi Wang
2026-09-30 14:25 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 09/10] rust: pci: add SR-IOV enable and disable callbacks Zhi Wang
2026-09-30 14:46 ` Danilo Krummrich
2026-09-30 10:18 ` [PATCH v3 10/10] samples: rust: add Rust SR-IOV VF driver sample Zhi Wang
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®