* [PATCH v9 0/1] Rust PCI capability infrastructure and SR-IOV support
@ 2026-08-27 12:18 Zhi Wang
2026-08-27 12:18 ` [PATCH v9 1/1] rust: pci: add extended capability " Zhi Wang
0 siblings, 1 reply; 2+ messages in thread
From: Zhi Wang @ 2026-08-27 12: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, Zhi Wang, joelagnelf, daniel.almeida,
tamird, work
This is a follow-up to v8 [1].
The patch adds typed PCIe extended-capability lookup to the Rust PCI
abstraction and an SR-IOV layout used by the nova-core vGPU boot work.
This series is based on current mainline. That base provides the existing
num::casts helpers but not const_as!, so the const narrowing call uses the
temporary turbofish form discussed during review. It can be converted as
part of the subsequent const_as! migration before the old helper is
removed.
Changes since v8:
- Made all raw ExtSriovRegs fields private. (Danilo)
- Added a read-only first_vf_offset() accessor for the GSP ABI and
documented why pci_iov_virtfn_devfn() cannot replace it. (Danilo, Zhi)
- Simplified VF BAR decoding using a slot iterator and removed
ExtSriovVfBars. (Alexandre, Danilo)
- Explained why I/O-space VF BAR encodings are rejected. (Alexandre)
- Clarified that decoded BAR addresses are PCI bus addresses. (Zhi)
- Removed unused ExtCapId::new() and kept as_raw() private. (Danilo)
- Replaced documented integer casts with the num::casts helpers available
in current mainline. (Alexandre)
- Rebased onto current mainline.
[1] https://lore.kernel.org/rust-for-linux/20260818084633.1673214-1-zhiw@nvidia.com/
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 | 290 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 303 insertions(+)
create mode 100644 rust/kernel/pci/cap.rs
base-commit: 45c13f3f9e3bb15fd89ff2864c6f627a3b4b4229
--
2.53.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH v9 1/1] rust: pci: add extended capability and SR-IOV support
2026-08-27 12:18 [PATCH v9 0/1] Rust PCI capability infrastructure and SR-IOV support Zhi Wang
@ 2026-08-27 12:18 ` Zhi Wang
0 siblings, 0 replies; 2+ messages in thread
From: Zhi Wang @ 2026-08-27 12: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, Zhi Wang, joelagnelf, 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.
Keep raw SR-IOV registers private because PCI core owns their state. Expose
the current First VF Offset through a read-only accessor because the Nova
GSP ABI needs the raw 16-bit First VF Offset in Routing ID space. The
pci_iov_virtfn_devfn() helper instead returns the low eight bits of a
calculated VF Routing ID.
Link: https://lore.kernel.org/rust-for-linux/20260818084633.1673214-2-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 | 290 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 303 insertions(+)
create mode 100644 rust/kernel/pci/cap.rs
diff --git a/rust/helpers/pci.c b/rust/helpers/pci.c
index a714cc2bfb7a..502f56e02fcc 100644
--- a/rust/helpers/pci.c
+++ b/rust/helpers/pci.c
@@ -29,6 +29,11 @@ __rust_helper unsigned int rust_helper_pci_irq_type(struct pci_dev *pdev)
return pci_irq_type(pdev);
}
+__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 3ec897709e89..77c52fe59551 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..74cd9e50e919
--- /dev/null
+++ b/rust/kernel/pci/cap.rs
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! PCI extended capability support.
+
+use super::{
+ io::ConfigSpaceBackend,
+ ConfigSpace,
+ Extended, //
+};
+use crate::{
+ bindings,
+ io::{
+ Io,
+ IoBackend,
+ Region, //
+ },
+ num::{casts, Bounded},
+ prelude::*,
+};
+
+/// Number of VF BAR register slots in an SR-IOV capability.
+const NUM_VF_BARS: usize = casts::u32_as_usize(bindings::PCI_SRIOV_NUM_BARS);
+
+/// PCI extended capability IDs.
+#[repr(transparent)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct ExtCapId(u16);
+
+impl ExtCapId {
+ /// Single Root I/O Virtualization.
+ pub const SRIOV: Self = Self(casts::u32_into_u16::<{ bindings::PCI_EXT_CAP_ID_SRIOV }>());
+
+ /// Returns the raw PCIe extended capability ID.
+ #[inline]
+ const fn as_raw(self) -> u16 {
+ self.0
+ }
+}
+
+/// 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.
+ ///
+ /// Returns an error if the capability is present but its register span is too small or
+ /// insufficiently aligned for `C`.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use kernel::{
+ /// device::Bound,
+ /// pci,
+ /// prelude::*,
+ /// };
+ ///
+ /// fn probe_sriov(pdev: &pci::Device<Bound>) -> Result {
+ /// let Some(sriov) = pdev
+ /// .config_space_extended()?
+ /// .find_ext_capability::<pci::ExtSriovRegs>()?
+ /// else {
+ /// return Ok(());
+ /// };
+ ///
+ /// let first_vf_offset = sriov.first_vf_offset();
+ /// let mut vf_bars = sriov.vf_bars()?;
+ /// let bar0 = vf_bars.next().ok_or(EINVAL)?;
+ /// let bar1 = vf_bars.next().ok_or(EINVAL)?;
+ /// let bar2 = vf_bars.next().ok_or(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.
+ let next = casts::u32_as_usize(unsafe { bindings::pci_ext_cap_next(header) });
+
+ Ok(if next > offset {
+ next - offset
+ } else {
+ self.size() - offset
+ })
+ }
+}
+
+/// SR-IOV register layout per PCIe spec (64 bytes starting at cap offset).
+///
+/// The raw registers are private because PCI core owns SR-IOV state management. Drivers should use
+/// PCI core APIs for operations such as enabling VFs and querying their topology.
+#[repr(C)]
+#[derive(FromBytes, IntoBytes)]
+pub struct ExtSriovRegs {
+ /// Extended capability header.
+ _header: u32,
+ /// SR-IOV capabilities.
+ _cap: u32,
+ /// SR-IOV control.
+ _ctrl: u16,
+ /// SR-IOV status.
+ _status: u16,
+ /// Initial VFs.
+ _initial_vfs: u16,
+ /// Total VFs.
+ _total_vfs: u16,
+ /// Number of VFs.
+ _num_vfs: u16,
+ /// Function dependency link.
+ _func_dep_link: u8,
+ _reserved_0: u8,
+ /// First VF offset.
+ vf_offset: u16,
+ /// VF stride.
+ _vf_stride: u16,
+ _reserved_1: u16,
+ /// VF device ID.
+ _vf_device_id: u16,
+ /// Supported page sizes.
+ _supported_page_sizes: u32,
+ /// System page size.
+ _system_page_size: u32,
+ /// VF BARs (BAR0–BAR5).
+ vf_bar: [u32; NUM_VF_BARS],
+ /// VF migration state array offset.
+ _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 PCI bus address encoded by the BAR, without PCI attribute bits.
+ ///
+ /// This is not necessarily the CPU resource address.
+ pub address: u64,
+
+ /// Whether the BAR is 64-bit.
+ pub is_64bit: bool,
+}
+
+impl ExtSriovCapability<'_> {
+ /// Returns the current PCIe First VF Offset in Routing ID space.
+ ///
+ /// This value may change when NumVFs changes. Drivers should use PCI core helpers when they
+ /// need to calculate a VF BDF; this accessor is intended for interfaces that require the First
+ /// VF Offset itself.
+ #[inline]
+ pub fn first_vf_offset(&self) -> u16 {
+ crate::io_read!(*self, .vf_offset)
+ }
+
+ /// Returns an iterator over decoded VF BAR register encodings.
+ ///
+ /// All six raw VF BAR register slots are read and decoded up front. A 32-bit encoding yields
+ /// one entry; 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]));
+ let mut slots = slots.into_iter();
+ let mut bars = [None; NUM_VF_BARS];
+ let mut count = 0;
+
+ let mut decode = || {
+ while let Some(low) = slots.next().map(VfBarLow::from) {
+ // SR-IOV VF BARs describe memory-space apertures; an I/O-space encoding is not
+ // valid for these registers.
+ if low.io_space() {
+ return Err(EINVAL);
+ }
+
+ let low_address = u64::from(low.address()) << VfBarLow::ADDRESS_SHIFT;
+ let bar = match low.memory_type()? {
+ VfBarMemoryType::Bits64 => ExtSriovVfBar {
+ address: (u64::from(slots.next().ok_or(EINVAL)?) << 32) | low_address,
+ is_64bit: true,
+ },
+ VfBarMemoryType::Bits32 => ExtSriovVfBar {
+ address: low_address,
+ is_64bit: false,
+ },
+ };
+
+ bars[count] = Some(bar);
+ count += 1;
+ }
+
+ Ok(())
+ };
+
+ decode().inspect_err(|_| {
+ dev_err!(self.pdev, "invalid VF BAR encoding in SR-IOV capability\n");
+ })?;
+ Ok(bars.into_iter().flatten())
+ }
+}
--
2.53.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-27 12:19 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 12:18 [PATCH v9 0/1] Rust PCI capability infrastructure and SR-IOV support Zhi Wang
2026-08-27 12:18 ` [PATCH v9 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