mirror of
https://github.com/torvalds/linux.git
synced 2026-10-09 03:56:03 +02:00
IrqVectorRegistration::index() accepts a usize, but pci_irq_vector()
takes an unsigned int. On 64-bit architectures, casting an index larger
than u32::MAX wraps it before the PCI core can validate it. In
particular, u32::MAX + 1 becomes zero and can resolve to the first
allocated vector.
Use a checked conversion and return EINVAL when the index cannot be
represented by the C API.
Fixes: 2fb7755b0a ("rust: pci: resolve IRQ in index() and embed IrqRequest in IrqVector")
Signed-off-by: Sophon Zhang <aiqubits@hotmail.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Link: https://patch.msgid.link/20260901-fix-pci-irq-vector-index-truncation-v4-1-f94aa6932fd9@hotmail.com
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
237 lines
7.3 KiB
Rust
237 lines
7.3 KiB
Rust
// 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<'_>> {
|
|
let index = u32::try_from(index)?;
|
|
|
|
// 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) };
|
|
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 })
|
|
}
|
|
}
|