Skip to main content

kernel/pci/
cap.rs

1// SPDX-License-Identifier: GPL-2.0
2
3//! PCI extended capability support.
4
5use super::{
6    io::ConfigSpaceBackend,
7    ConfigSpace,
8    Extended, //
9};
10use crate::{
11    bindings,
12    io::{
13        Io,
14        IoBackend,
15        Region, //
16    },
17    num::{casts, Bounded},
18    prelude::*,
19};
20
21/// Number of VF BAR register slots in an SR-IOV capability.
22const NUM_VF_BARS: usize = casts::u32_as_usize(bindings::PCI_SRIOV_NUM_BARS);
23
24/// PCI extended capability IDs.
25#[repr(transparent)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct ExtCapId(u16);
28
29impl ExtCapId {
30    /// Single Root I/O Virtualization.
31    pub const SRIOV: Self = Self(casts::u32_into_u16::<{ bindings::PCI_EXT_CAP_ID_SRIOV }>());
32
33    /// Returns the raw PCIe extended capability ID.
34    #[inline]
35    const fn as_raw(self) -> u16 {
36        self.0
37    }
38}
39
40/// A typed PCI extended capability register layout.
41///
42/// Implementors describe the register layout of one extended capability. The layout must start at
43/// the extended capability header, and [`Self::ID`] must identify that layout.
44pub trait ExtCapability: FromBytes + IntoBytes {
45    /// PCI extended capability ID for this register layout.
46    const ID: ExtCapId;
47}
48
49impl<'a> ConfigSpace<'a, Extended> {
50    /// Finds and projects an extended capability into its typed register layout.
51    ///
52    /// Returns [`None`] if the device does not implement the capability.
53    ///
54    /// Returns an error if the capability is present but its register span is too small or
55    /// insufficiently aligned for `C`.
56    ///
57    /// # Examples
58    ///
59    /// ```no_run
60    /// use kernel::{
61    ///     device::Bound,
62    ///     pci,
63    ///     prelude::*,
64    /// };
65    ///
66    /// fn probe_sriov(pdev: &pci::Device<Bound>) -> Result {
67    ///     let Some(sriov) = pdev
68    ///         .config_space_extended()?
69    ///         .find_ext_capability::<pci::ExtSriovRegs>()?
70    ///     else {
71    ///         return Ok(());
72    ///     };
73    ///
74    ///     let first_vf_offset = sriov.first_vf_offset();
75    ///     let mut vf_bars = sriov.vf_bars()?;
76    ///     let bar0 = vf_bars.next().ok_or(EINVAL)?;
77    ///     let bar1 = vf_bars.next().ok_or(EINVAL)?;
78    ///     let bar2 = vf_bars.next().ok_or(EINVAL)?;
79    ///
80    ///     Ok(())
81    /// }
82    /// ```
83    pub fn find_ext_capability<C: ExtCapability>(&self) -> Result<Option<ConfigSpace<'a, C>>> {
84        let offset = usize::from(
85            // SAFETY: `self.pdev` is valid by the type invariant of `ConfigSpace`.
86            unsafe {
87                bindings::pci_find_ext_capability(self.pdev.as_raw(), i32::from(C::ID.as_raw()))
88            },
89        );
90
91        if offset == 0 {
92            return Ok(None);
93        }
94
95        let size = self.calculate_ext_cap_size(offset)?;
96
97        let base = ConfigSpaceBackend::as_ptr(*self)
98            .cast::<u8>()
99            .wrapping_add(offset);
100        let ptr = Region::<0>::ptr_try_from_raw_parts_mut(base, size)?;
101
102        // SAFETY: `offset` was returned by `pci_find_ext_capability`, and
103        // `calculate_ext_cap_size` bounds `ptr` at the next capability or the end of the extended
104        // configuration space. `ptr_try_from_raw_parts_mut` verified the region layout.
105        let capability = unsafe { ConfigSpaceBackend::project_view(*self, ptr) };
106
107        capability.try_cast::<C>().map(Some)
108    }
109
110    /// Calculates the size of the extended capability at `offset`.
111    ///
112    /// The capability extends to the next extended capability, or to the end of the extended
113    /// configuration space if it is the last one. `offset` must be a DWORD-aligned offset within
114    /// the extended configuration space returned by `pci_find_ext_capability`. Returns an error if
115    /// the capability header is outside the extended configuration space.
116    fn calculate_ext_cap_size(&self, offset: usize) -> Result<usize> {
117        let header = self.try_read32(offset)?;
118        // SAFETY: Pure bit manipulation, no preconditions.
119        let next = casts::u32_as_usize(unsafe { bindings::pci_ext_cap_next(header) });
120
121        Ok(if next > offset {
122            next - offset
123        } else {
124            self.size() - offset
125        })
126    }
127}
128
129/// SR-IOV register layout per PCIe spec (64 bytes starting at cap offset).
130///
131/// The raw registers are private because PCI core owns SR-IOV state management. Drivers should use
132/// PCI core APIs for operations such as enabling VFs and querying their topology.
133#[repr(C)]
134#[derive(FromBytes, IntoBytes)]
135pub struct ExtSriovRegs {
136    /// Extended capability header.
137    _header: u32,
138    /// SR-IOV capabilities.
139    _cap: u32,
140    /// SR-IOV control.
141    _ctrl: u16,
142    /// SR-IOV status.
143    _status: u16,
144    /// Initial VFs.
145    _initial_vfs: u16,
146    /// Total VFs.
147    _total_vfs: u16,
148    /// Number of VFs.
149    _num_vfs: u16,
150    /// Function dependency link.
151    _func_dep_link: u8,
152    _reserved_0: u8,
153    /// First VF offset.
154    vf_offset: u16,
155    /// VF stride.
156    _vf_stride: u16,
157    _reserved_1: u16,
158    /// VF device ID.
159    _vf_device_id: u16,
160    /// Supported page sizes.
161    _supported_page_sizes: u32,
162    /// System page size.
163    _system_page_size: u32,
164    /// VF BARs (BAR0–BAR5).
165    vf_bar: [u32; NUM_VF_BARS],
166    /// VF migration state array offset.
167    _migration_state: u32,
168}
169
170impl ExtCapability for ExtSriovRegs {
171    const ID: ExtCapId = ExtCapId::SRIOV;
172}
173
174/// A typed view of an SR-IOV extended capability.
175pub type ExtSriovCapability<'a> = ConfigSpace<'a, ExtSriovRegs>;
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178enum BarMemoryType {
179    Bits32,
180    Bits64,
181}
182
183impl TryFrom<Bounded<u32, 2>> for BarMemoryType {
184    type Error = Error;
185
186    fn try_from(value: Bounded<u32, 2>) -> Result<Self> {
187        match value.get() {
188            0b00 => Ok(Self::Bits32),
189            0b10 => Ok(Self::Bits64),
190            _ => Err(EINVAL),
191        }
192    }
193}
194
195impl From<BarMemoryType> for Bounded<u32, 2> {
196    fn from(value: BarMemoryType) -> Self {
197        match value {
198            BarMemoryType::Bits32 => Self::new::<0b00>(),
199            BarMemoryType::Bits64 => Self::new::<0b10>(),
200        }
201    }
202}
203
204// TODO: Move the common BAR encoding definitions into the PCI core abstraction once it provides
205// BAR register decoding.
206crate::bitfield! {
207    /// Low DWORD of an SR-IOV VF BAR.
208    struct BarLow(u32) {
209        /// Base address bits 31:4.
210        31:4 address;
211        /// Whether the address range is prefetchable.
212        3:3 prefetchable => bool;
213        /// Memory BAR type.
214        2:1 memory_type ?=> BarMemoryType;
215        /// Whether this is an I/O-space BAR.
216        0:0 io_space => bool;
217    }
218}
219
220/// A decoded VF BAR register encoding.
221#[derive(Debug, Clone, Copy, PartialEq, Eq)]
222pub struct ExtSriovVfBar {
223    /// The PCI bus address encoded by the BAR, without PCI attribute bits.
224    ///
225    /// This is not necessarily the CPU resource address.
226    pub address: u64,
227
228    /// Whether the BAR is 64-bit.
229    pub is_64bit: bool,
230}
231
232impl ExtSriovCapability<'_> {
233    /// Returns the current PCIe First VF Offset in Routing ID space.
234    ///
235    /// This value may change when NumVFs changes. Drivers should use PCI core helpers when they
236    /// need to calculate a VF BDF; this accessor is intended for interfaces that require the First
237    /// VF Offset itself.
238    #[inline]
239    pub fn first_vf_offset(&self) -> u16 {
240        crate::io_read!(*self, .vf_offset)
241    }
242
243    /// Returns an iterator over decoded VF BAR register encodings.
244    ///
245    /// All six raw VF BAR register slots are read and decoded up front. A 32-bit encoding yields
246    /// one entry; a 64-bit encoding combines two slots into one entry.
247    ///
248    /// A zero-valued low DWORD is yielded as a 32-bit BAR at address zero; this method does not
249    /// probe whether a BAR is implemented.
250    ///
251    /// Returns [`EINVAL`] and logs an error if a BAR low DWORD does not encode a 32-bit or 64-bit
252    /// memory BAR, or if a 64-bit encoding has no upper DWORD.
253    pub fn vf_bars(&self) -> Result<impl Iterator<Item = ExtSriovVfBar>> {
254        let slots: [u32; NUM_VF_BARS] =
255            core::array::from_fn(|slot| crate::io_read!(*self, .vf_bar[panic: slot]));
256        let mut slots = slots.into_iter();
257        let mut bars = [None; NUM_VF_BARS];
258        let mut count = 0;
259
260        let mut decode = || {
261            while let Some(low) = slots.next().map(BarLow::from) {
262                // SR-IOV VF BARs describe memory-space apertures; an I/O-space encoding is not
263                // valid for these registers.
264                if low.io_space() {
265                    return Err(EINVAL);
266                }
267
268                let low_address = u64::from(low.address()) << BarLow::ADDRESS_SHIFT;
269                let bar = match low.memory_type()? {
270                    BarMemoryType::Bits64 => ExtSriovVfBar {
271                        address: (u64::from(slots.next().ok_or(EINVAL)?) << 32) | low_address,
272                        is_64bit: true,
273                    },
274                    BarMemoryType::Bits32 => ExtSriovVfBar {
275                        address: low_address,
276                        is_64bit: false,
277                    },
278                };
279
280                bars[count] = Some(bar);
281                count += 1;
282            }
283
284            Ok(())
285        };
286
287        decode().inspect_err(|_| {
288            dev_err!(self.pdev, "invalid VF BAR encoding in SR-IOV capability\n");
289        })?;
290        Ok(bars.into_iter().flatten())
291    }
292}