| // SPDX-License-Identifier: GPL-2.0 |
| |
| //! PCI interrupt infrastructure. |
| |
| use super::Device; |
| use crate::{ |
| bindings, |
| device, |
| device::Bound, |
| devres, |
| error::to_result, |
| irq::{ |
| self, |
| IrqRequest, // |
| }, |
| prelude::*, |
| str::CStr, |
| sync::aref::ARef, // |
| }; |
| use core::ops::RangeInclusive; |
| |
| /// 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, |
| } |
| } |
| } |
| |
| /// 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 |
| } |
| } |
| |
| /// Represents an allocated IRQ vector for a specific PCI device. |
| /// |
| /// This type ties an IRQ vector to the device it was allocated for, |
| /// ensuring the vector is only used with the correct device. |
| #[derive(Clone, Copy)] |
| pub struct IrqVector<'a> { |
| dev: &'a Device<Bound>, |
| index: u32, |
| } |
| |
| impl<'a> IrqVector<'a> { |
| /// Creates a new [`IrqVector`] for the given device and index. |
| /// |
| /// # Safety |
| /// |
| /// - `index` must be a valid IRQ vector index for `dev`. |
| /// - `dev` must point to a [`Device`] that has successfully allocated IRQ vectors. |
| unsafe fn new(dev: &'a Device<Bound>, index: u32) -> Self { |
| Self { dev, index } |
| } |
| |
| /// Returns the raw vector index. |
| fn index(&self) -> u32 { |
| self.index |
| } |
| } |
| |
| impl<'a> TryInto<IrqRequest<'a>> for IrqVector<'a> { |
| type Error = Error; |
| |
| fn try_into(self) -> Result<IrqRequest<'a>> { |
| // SAFETY: `self.as_raw` returns a valid pointer to a `struct pci_dev`. |
| let irq = unsafe { bindings::pci_irq_vector(self.dev.as_raw(), self.index()) }; |
| if irq < 0 { |
| return Err(crate::error::Error::from_errno(irq)); |
| } |
| // SAFETY: `irq` is guaranteed to be a valid IRQ number for `&self`. |
| Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) }) |
| } |
| } |
| |
| /// Represents an IRQ vector allocation for a PCI device. |
| /// |
| /// This type ensures that IRQ vectors are properly allocated and freed by |
| /// tying the allocation to the lifetime of this registration object. |
| /// |
| /// # Invariants |
| /// |
| /// The [`Device`] has successfully allocated IRQ vectors. |
| struct IrqVectorRegistration { |
| dev: ARef<Device>, |
| } |
| |
| impl IrqVectorRegistration { |
| /// Allocate and register IRQ vectors for the given PCI device. |
| /// |
| /// Allocates IRQ vectors and registers them with devres for automatic cleanup. |
| /// Returns a range of valid IRQ vectors. |
| fn register<'a>( |
| dev: &'a Device<Bound>, |
| min_vecs: u32, |
| max_vecs: u32, |
| irq_types: IrqTypes, |
| ) -> Result<RangeInclusive<IrqVector<'a>>> { |
| // SAFETY: |
| // - `dev.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(dev.as_raw(), min_vecs, max_vecs, irq_types.as_raw()) |
| }; |
| |
| to_result(ret)?; |
| let count = ret as u32; |
| |
| // SAFETY: |
| // - `pci_alloc_irq_vectors` returns the number of allocated vectors on success. |
| // - Vectors are 0-based, so valid indices are [0, count-1]. |
| // - `pci_alloc_irq_vectors` guarantees `count >= min_vecs > 0`, so both `0` and |
| // `count - 1` are valid IRQ vector indices for `dev`. |
| let range = unsafe { IrqVector::new(dev, 0)..=IrqVector::new(dev, count - 1) }; |
| |
| // INVARIANT: The IRQ vector allocation for `dev` above was successful. |
| let irq_vecs = Self { dev: dev.into() }; |
| devres::register(dev.as_ref(), irq_vecs, GFP_KERNEL)?; |
| |
| Ok(range) |
| } |
| } |
| |
| impl Drop for IrqVectorRegistration { |
| fn drop(&mut self) { |
| // SAFETY: |
| // - By the type invariant, `self.dev.as_raw()` is a valid pointer to a `struct pci_dev`. |
| // - `self.dev` has successfully allocated IRQ vectors. |
| unsafe { bindings::pci_free_irq_vectors(self.dev.as_raw()) }; |
| } |
| } |
| |
| impl Device<device::Bound> { |
| /// Returns a [`kernel::irq::Registration`] for the given IRQ vector. |
| pub fn request_irq<'a, T: crate::irq::Handler + 'static>( |
| &'a self, |
| vector: IrqVector<'a>, |
| flags: irq::Flags, |
| name: &'static CStr, |
| handler: impl PinInit<T, Error> + 'a, |
| ) -> impl PinInit<irq::Registration<T>, Error> + 'a { |
| pin_init::pin_init_scope(move || { |
| let request = vector.try_into()?; |
| |
| Ok(irq::Registration::<T>::new(request, flags, name, handler)) |
| }) |
| } |
| |
| /// Returns a [`kernel::irq::ThreadedRegistration`] for the given IRQ vector. |
| pub fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'static>( |
| &'a self, |
| vector: IrqVector<'a>, |
| flags: irq::Flags, |
| name: &'static CStr, |
| handler: impl PinInit<T, Error> + 'a, |
| ) -> impl PinInit<irq::ThreadedRegistration<T>, Error> + 'a { |
| pin_init::pin_init_scope(move || { |
| let request = vector.try_into()?; |
| |
| Ok(irq::ThreadedRegistration::<T>::new( |
| request, flags, name, handler, |
| )) |
| }) |
| } |
| |
| /// Allocate IRQ vectors for this PCI device with automatic cleanup. |
| /// |
| /// 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 automatically freed when the device is unbound, using the |
| /// devres (device resource management) system. |
| /// |
| /// # 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 a range of IRQ vectors that were successfully allocated, or an error if the |
| /// allocation fails or cannot meet the minimum requirement. |
| /// |
| /// # 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<RangeInclusive<IrqVector<'_>>> { |
| IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types) |
| } |
| } |