[PATCH v9 1/1] rust: pci: add extended capability and SR-IOV support

Zhi Wang <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci
Message-ID <2e9d992509299c2f13de48109c4c933f53817acb.1787830828.git.zhiw@nvidia.com>
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/[email protected]/
Cc: Alexandre Courbot <[email protected]>
Cc: Gary Guo <[email protected]>
Signed-off-by: Zhi Wang <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.