aboutsummaryrefslogtreecommitdiffstats
path: root/rust/kernel/pci/irq.rs
blob: 6741046ec1c09d1bd873adfad3631b1646bcfdb9 (plain)
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
// SPDX-License-Identifier: GPL-2.0

//! PCI interrupt infrastructure.

use super::Device;
use crate::{
    bindings,
    device,
    device::Bound,
    error::to_result,
    irq::IrqRequest,
    prelude::*, //
};
use core::num::NonZero;

/// IRQ type flags for PCI interrupt allocation.
#[derive(Debug, Clone, Copy)]
pub enum IrqType {
    /// INTx interrupts.
    Intx,
    /// Message Signaled Interrupts (MSI).
    Msi,
    /// Extended Message Signaled Interrupts (MSI-X).
    MsiX,
}

impl IrqType {
    /// Convert to the corresponding kernel flags.
    const fn as_raw(self) -> u32 {
        match self {
            IrqType::Intx => bindings::PCI_IRQ_INTX,
            IrqType::Msi => bindings::PCI_IRQ_MSI,
            IrqType::MsiX => bindings::PCI_IRQ_MSIX,
        }
    }

    /// Construct from raw value.
    #[inline]
    const fn from_raw(raw: u32) -> Self {
        match raw {
            bindings::PCI_IRQ_MSIX => IrqType::MsiX,
            bindings::PCI_IRQ_MSI => IrqType::Msi,
            _ => IrqType::Intx,
        }
    }
}

/// Set of IRQ types that can be used for PCI interrupt allocation.
#[derive(Debug, Clone, Copy, Default)]
pub struct IrqTypes(u32);

impl IrqTypes {
    /// Create a set containing all IRQ types (MSI-X, MSI, and INTx).
    pub const fn all() -> Self {
        Self(bindings::PCI_IRQ_ALL_TYPES)
    }

    /// Build a set of IRQ types.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// // Create a set with only MSI and MSI-X (no INTx interrupts).
    /// let msi_only = IrqTypes::default()
    ///     .with(IrqType::Msi)
    ///     .with(IrqType::MsiX);
    /// ```
    pub const fn with(self, irq_type: IrqType) -> Self {
        Self(self.0 | irq_type.as_raw())
    }

    /// Get the raw flags value.
    const fn as_raw(self) -> u32 {
        self.0
    }
}

/// A resolved IRQ vector from a PCI interrupt vector allocation.
///
/// Created by [`IrqVectorRegistration::index`]. Convert to [`IrqRequest`] via [`From`] to register
/// a handler with [`irq::Registration::new`](crate::irq::Registration::new).
pub struct IrqVector<'a> {
    request: IrqRequest<'a>,
    reg: &'a IrqVectorRegistration<'a>,
}

impl<'a> IrqVector<'a> {
    /// Creates a new [`IrqVector`] with an already resolved [`IrqRequest`].
    ///
    /// # Safety
    ///
    /// `request` must have been resolved from `reg`.
    #[inline]
    unsafe fn new(request: IrqRequest<'a>, reg: &'a IrqVectorRegistration<'a>) -> Self {
        Self { request, reg }
    }

    /// Returns the [`IrqVectorRegistration`] this vector was derived from.
    #[inline]
    pub fn vectors(&self) -> &'a IrqVectorRegistration<'a> {
        self.reg
    }

    /// Returns the interrupt type the PCI core selected for this vector's allocation.
    #[inline]
    pub fn irq_type(&self) -> IrqType {
        self.reg.irq_type()
    }
}

impl<'a> From<IrqVector<'a>> for IrqRequest<'a> {
    #[inline]
    fn from(vector: IrqVector<'a>) -> Self {
        vector.request
    }
}

/// An allocation of PCI interrupt vectors for a device.
///
/// This type owns the vector allocation; dropping it frees the vectors. IRQ handlers borrow from
/// this registration and must be dropped before it is.
///
/// # Invariants
///
/// `dev` has an allocation of `len` interrupt vectors.
pub struct IrqVectorRegistration<'a> {
    dev: &'a Device<Bound>,
    len: NonZero<usize>,
}

