* [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
2026-08-13 6:54 ` [PATCH v7 0/1] Rust PCI capability infrastructure " Zhi Wang
1 sibling, 0 replies; 3+ 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] 3+ messages in thread* Re: [PATCH v7 0/1] Rust PCI capability infrastructure 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 ` [PATCH v7 1/1] rust: pci: add extended capability " Zhi Wang
@ 2026-08-13 6:54 ` Zhi Wang
1 sibling, 0 replies; 3+ messages in thread
From: Zhi Wang @ 2026-08-13 6:54 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, Zhi Wang, daniel.almeida, tamird,
work
On Tue, 4 Aug 2026 19:16:10 +0300
Zhi Wang <zhiw@nvidia.com> wrote:
Gentle ping. :)
> 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
^ permalink raw reply [flat|nested] 3+ messages in thread