Rust for Linux List
 help / color / mirror / Atom feed
* [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