[PATCH 08/17] gpu: nova-core: allocate interrupt vectors for the serviced subtrees

From: John Hubbard

Date: Fri Aug 07 2026 - 23:13:54 EST


Every subtree nova-core enables at TOP needs an allocated PCI vector
with a handler on it. How many vectors that takes depends on the type
the PCI core grants. MSI has one message that every subtree raises,
so one vector serves the whole tree. MSI-X gives each subtree its own
table entry, and Linux masks every entry a driver does not allocate. A
serviced subtree with no entry of its own loses the interrupts it
raises, while its GIN leaf and TOP bits read pending and enabled.

nova-core allocated one vector at probe, and the tree enabled every
implemented subtree.

Size the allocation to the serviced set: MSI-X entries up to the highest
serviced subtree, falling back to a single MSI. Drop the INTx fallback,
since nova-core does not share a level-triggered line. Enable only
the serviced subtrees at TOP, and take the leaf count and the rearm
method from the interrupt HAL when the tree is built.

Assisted-by: Cursor:claude-opus-5
Reviewed-by: Will Pierce <wpierce@xxxxxxxxxx>
Signed-off-by: John Hubbard <jhubbard@xxxxxxxxxx>
---
drivers/gpu/nova-core/gpu.rs | 6 --
drivers/gpu/nova-core/irq.rs | 78 ++++++++++++++++++---
drivers/gpu/nova-core/irq/hal.rs | 2 -
drivers/gpu/nova-core/irq/interrupt_tree.rs | 68 +++++++++++-------
4 files changed, 111 insertions(+), 43 deletions(-)

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 5efeba056f1b..42a4cd7971fa 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -29,7 +29,6 @@
Gsp,
GspBootContext, //
},
- irq,
regs,
vgpu::VgpuManager, //
};
@@ -387,11 +386,6 @@ pub(crate) fn new(
})?,
}),

- // Allocate a PCI interrupt vector.
- _: {
- let _irq_vector = irq::alloc_vector(pdev)?;
- },
-
gsp_static_info: {
// Obtain and display basic GPU information.
let info = gsp_resources.gsp.get_static_info(bar)?;
diff --git a/drivers/gpu/nova-core/irq.rs b/drivers/gpu/nova-core/irq.rs
index ef77066e0514..2f0e2644b9bd 100644
--- a/drivers/gpu/nova-core/irq.rs
+++ b/drivers/gpu/nova-core/irq.rs
@@ -21,16 +21,76 @@
prelude::*,
};

