* [PATCH v7 0/1] Rust PCI capability infrastructure and SR-IOV support
@ 2026-08-04 16:16 Zhi Wang
2026-08-04 16:16 ` [PATCH v7 1/1] rust: pci: add extended capability " Zhi Wang
0 siblings, 1 reply; 2+ messages in thread
From: Zhi Wang @ 2026-08-04 16:16 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, joelagnelf, jhubbard, zhiwang, Zhi Wang, daniel.almeida,
tamird, work
This is a follow-up to v6 [10].
This patch has been used in the Boot GSP with vGPU enabled series [6].
The patch defines an ExtCapability trait that associates an extended
capability ID with its register layout. The generic
ConfigSpace::find_ext_capability() finder locates the capability, bounds
it at the next capability or the end of extended configuration space,
and projects the ConfigSpace view to the requested layout. It returns
None when the capability is absent and propagates errors encountered
while constructing the view. This lets the existing I/O projection and
access macros operate on capability registers.
ExtSriovRegs provides the SR-IOV register layout.
ExtSriovCapability::vf_bars() validates the six raw VF BAR slots and
returns an iterator over logical BARs. The iterator handles the different
slot widths of 32-bit and 64-bit BARs internally and yields decoded
ExtSriovVfBar values containing the address and width. A typed bitfield
decodes each low DWORD while the register layout remains an array of raw
u32 values. ExtSriovCapability remains as a convenience alias.
Changes since v6:
- Changed ConfigSpace::find_ext_capability() to return
Result<Option<...>>, using None rather than ENODEV when the capability
is absent. (Gary)
- Made calculate_ext_cap_size() propagate errors instead of treating a
failed read as the end of capability. (Alex)
- Replaced indexed VF BAR access with an iterator so callers cannot select
the high DWORD of a 64-bit BAR. (Gary, Alex)
- Validated all six VF BAR slots before iteration, made normal iterator
exhaustion return None, and logged invalid BAR encodings before returning
EINVAL. (Gary)
- Decoded VF BAR low DWORDs through a typed bitfield while retaining u32 in
ExtSriovRegs for entries that may be 64-bit BAR high DWORDs. (Alex)
- Made ExtSriovVfBar fields public and documented, removed their trivial
getters, used the ExtSriovCapability alias for its impl block, and applied
the suggested local cleanups. (Alex)
- Updated the doctest for the optional finder result and VF BAR iterator.
(Zhi)
- Added #[inline] to the ExtCapId::as_raw() abstraction method. (Sashiko)
- Rebased onto the latest drm-rust-next. (Zhi)
Changes since v5:
- Removed the unused ConfigSpace<Region<0>> offset() and size() inherent
methods; ConfigSpace already provides size through the Io trait.
(Sashiko, Zhi)
- Removed the doctest write to the SR-IOV NumVFs register, avoiding an
example that bypasses PCI core SR-IOV state management. (Sashiko)
- Corrected Function Dependency Link to an 8-bit field followed by its
reserved byte, matching the PCIe SR-IOV register layout. (Sashiko)
Changes since v4:
- Replaced the separate is_vf_bar_64bit() and read_vf_bar64() helpers
with read_vf_bar(), returning a decoded ExtSriovVfBar. (Zhi)
- Moved memory BAR attribute stripping into the PCI abstraction and used
named PCI attribute definitions rather than an open-coded mask. (Zhi)
- Exposed the next logical BAR slot so callers can walk mixed 32-bit and
64-bit VF BAR layouts without duplicating slot arithmetic. (Zhi)
- Updated the doctest and PCI exports for the decoded BAR API. (Zhi)
Changes since v3:
- Replaced the custom ExtCapability<T> I/O wrapper with the existing
ConfigSpace view infrastructure. (Alex)
- Reused ExtCapability as a trait carrying the capability ID, and made
ConfigSpace::find_ext_capability() generic over register layouts.
(Alex)
- Removed public cast_sized() and unused find_next_ext_capability().
(Alex)
- Kept capability construction in the generic finder and documented
calculate_ext_cap_size(). (Alex)
- Used PCI_SRIOV_NUM_BARS rather than a literal VF BAR count.
(Alex, Zhi)
- Added is_vf_bar_64bit() and made read_vf_bar64() reject BARs that are
not 64-bit memory BARs. (Alex, Zhi)
- Kept indexed VF BAR helpers because the Nova user accesses fixed BAR
slots rather than iterating over them. (Alex)
- Adapted the implementation and doctest to the current ConfigSpace I/O
APIs. (Zhi)
Changes since RFC v2:
- Hardened calculate_ext_cap_size() against corrupt capability lists.
(Zhi)
- Added // INVARIANT: comments at all ExtCapability construction sites
(make_ext_capability and cast_sized). (Zhi)
- Added #[inline] to small forwarding methods (find, read_vf_bar64).
(Zhi)
Changes since RFC:
- Rebased on io_projection branch, using Gary's Io/IoCapable traits.
(Gary)
- ExtCapability implements Io and delegates IoCapable to ConfigSpace
instead of duplicating config read/write logic. (Gary)
- Dropped the fallible I/O patch (now upstream in this tree). (Zhi)
- Added Rust helper for PCI_EXT_CAP_NEXT() macro. (Zhi)
- Replaced raw `as` casts with From conversions where possible. (Zhi)
- Renamed SriovRegs/SriovCapability to ExtSriovRegs/ExtSriovCapability.
(Zhi)
[1] https://lore.kernel.org/rust-for-linux/20260409185254.3869808-1-zhiw@nvidia.com/
[2] https://lore.kernel.org/rust-for-linux/DHRTUAF52GNI.1J98TSAG1LS6Q@nvidia.com/
[3] https://lore.kernel.org/rust-for-linux/DI2SL4G5INLY.2W1IFTR081ID3@nvidia.com/
[4] https://lore.kernel.org/rust-for-linux/20260225180449.1813833-1-zhiw@nvidia.com/
[5] https://lore.kernel.org/rust-for-linux/20260323153807.1360705-1-gary@kernel.org/
[6] https://lore.kernel.org/rust-for-linux/20260313165336.935771-1-zhiw@nvidia.com/
[7] https://lore.kernel.org/rust-for-linux/20260714165827.2937960-1-zhiw@nvidia.com/
[8] https://lore.kernel.org/rust-for-linux/20260730180349.771719-1-zhiw@nvidia.com/
[9] https://sashiko.dev/#/patchset/20260730180349.771719-2-zhiw@nvidia.com?part=1
[10] https://lore.kernel.org/rust-for-linux/20260730182954.783568-1-zhiw@nvidia.com/
[11] https://lore.kernel.org/rust-for-linux/DKC4MR1ZBQMP.3L7X2C96OJLFU@garyguo.net/
[12] https://lore.kernel.org/rust-for-linux/DKCOUE9DRG5R.2VAVA78UTVKST@nvidia.com/
[13] https://lore.kernel.org/rust-for-linux/DKCRGMZJXO6X.1SWGREC78DH0W@garyguo.net/
[14] https://lore.kernel.org/linux-pci/20260730183913.B00061F000E9@smtp.kernel.org/
Zhi Wang (1):
rust: pci: add extended capability and SR-IOV support
rust/helpers/pci.c | 5 +
rust/kernel/pci.rs | 8 ++
rust/kernel/pci/cap.rs | 317 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 330 insertions(+)
create mode 100644 rust/kernel/pci/cap.rs
base-commit: 44e7e7f7cffb10a93bb88e7cb59b7b8b3e2deb1c
--
2.53.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH v7 1/1] rust: pci: add extended capability and SR-IOV support
2026-08-04 16:16 [PATCH v7 0/1] Rust PCI capability infrastructure and SR-IOV support Zhi Wang
@ 2026-08-04 16:16 ` Zhi Wang
0 siblings, 0 replies; 2+ messages in thread
From: Zhi Wang @ 2026-08-04 16:16 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, joelagnelf, jhubbard, zhiwang, Zhi Wang, daniel.almeida,
tamird, work
Rust PCI drivers have no typed interface for locating and accessing PCIe
extended capabilities.
The SR-IOV extended capability describes VF topology and VF BARs. Expose
this information through the Rust PCI abstraction so drivers can use the
existing typed configuration-space accessors instead of raw bindings.
Define ExtCapability to associate a capability ID with a register layout,
and add ConfigSpace::find_ext_capability() to locate and project that
layout. Bound the view at the next capability or the end of extended
configuration space. Add ExtSriovRegs and a decoded VF BAR iterator that
reads and validates all six VF BAR register slots up front, yields decoded
BAR addresses and widths in logical order, and keeps the raw
configuration-space slot advancement internal. Since PCI_EXT_CAP_NEXT() is
a function-like macro, expose it through a Rust helper.
Link: https://lore.kernel.org/rust-for-linux/20260730182954.783568-1-zhiw@nvidia.com/
Cc: Alexandre Courbot <acourbot@nvidia.com>
Cc: Gary Guo <gary@garyguo.net>
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
---
rust/helpers/pci.c | 5 +
rust/kernel/pci.rs | 8 ++
rust/kernel/pci/cap.rs | 317 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 330 insertions(+)
create mode 100644 rust/kernel/pci/cap.rs
diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c
index 4ebf256dff23..b946b14d79e4 100644
--- a/rust/helpers/pci.c
+++ b/rust/helpers/pci.c
@@ -24,6 +24,11 @@ __rust_helper bool rust_helper_dev_is_pci(const struct device *dev)
return dev_is_pci(dev);
}
+__rust_helper u32 rust_helper_pci_ext_cap_next(u32 header)
+{
+ return PCI_EXT_CAP_NEXT(header);
+}
+
#ifndef CONFIG_PCI_IOV
__rust_helper unsigned int
rust_helper_pci_sriov_get_totalvfs(struct pci_dev *pdev)
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 9f19ccd5905c..008c2770a3f3 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -32,10 +32,18 @@
},
};
+mod cap;
mod id;
mod io;
mod irq;
+pub use self::cap::{
+ ExtCapId,
+ ExtCapability,
+ ExtSriovCapability,
+ ExtSriovRegs,
+ ExtSriovVfBar, //
+};
pub use self::id::{
Class,
ClassMask,
diff --git a/rust/kernel/pci/cap.rs b/rust/kernel/pci/cap.rs
new file mode 100644
index 000000000000..c49de8682f6d
--- /dev/null
+++ b/rust/kernel/pci/cap.rs
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! PCI extended capability support.
+
+use super::{
+ io::ConfigSpaceBackend,
+ ConfigSpace,
+ Extended, //
+};
+use crate::{
+ bindings,
+ io::{
+ Io,
+ IoBackend,
+ Region, //
+ },
+ num::Bounded,
+ prelude::*,
+};
+
+/// Number of VF BAR register slots in an SR-IOV capability.
+// CAST: `PCI_SRIOV_NUM_BARS` is 6, which fits in `usize`.
+const NUM_VF_BARS: usize = bindings::PCI_SRIOV_NUM_BARS as usize;
+
+/// PCI extended capability IDs.
+#[repr(u16)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ExtCapId {
+ /// Single Root I/O Virtualization.
+ // CAST: `PCI_EXT_CAP_ID_SRIOV` is `0x10`, which fits in `u16`.
+ Sriov = bindings::PCI_EXT_CAP_ID_SRIOV as u16,
+}
+
+impl ExtCapId {
+ #[inline]
+ fn as_raw(self) -> u16 {
+ self as u16
+ }
+}
+
+/// A typed PCI extended capability register layout.
+///
+/// Implementors describe the register layout of one extended capability. The layout must start at
+/// the extended capability header, and [`Self::ID`] must identify that layout.
+pub trait ExtCapability: FromBytes + IntoBytes {
+ /// PCI extended capability ID for this register layout.
+ const ID: ExtCapId;
+}
+
+impl<'a> ConfigSpace<'a, Extended> {
+ /// Finds and projects an extended capability into its typed register layout.
+ ///
+ /// Returns [`None`] if the device does not implement the capability.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use kernel::pci;
+ ///
+ /// fn probe_sriov(
+ /// pdev: &pci::Device<kernel::device::Bound>,
+ /// ) -> Result<(), kernel::error::Error> {
+ /// let Some(sriov) = pdev
+ /// .config_space_extended()?
+ /// .find_ext_capability::<pci::ExtSriovRegs>()?
+ /// else {
+ /// return Ok(());
+ /// };
+ ///
+ /// let total_vfs = kernel::io_read!(sriov, .total_vfs);
+ /// let vf_offset = kernel::io_read!(sriov, .vf_offset);
+ /// let mut vf_bars = sriov.vf_bars()?;
+ /// let bar0 = vf_bars.next().ok_or(kernel::error::code::EINVAL)?;
+ /// let bar1 = vf_bars.next().ok_or(kernel::error::code::EINVAL)?;
+ /// let bar2 = vf_bars.next().ok_or(kernel::error::code::EINVAL)?;
+ ///
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn find_ext_capability<C: ExtCapability>(&self) -> Result<Option<ConfigSpace<'a, C>>> {
+ let offset = usize::from(
+ // SAFETY: `self.pdev` is valid by the type invariant of `ConfigSpace`.
+ unsafe {
+ bindings::pci_find_ext_capability(self.pdev.as_raw(), i32::from(C::ID.as_raw()))
+ },
+ );
+
+ if offset == 0 {
+ return Ok(None);
+ }
+
+ let size = self.calculate_ext_cap_size(offset)?;
+
+ let base = ConfigSpaceBackend::as_ptr(*self)
+ .cast::<u8>()
+ .wrapping_add(offset);
+ let ptr = Region::<0>::ptr_try_from_raw_parts_mut(base, size)?;
+
+ // SAFETY: `offset` was returned by `pci_find_ext_capability`, and
+ // `calculate_ext_cap_size` bounds `ptr` at the next capability or the end of the extended
+ // configuration space. `ptr_try_from_raw_parts_mut` verified the region layout.
+ let capability = unsafe { ConfigSpaceBackend::project_view(*self, ptr) };
+
+ capability.try_cast::<C>().map(Some)
+ }
+
+ /// Calculates the size of the extended capability at `offset`.
+ ///
+ /// The capability extends to the next extended capability, or to the end of the extended
+ /// configuration space if it is the last one. `offset` must be a DWORD-aligned offset within
+ /// the extended configuration space returned by `pci_find_ext_capability`. Returns an error if
+ /// the capability header is outside the extended configuration space.
+ fn calculate_ext_cap_size(&self, offset: usize) -> Result<usize> {
+ let header = self.try_read32(offset)?;
+ // SAFETY: Pure bit manipulation, no preconditions.
+ // CAST: The next-cap pointer is a 12-bit field (max 0xFFC), always fits in `usize`.
+ let next = unsafe { bindings::pci_ext_cap_next(header) } as usize;
+
+ Ok(if next > offset {
+ next - offset
+ } else {
+ self.size() - offset
+ })
+ }
+}
+
+/// SR-IOV register layout per PCIe spec (64 bytes starting at cap offset).
+#[repr(C)]
+#[derive(FromBytes, IntoBytes)]
+pub struct ExtSriovRegs {
+ /// Extended capability header.
+ pub header: u32,
+ /// SR-IOV capabilities.
+ pub cap: u32,
+ /// SR-IOV control.
+ pub ctrl: u16,
+ /// SR-IOV status.
+ pub status: u16,
+ /// Initial VFs.
+ pub initial_vfs: u16,
+ /// Total VFs.
+ pub total_vfs: u16,
+ /// Number of VFs.
+ pub num_vfs: u16,
+ /// Function dependency link.
+ pub func_dep_link: u8,
+ _reserved_0: u8,
+ /// First VF offset.
+ pub vf_offset: u16,
+ /// VF stride.
+ pub vf_stride: u16,
+ _reserved_1: u16,
+ /// VF device ID.
+ pub vf_device_id: u16,
+ /// Supported page sizes.
+ pub supported_page_sizes: u32,
+ /// System page size.
+ pub system_page_size: u32,
+ /// VF BARs (BAR0–BAR5).
+ pub vf_bar: [u32; NUM_VF_BARS],
+ /// VF migration state array offset.
+ pub migration_state: u32,
+}
+
+impl ExtCapability for ExtSriovRegs {
+ const ID: ExtCapId = ExtCapId::Sriov;
+}
+
+/// A typed view of an SR-IOV extended capability.
+pub type ExtSriovCapability<'a> = ConfigSpace<'a, ExtSriovRegs>;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum VfBarMemoryType {
+ Bits32,
+ Bits64,
+}
+
+impl TryFrom<Bounded<u32, 2>> for VfBarMemoryType {
+ type Error = Error;
+
+ fn try_from(value: Bounded<u32, 2>) -> Result<Self> {
+ match value.get() {
+ 0b00 => Ok(Self::Bits32),
+ 0b10 => Ok(Self::Bits64),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+impl From<VfBarMemoryType> for Bounded<u32, 2> {
+ fn from(value: VfBarMemoryType) -> Self {
+ match value {
+ VfBarMemoryType::Bits32 => Self::new::<0b00>(),
+ VfBarMemoryType::Bits64 => Self::new::<0b10>(),
+ }
+ }
+}
+
+crate::bitfield! {
+ /// Low DWORD of an SR-IOV VF BAR.
+ struct VfBarLow(u32) {
+ /// Base address bits 31:4.
+ 31:4 address;
+ /// Whether the address range is prefetchable.
+ 3:3 prefetchable => bool;
+ /// Memory BAR type.
+ 2:1 memory_type ?=> VfBarMemoryType;
+ /// Whether this is an I/O-space BAR.
+ 0:0 io_space => bool;
+ }
+}
+
+/// A decoded VF BAR register encoding.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ExtSriovVfBar {
+ /// The BAR address without PCI attribute bits.
+ pub address: u64,
+
+ /// Whether the BAR is 64-bit.
+ pub is_64bit: bool,
+}
+
+/// Iterator over decoded VF BAR register encodings.
+///
+/// `slots` contains the six consecutive 32-bit registers VF BAR0 through VF BAR5. A 32-bit
+/// memory BAR encoding uses one register. A 64-bit memory BAR encoding uses that register for
+/// bits 31:0 and the immediately following register for bits 63:32.
+///
+/// # Invariants
+///
+/// - `config_slot <= NUM_VF_BARS`.
+/// - If `config_slot < NUM_VF_BARS`, it identifies the next register to interpret as a BAR low
+/// DWORD. Its address-space encoding is memory and its type encoding is either 32-bit or 64-bit.
+/// - If that low DWORD encodes a 64-bit BAR, `config_slot + 1 < NUM_VF_BARS`, and the register at
+/// `config_slot + 1` is its upper DWORD.
+struct ExtSriovVfBars {
+ slots: [u32; NUM_VF_BARS],
+ config_slot: usize,
+}
+
+impl ExtSriovVfBars {
+ fn new(slots: [u32; NUM_VF_BARS]) -> Result<Self> {
+ let mut config_slot = 0;
+
+ while config_slot < NUM_VF_BARS {
+ let low = VfBarLow::from(slots[config_slot]);
+
+ if low.io_space() {
+ return Err(EINVAL);
+ }
+
+ let is_64bit = low.memory_type()? == VfBarMemoryType::Bits64;
+
+ if is_64bit {
+ if config_slot + 1 >= NUM_VF_BARS {
+ return Err(EINVAL);
+ }
+
+ config_slot += 2;
+ } else {
+ config_slot += 1;
+ }
+ }
+
+ Ok(Self {
+ slots,
+ config_slot: 0,
+ })
+ }
+}
+
+impl Iterator for ExtSriovVfBars {
+ type Item = ExtSriovVfBar;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.config_slot >= NUM_VF_BARS {
+ return None;
+ }
+
+ let config_slot = self.config_slot;
+ let low = VfBarLow::from(self.slots[config_slot]);
+ let is_64bit = matches!(low.memory_type(), Ok(VfBarMemoryType::Bits64));
+ let low_address = u64::from(low.address()) << VfBarLow::ADDRESS_SHIFT;
+
+ let address = if is_64bit {
+ let high = self.slots[config_slot + 1];
+ self.config_slot += 2;
+ (u64::from(high) << 32) | low_address
+ } else {
+ self.config_slot += 1;
+ low_address
+ };
+
+ Some(ExtSriovVfBar { address, is_64bit })
+ }
+}
+
+impl ExtSriovCapability<'_> {
+ /// Returns an iterator over decoded VF BAR register encodings.
+ ///
+ /// The iterator tracks the six raw VF BAR register slots internally. A 32-bit encoding yields
+ /// one entry and advances by one slot; a 64-bit encoding combines two slots into one entry.
+ ///
+ /// A zero-valued low DWORD is yielded as a 32-bit BAR at address zero; this method does not
+ /// probe whether a BAR is implemented.
+ ///
+ /// Returns [`EINVAL`] and logs an error if a BAR low DWORD does not encode a 32-bit or 64-bit
+ /// memory BAR, or if a 64-bit encoding has no upper DWORD.
+ pub fn vf_bars(&self) -> Result<impl Iterator<Item = ExtSriovVfBar>> {
+ let slots: [u32; NUM_VF_BARS] =
+ core::array::from_fn(|slot| crate::io_read!(*self, .vf_bar[panic: slot]));
+
+ ExtSriovVfBars::new(slots).inspect_err(|_| {
+ dev_err!(self.pdev, "invalid VF BAR encoding in SR-IOV capability\n");
+ })
+ }
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-04 16:17 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04 16:16 [PATCH v7 0/1] Rust PCI capability infrastructure and SR-IOV support Zhi Wang
2026-08-04 16:16 ` [PATCH v7 1/1] rust: pci: add extended capability " Zhi Wang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox