Re: [PATCH v2 1/5] rust: pci: convert IrqVectorRegistration to a lifetime-managed owning type

"Gary Guo" <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,dev.linux.lists.driver-core,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci
Message-ID <[email protected]>
On Wed Aug 12, 2026 at 12:39 AM BST, Danilo Krummrich wrote:
> Convert IrqVectorRegistration from a devres-managed internal type to a
> lifetime-annotated type that owns the PCI interrupt vector allocation.
> Dropping it frees the vectors.
>
> IrqVector gains a reference to the IrqVectorRegistration it was derived
> from. Since vector() borrows the registration, the compiler prevents the
> allocation from being dropped while any IrqVector (and hence any
> irq::Registration built from it) is still live.
>
> alloc_irq_vectors() returns IrqVectorRegistration<'_> directly, giving
> drivers explicit control over the allocation lifetime, which is needed
> by net and block drivers that re-allocate vectors at runtime, e.g.
> during queue reconfiguration or device recovery.
>
> Signed-off-by: Danilo Krummrich <[email protected]>
> ---
>  rust/kernel/pci.rs     |   3 +-
>  rust/kernel/pci/irq.rs | 127 ++++++++++++++++++++++-------------------
>  2 files changed, 69 insertions(+), 61 deletions(-)
>
> diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
> index c6417af2bb17..2757a0cc0f11 100644
> --- a/rust/kernel/pci.rs
> +++ b/rust/kernel/pci.rs
> @@ -51,7 +51,8 @@
>  pub use self::irq::{
>      IrqType,
>      IrqTypes,
> -    IrqVector, //
> +    IrqVector,
> +    IrqVectorRegistration, //
>  };
>  
>  /// An adapter for the registration of PCI drivers.
> diff --git a/rust/kernel/pci/irq.rs b/rust/kernel/pci/irq.rs
> index fea484dcf9cf..8e0651587829 100644
> --- a/rust/kernel/pci/irq.rs
> +++ b/rust/kernel/pci/irq.rs
> @@ -7,17 +7,14 @@
>      bindings,
>      device,
>      device::Bound,
> -    devres,
>      error::to_result,
>      irq::{
>          self,
>          IrqRequest, //
>      },
> -    prelude::*,
> -    str::CStr,
> -    sync::aref::ARef, //
> +    prelude::*, //
>  };
> -use core::ops::RangeInclusive;
> +use core::num::NonZero;
>  
>  /// IRQ type flags for PCI interrupt allocation.
>  #[derive(Debug, Clone, Copy)]
> @@ -78,6 +75,7 @@ const fn as_raw(self) -> u32 {
>  #[derive(Clone, Copy)]
>  pub struct IrqVector<'a> {
>      dev: &'a Device<Bound>,
> +    reg: &'a IrqVectorRegistration<'a>,

The registration has a refence to the device so we don't need to keep both reg
and dev?

>      index: u32,
>  }
>  
> @@ -86,87 +84,81 @@ impl<'a> IrqVector<'a> {
>      ///
>      /// # 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 }
> +    /// - `index` must be a valid IRQ vector index for `reg`.
> +    /// - `dev` must be the device `reg` was allocated from.
> +    #[inline]
> +    unsafe fn new(dev: &'a Device<Bound>, reg: &'a IrqVectorRegistration<'a>, index: u32) -> Self {
> +        Self { dev, reg, index }
>      }
>  
>      /// Returns the raw vector index.
>      fn index(&self) -> u32 {
>          self.index
>      }
> +
> +    /// Returns the [`IrqVectorRegistration`] this vector was derived from.

#[inline]

> +    pub fn vectors(&self) -> &'a IrqVectorRegistration<'a> {
> +        self.reg
> +    }
>  }
>  
>  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`.
> +        // SAFETY: `self.dev.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`.
> +        // SAFETY: `irq` is guaranteed to be a valid IRQ number for `self.dev`.
>          Ok(unsafe { IrqRequest::new(self.dev.as_ref(), irq as u32) })
>      }
>  }
>  
> -/// Represents an IRQ vector allocation for a PCI device.
> +/// An allocation of PCI interrupt vectors for a device.
>  ///
> -/// This type ensures that IRQ vectors are properly allocated and freed by
> -/// tying the allocation to the lifetime of this registration object.
> +/// 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
>  ///
> -/// The [`Device`] has successfully allocated IRQ vectors.
> -struct IrqVectorRegistration {
> -    dev: ARef<Device>,
> +/// `dev` has an allocation of `count` interrupt vectors.
> +pub struct IrqVectorRegistration<'a> {
> +    dev: &'a Device<Bound>,
> +    count: NonZero<usize>,

I wonder if it should be called "len" as I view this as a collection of IRQ
vectors.

>  }
>  
> -impl IrqVectorRegistration {
> -    /// Allocate and register IRQ vectors for the given PCI device.
> +impl<'a> IrqVectorRegistration<'a> {
> +    /// Returns the number of allocated vectors.
>      ///
> -    /// 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) };
> +    /// This is at least the `min_vecs` that [`Device::alloc_irq_vectors`] was asked for.
> +    #[inline]
> +    pub fn vector_count(&self) -> usize {
> +        self.count.get()
> +    }
>  
> -        // 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)?;
> +    /// Returns the [`IrqVector`] at `index`.
> +    ///
> +    /// The returned [`IrqVector`] borrows from this registration, ensuring the vector allocation
> +    /// remains live while any handler is registered on it.

nit: I think this is redundant information as it's just stating what the
signature already conveys.

> +    #[inline]
> +    pub fn vector(&self, index: usize) -> Result<IrqVector<'_>> {
> +        if index >= self.count.get() {
> +            return Err(EINVAL);
> +        }

Given that the error is for out-of-bound access only, perhaps return `Option`
like `get()` function of various containers?

>  
> -        Ok(range)
> +        // SAFETY: `index` is within bounds of this registration's allocation, and `self.dev` is
> +        // the device it was allocated from.
> +        Ok(unsafe { IrqVector::new(self.dev, self, index as u32) })
>      }
>  }
>  
> -impl Drop for IrqVectorRegistration {
> +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`.
> -        // - `self.dev` has successfully allocated IRQ vectors.
> +        // 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()) };
>      }
>  }
> @@ -214,15 +206,16 @@ pub unsafe fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'a>(
>          })
>      }
>  
> -    /// Allocate IRQ vectors for this PCI device with automatic cleanup.
> +    /// 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 automatically freed when the device is unbound, using the
> -    /// devres (device resource management) system.
> +    /// The allocated vectors are freed when the returned [`IrqVectorRegistration`] is dropped.
> +    /// IRQ handlers registered via [`Self::request_irq`] or [`Self::request_threaded_irq`]
> +    /// borrow from the registration, so the compiler ensures they are freed first.
>      ///
>      /// # Arguments
>      ///
> @@ -232,8 +225,8 @@ pub unsafe fn request_threaded_irq<'a, T: crate::irq::ThreadedHandler + 'a>(
>      ///
>      /// # Returns
>      ///
> -    /// Returns a range of IRQ vectors that were successfully allocated, or an error if the
> -    /// allocation fails or cannot meet the minimum requirement.
> +    /// Returns the IRQ vector registration, or an error if `min_vecs` vectors cannot be
> +    /// allocated.
>      ///
>      /// # Examples
>      ///
> @@ -256,7 +249,21 @@ pub fn alloc_irq_vectors(
>          min_vecs: u32,
>          max_vecs: u32,
>          irq_types: IrqTypes,
> -    ) -> Result<RangeInclusive<IrqVector<'_>>> {
> -        IrqVectorRegistration::register(self, min_vecs, max_vecs, irq_types)
> +    ) -> 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 count = NonZero::new(ret as usize).ok_or(EINVAL)?;

I don't think `ret` can ever be zero. `expect` or `new_unchecked()` perhaps?

Best,
Gary

> +
> +        // INVARIANT: `pci_alloc_irq_vectors()` allocated `count` vectors for `self`.
> +        Ok(IrqVectorRegistration { dev: self, count })
>      }
>  }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.