-pub(crate) fn alloc_vector(pdev: &pci::Device<Bound>) -> Result<pci::IrqVector<'_>> {
- let msi_types = IrqTypes::default().with(IrqType::Msi).with(IrqType::MsiX);
-
- let irq_vectors = match pdev.alloc_irq_vectors(1, 1, msi_types) {
- Ok(vecs) => vecs,
- Err(_) => {
- dev_warn!(pdev.as_ref(), "MSI not available, falling back to INTx\n");
- pdev.alloc_irq_vectors(1, 1, IrqTypes::default().with(IrqType::Intx))?
+/// The PCI interrupt vector that delivers each serviced subtree.
+///
+/// MSI-X raises a separate table entry per subtree, so subtree `N` arrives on entry `N`. MSI has a
+/// single message that every subtree raises, so all of them arrive on the one allocated entry.
+#[derive(Clone, Copy)]
+pub(crate) struct SubtreeVectors<'a> {
+ vectors: pci::IrqAllocation<'a>,
+ /// `TOP` bit of every subtree nova-core services.
+ serviced: u32,
+}
+
+impl<'a> SubtreeVectors<'a> {
+ /// Returns the interrupt type the PCI core selected for these vectors.
+ pub(crate) fn irq_type(&self) -> IrqType {
+ self.vectors.irq_type()
+ }
+
+ /// Returns the vector that delivers `subtree`, a single `TOP` bit of the form
+ /// `interrupt_tree::vector_subtree_mask` returns.
+ ///
+ /// # Errors
+ ///
+ /// `EINVAL` if `subtree` names anything other than a single subtree nova-core services.
+ pub(crate) fn vector_for(&self, subtree: u32) -> Result<pci::IrqVector<'a>> {
+ if subtree.count_ones() != 1 || subtree & self.serviced == 0 {
+ return Err(EINVAL);
}
+
+ self.vectors.vector(entry_index(self.irq_type(), subtree))
+ }
+}
+
+/// Returns the index of the allocated entry that `subtree` raises.
+///
+/// MSI-X gives subtree `N` its own table entry `N`. MSI raises its one message from every subtree,
+/// and nova-core allocates a single entry for it. nova-core never allocates INTx.
+fn entry_index(irq_type: IrqType, subtree: u32) -> u32 {
+ match irq_type {
+ IrqType::MsiX => subtree.trailing_zeros(),
+ IrqType::Msi | IrqType::Intx => 0,
+ }
+}
+
+/// Allocates the interrupt vectors that the subtrees in `serviced` require.
+///
+/// Every subtree nova-core enables at `TOP` must have an allocated vector with a registered
+/// handler, or the interrupts it raises are lost. Linux masks every MSI-X entry a driver did not
+/// allocate, so the MSI-X request covers every entry up to the highest serviced subtree. A part
+/// whose MSI-X table is smaller than that falls back to a single MSI, which serves the whole tree.
+/// nova-core does not fall back to a shared INTx line.
+///
+/// # Errors
+///
+/// `EINVAL` if `serviced` is empty. The error from the MSI request if neither type can be
+/// allocated.
+pub(crate) fn alloc_vectors(
+ pdev: &pci::Device<Bound>,
+ serviced: u32,
+) -> Result<SubtreeVectors<'_>> {
+ // One entry per subtree up to and including the highest serviced one.
+ let msix_count = u32::BITS - serviced.leading_zeros();
+ if msix_count == 0 {
+ return Err(EINVAL);
+ }
+
+ let msix = IrqTypes::default().with(IrqType::MsiX);
+ let vectors = match pdev.alloc_irq_vectors(msix_count, msix_count, msix) {
+ Ok(vectors) => vectors,
+ Err(_) => pdev.alloc_irq_vectors(1, 1, IrqTypes::default().with(IrqType::Msi))?,
};

