summaryrefslogtreecommitdiff
path: root/rust/kernel/pci/cap.rs
blob: 62f263ada1b3ce79e37b976931f805d2f745455b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
// 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 BarMemoryType {
    Bits32,
    Bits64,
}

impl TryFrom<Bounded<u32, 2>> for BarMemoryType {
    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<BarMemoryType> for Bounded<u32, 2> {
    fn from(value: BarMemoryType) -> Self {
        match value {
            BarMemoryType::Bits32 => Self::new::<0b00>(),
            BarMemoryType::Bits64 => Self::new::<0b10>(),
        }
    }
}

// TODO: Move the common BAR encoding definitions into the PCI core abstraction once it provides
// BAR register decoding.
crate::bitfield! {
    /// Low DWORD of an SR-IOV VF BAR.
    struct BarLow(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 ?=> BarMemoryType;
        /// 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(BarLow::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()) << BarLow::ADDRESS_SHIFT;
                let bar = match low.memory_type()? {
                    BarMemoryType::Bits64 => ExtSriovVfBar {
                        address: (u64::from(slots.next().ok_or(EINVAL)?) << 32) | low_address,
                        is_64bit: true,
                    },
                    BarMemoryType::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())
    }
}