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}