- irq_vectors.vector(0)
+ Ok(SubtreeVectors { vectors, serviced })
}
diff --git a/drivers/gpu/nova-core/irq/hal.rs b/drivers/gpu/nova-core/irq/hal.rs
index 8de2f6e536c2..cf2d1aa080fa 100644
--- a/drivers/gpu/nova-core/irq/hal.rs
+++ b/drivers/gpu/nova-core/irq/hal.rs
@@ -50,7 +50,6 @@ impl PciIrqRearmMethod {
/// `serviced` holds the `TOP` bit of every subtree the driver services, and `subtree` holds
/// the bit of the one subtree the calling handler serves. Each method uses whichever of the
/// two its interrupt type delivers on, so both are required.
- #[expect(dead_code)]
pub(super) fn rearm(self, bar: Bar0<'_>, serviced: u32, subtree: u32) {
let subtrees = match self {
// The written value is ignored, so any write rearms delivery.
@@ -98,7 +97,6 @@ fn implemented_subtrees(&self) -> u32 {
///
/// `None` means that `irq_type` needs no rearm write. That is the case for `INTx`, which is
/// level-triggered, and which nova-core does not allocate.
- #[expect(dead_code)]
fn pci_irq_rearm_method(&self, irq_type: IrqType) -> Option<PciIrqRearmMethod>;
}

diff --git a/drivers/gpu/nova-core/irq/interrupt_tree.rs b/drivers/gpu/nova-core/irq/interrupt_tree.rs
index 9f6cfed89bec..51add9f33c89 100644
--- a/drivers/gpu/nova-core/irq/interrupt_tree.rs
+++ b/drivers/gpu/nova-core/irq/interrupt_tree.rs
@@ -16,14 +16,16 @@
Io, //
},
num::Bounded,
+ pci::IrqType,
prelude::*,
};

use crate::{
driver::Bar0,
- gpu::{
- Architecture,
- Chipset, //
+ gpu::Chipset,
+ irq::hal::{
+ cpu_interrupt_hal,
+ PciIrqRearmMethod, //
},
regs::{
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF as CPU_INTR_LEAF,
@@ -82,31 +84,43 @@ impl Sealed for super::Pending {}
pub(super) struct Tree {
/// Number of implemented leaves in this tree, either 8 or 16.
num_leaves: usize,
- /// Mask of subtree bits the architecture implements.
- subtree_mask: u32,
+ /// The subtrees this tree enables and services.
+ serviced_subtrees: u32,
+ /// Method that rearms PCI interrupt delivery, or `None` if the interrupt type needs no rearm
+ /// write.
+ rearm_method: Option<PciIrqRearmMethod>,
}

impl Tree {
- /// Creates a `Tree` sized for `chipset`.
- pub(super) fn new(chipset: Chipset) -> Self {
- let num_leaves = match chipset.arch() {
- Architecture::Turing | Architecture::Ampere | Architecture::Ada => 8,
- Architecture::Hopper | Architecture::BlackwellGB10x | Architecture::BlackwellGB20x => {
- 16
- }
- };
-
+ /// Creates a `Tree` for `chipset` covering `serviced_subtrees`, with the rearm method that
+ /// `irq_type` requires.
+ ///
+ /// Each serviced subtree must have an allocated PCI vector and a registered handler, which
+ /// [`super::alloc_vectors`] sizes the allocation for. Bits outside the subtrees the
+ /// architecture implements are dropped.
+ pub(super) fn new(chipset: Chipset, irq_type: IrqType, serviced_subtrees: u32) -> Self {
+ let hal = cpu_interrupt_hal(chipset);
Self {
- num_leaves,
- // Each subtree covers two leaves, so one bit per pair of leaves.
- subtree_mask: (1u32 << (num_leaves / 2)) - 1,
+ num_leaves: hal.num_leaves(),
+ serviced_subtrees: serviced_subtrees & hal.implemented_subtrees(),
+ rearm_method: hal.pci_irq_rearm_method(irq_type),
+ }
+ }
+
+ /// Rearms PCI interrupt delivery to the CPU after servicing `subtree`, the `TOP` bit of the
+ /// one subtree the calling handler serves.
+ ///
+ /// A handler must call this before it returns, or it receives no further interrupts.
+ pub(super) fn rearm_pci_irq(&self, bar: Bar0<'_>, subtree: u32) {
+ if let Some(method) = self.rearm_method {
+ method.rearm(bar, self.serviced_subtrees, subtree);
}
}

/// Returns a [`Top`] handle for this tree.
pub(super) fn top(&self) -> Top {
Top {
- subtree_mask: self.subtree_mask,
+ serviced_subtrees: self.serviced_subtrees,
}
}

@@ -131,9 +145,9 @@ pub(super) fn trigger(&self, bar: Bar0<'_>, vector: u32) -> Result {

/// Clears every pending bit in every implemented leaf.
///
- /// The walk runs with every implemented subtree disabled at `TOP`, and every implemented
- /// subtree is enabled on return, whatever its state on entry. The leaves cleared and the
- /// `TOP_EN` writes both reach subtrees the driver does not service.
+ /// Disables this tree's serviced subtrees at `TOP` across the walk, then enables them,
+ /// whatever their state on entry. The leaves cleared reach subtrees the driver does not
+ /// service, and the `TOP_EN` writes do not.
///
/// Call `drain()` only during probe. It must not run concurrently with an interrupt handler.
pub(super) fn drain(&self, bar: Bar0<'_>) {
@@ -152,19 +166,21 @@ pub(super) fn drain(&self, bar: Bar0<'_>) {
}

/// Top-level view of the interrupt tree, enabling and disabling whole subtrees.
+///
+/// Both writes cover the serviced subtrees alone, leaving the rest of the tree as it was.
pub(super) struct Top {
- subtree_mask: u32,
+ serviced_subtrees: u32,
}

impl Top {
- /// Enables interrupt delivery for every implemented subtree (`TOP_EN_SET`).
+ /// Enables this tree's serviced subtrees (`TOP_EN_SET`).
pub(super) fn enable(self, bar: Bar0<'_>) {
- bar.write(CPU_INTR_TOP_EN_SET, self.subtree_mask.into());
+ bar.write(CPU_INTR_TOP_EN_SET, self.serviced_subtrees.into());
}

- /// Disables interrupt delivery for every implemented subtree (`TOP_EN_CLEAR`).
+ /// Disables this tree's serviced subtrees (`TOP_EN_CLEAR`).
pub(super) fn disable(self, bar: Bar0<'_>) {
- bar.write(CPU_INTR_TOP_EN_CLEAR, self.subtree_mask.into());
+ bar.write(CPU_INTR_TOP_EN_CLEAR, self.serviced_subtrees.into());
}
}

--
2.55.0