impl<'a> IrqVectorRegistration<'a> {
    /// Returns the number of allocated vectors.
    ///
    /// This is at least the `min_vecs` that [`Device::alloc_irq_vectors`] was asked for.
    #[inline]
    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        self.len.get()
    }

    /// Returns the interrupt type the PCI core selected for this allocation.
    #[inline]
    pub fn irq_type(&self) -> IrqType {
        // SAFETY: `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`.
        IrqType::from_raw(unsafe { bindings::pci_irq_type(self.dev.as_raw()) })
    }

    /// Returns the [`IrqVector`] at `index`.
    ///
    /// Returns [`EINVAL`] if the `index` is out of bounds for the length reported by
    /// [`Self::len()`].
    #[inline]
    pub fn index(&self, index: usize) -> Result<IrqVector<'_>> {
        // SAFETY: `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`.
        let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), index as u32) };
        if irq < 0 {
            return Err(Error::from_errno(irq));
        }

        // SAFETY: `irq` is a valid IRQ number for `self.dev`, resolved from this registration.
        Ok(unsafe { IrqVector::new(IrqRequest::new(self.dev.as_ref(), irq as u32), self) })
    }
}

impl Drop for IrqVectorRegistration<'_> {
    #[inline]
    fn drop(&mut self) {
        // SAFETY: By the type invariant, `self.dev.as_raw()` is a valid pointer to a
        // `struct pci_dev` that has successfully allocated IRQ vectors.
        unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) };
    }
}

impl Device<device::Bound> {
    /// Allocate IRQ vectors for this PCI device.
    ///
    /// Allocates between `min_vecs` and `max_vecs` interrupt vectors for the device.
    /// The allocation will use MSI-X, MSI, or INTx interrupts based on the `irq_types`
    /// parameter and hardware capabilities. When multiple types are specified, the kernel
    /// will try them in order of preference: MSI-X first, then MSI, then INTx interrupts.
    ///
    /// The allocated vectors are freed when the returned [`IrqVectorRegistration`] is dropped.
    /// Use [`IrqVectorRegistration::index`] to obtain an [`IrqVector`] for a given vector
    /// index.
    ///
    /// # Arguments
    ///
    /// * `min_vecs` - Minimum number of vectors required.
    /// * `max_vecs` - Maximum number of vectors to allocate.
    /// * `irq_types` - Types of interrupts that can be used.
    ///
    /// # Returns
    ///
    /// Returns the IRQ vector registration, or an error if `min_vecs` vectors cannot be
    /// allocated.
    ///
    /// # Examples
    ///
    /// ```
    /// # use kernel::{ device::Bound, pci};
    /// # fn no_run(dev: &pci::Device<Bound>) -> Result {
    /// // Allocate using any available interrupt type in the order mentioned above.
    /// let vectors = dev.alloc_irq_vectors(1, 32, pci::IrqTypes::all())?;
    ///
    /// // Allocate MSI or MSI-X only (no INTx interrupts).
    /// let msi_only = pci::IrqTypes::default()
    ///     .with(pci::IrqType::Msi)
    ///     .with(pci::IrqType::MsiX);
    /// let vectors = dev.alloc_irq_vectors(4, 16, msi_only)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn alloc_irq_vectors(
        &self,
        min_vecs: u32,
        max_vecs: u32,
        irq_types: IrqTypes,
    ) -> Result<IrqVectorRegistration<'_>> {
        // SAFETY:
        // - `self.as_raw()` is guaranteed to be a valid pointer to a `struct pci_dev`
        //   by the type invariant of `Device`.
        // - `pci_alloc_irq_vectors` internally validates all other parameters
        //   and returns error codes.
        let ret = unsafe {
            bindings::pci_alloc_irq_vectors(self.as_raw(), min_vecs, max_vecs, irq_types.as_raw())
        };
        to_result(ret)?;

        let len = NonZero::new(ret as usize).ok_or(EINVAL)?;

        // INVARIANT: `pci_alloc_irq_vectors()` allocated `len` vectors for `self`.
        Ok(IrqVectorRegistration { dev: self, len })
    }
}