The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: John Hubbard <jhubbard@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
	Joel Fernandes <joel@joelfernandes.org>,
	Alexandre Courbot <acourbot@nvidia.com>
Cc: "Timur Tabi" <ttabi@nvidia.com>,
	"Alistair Popple" <apopple@nvidia.com>,
	"Eliot Courtney" <ecourtney@nvidia.com>,
	"Shashank Sharma" <shashanks@nvidia.com>,
	"Zhi Wang" <zhiw@nvidia.com>, "David Airlie" <airlied@gmail.com>,
	"Simona Vetter" <simona@ffwll.ch>,
	"Bjorn Helgaas" <bhelgaas@google.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	nova-gpu@lists.linux.dev, LKML <linux-kernel@vger.kernel.org>,
	"Joel Fernandes" <joelagnelf@nvidia.com>,
	"John Hubbard" <jhubbard@nvidia.com>,
	"Will Pierce" <wpierce@nvidia.com>
Subject: [PATCH 06/17] gpu: nova-core: add the GIN interrupt tree API
Date: Fri,  7 Aug 2026 20:11:08 -0700	[thread overview]
Message-ID: <20260808031120.363869-7-jhubbard@nvidia.com> (raw)
In-Reply-To: <20260808031120.363869-1-jhubbard@nvidia.com>

From: Joel Fernandes <joelagnelf@nvidia.com>

Servicing a GIN leaf has a required order: read its pending bits, then
clear them. Clearing a leaf before reading it discards every vector
latched in it, and nothing reports the loss.

Add an API for one PCIe function's CPU interrupt tree. The leaf handle
carries that order as a type state, so the wrong order does not compile.

The CPU doorbell self-test added later in this series is the first user.

Reviewed-by: Will Pierce <wpierce@nvidia.com>
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
[jhubbard: use the canonical NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_*
 register names, name the module interrupt_tree with a Tree type, drop
 the type state from the Top handle, take the leaf count from the
 chipset, define the vector encoding here, reject a trigger for a vector
 outside the tree, and read every implemented leaf in drain() rather
 than descending from the TOP registers, which cannot see a vector that
 latched while disabled]
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
 drivers/gpu/nova-core/irq.rs                |   2 +
 drivers/gpu/nova-core/irq/interrupt_tree.rs | 257 ++++++++++++++++++++
 drivers/gpu/nova-core/nova_core.rs          |   1 +
 3 files changed, 260 insertions(+)
 create mode 100644 drivers/gpu/nova-core/irq/interrupt_tree.rs

diff --git a/drivers/gpu/nova-core/irq.rs b/drivers/gpu/nova-core/irq.rs
index 48900c734cb6..b70efc239334 100644
--- a/drivers/gpu/nova-core/irq.rs
+++ b/drivers/gpu/nova-core/irq.rs
@@ -11,6 +11,8 @@
     prelude::*,
 };
 
+mod interrupt_tree;
+
 pub(crate) fn alloc_vector(pdev: &pci::Device<Bound>) -> Result<pci::IrqVector<'_>> {
     let msi_types = IrqTypes::default().with(IrqType::Msi).with(IrqType::MsiX);
 
diff --git a/drivers/gpu/nova-core/irq/interrupt_tree.rs b/drivers/gpu/nova-core/irq/interrupt_tree.rs
new file mode 100644
index 000000000000..9f6cfed89bec
--- /dev/null
+++ b/drivers/gpu/nova-core/irq/interrupt_tree.rs
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Type-state API for walking the GIN CPU interrupt tree.
+//!
+//! Each PCIe function has its own interrupt tree, and this module drives one function's CPU tree.
+//! A [`Leaf`] carries a type state, `Idle` -> `Pending`, so that clearing one before reading it
+//! fails to compile.
+//!
+//! The type state orders the operations on one [`Leaf`] value. Serializing access to the tree is
+//! the caller's responsibility.
+
+use kernel::{
+    io::{
+        register::Array,
+        Io, //
+    },
+    num::Bounded,
+    prelude::*,
+};
+
+use crate::{
+    driver::Bar0,
+    gpu::{
+        Architecture,
+        Chipset, //
+    },
+    regs::{
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF as CPU_INTR_LEAF,
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_EN_CLEAR as CPU_INTR_LEAF_EN_CLEAR,
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_EN_SET as CPU_INTR_LEAF_EN_SET,
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_TRIGGER as CPU_INTR_LEAF_TRIGGER,
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_TOP_EN_CLEAR as CPU_INTR_TOP_EN_CLEAR,
+        NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_TOP_EN_SET as CPU_INTR_TOP_EN_SET, //
+    },
+};
+
+/// Index of a leaf register, bounded to the `0..16` range covered by the leaf register arrays.
+pub(super) type LeafIndex = Bounded<usize, 4>;
+
+/// Maps an interrupt `vector` to its position in the tree: the leaf that carries it
+/// (`vector / 32`) and the bit index within that leaf (`vector % 32`).
+///
+/// The returned leaf is a raw index. [`LeafIndex::try_new`] bounds it to the leaf register
+/// arrays, and the architecture's leaf count is a separate, narrower bound.
+pub(super) const fn vector_leaf_bit(vector: u32) -> (usize, u32) {
+    (crate::num::u32_as_usize(vector / 32), vector % 32)
+}
+
+/// Maps an interrupt `vector` to the `TOP` enable mask of the subtree that carries it.
+///
+/// A subtree covers two adjacent leaves, so the vector's leaf is in subtree `vector / 64`. The
+/// result has that subtree's bit set, in the form `TOP_EN_SET` and `TOP_EN_CLEAR` take as a
+/// value.
+///
+/// The result is not validated against the subtrees that the architecture supports.
+pub(super) const fn vector_subtree_mask(vector: u32) -> u32 {
+    1 << (vector / 64)
+}
+
+/// Type state of a [`Leaf`] handle: `Idle` before its pending bits are read, `Pending` after.
+pub(super) trait State: private::Sealed {}
+
+/// State in which the handle holds no pending bits.
+pub(super) struct Idle;
+impl State for Idle {}
+
+/// State holding the pending bits read from hardware.
+pub(super) struct Pending {
+    pending_bits: u32,
+}
+impl State for Pending {}
+
+mod private {
+    pub(in crate::irq) trait Sealed {}
+    impl Sealed for super::Idle {}
+    impl Sealed for super::Pending {}
+}
+
+/// The GIN CPU interrupt tree for a single PCIe function.
+#[derive(Clone)]
+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,
+}
+
+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
+            }
+        };
+
+        Self {
+            num_leaves,
+            // Each subtree covers two leaves, so one bit per pair of leaves.
+            subtree_mask: (1u32 << (num_leaves / 2)) - 1,
+        }
+    }
+
+    /// Returns a [`Top`] handle for this tree.
+    pub(super) fn top(&self) -> Top {
+        Top {
+            subtree_mask: self.subtree_mask,
+        }
+    }
+
+    /// Returns a [`Leaf`] handle in the [`Idle`] state for `index`.
+    pub(super) fn leaf(&self, index: LeafIndex) -> Leaf<Idle> {
+        Leaf::from_index(index)
+    }
+
+    /// Injects a software interrupt for `vector` via the trigger register.
+    ///
+    /// # Errors
+    ///
+    /// `EINVAL` if `vector` lies outside this tree (`vector >= num_leaves * 32`). `EOVERFLOW` if
+    /// `vector` does not fit in the trigger register's vector field.
+    pub(super) fn trigger(&self, bar: Bar0<'_>, vector: u32) -> Result {
+        if crate::num::u32_as_usize(vector) >= self.num_leaves * 32 {
+            return Err(EINVAL);
+        }
+        bar.write_reg(CPU_INTR_LEAF_TRIGGER::zeroed().try_with_vector(vector)?);
+        Ok(())
+    }
+
+    /// 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.
+    ///
+    /// Call `drain()` only during probe. It must not run concurrently with an interrupt handler.
+    pub(super) fn drain(&self, bar: Bar0<'_>) {
+        self.top().disable(bar);
+
+        // `TOP` summarizes enabled leaf bits, so a vector that latched while it was disabled does
+        // not appear there.
+        for index in 0..(self.num_leaves / 2) {
+            for leaf in (Subtree { index }).iter_pending_leaves(self, bar) {
+                leaf.clear_pending(bar);
+            }
+        }
+
+        self.top().enable(bar);
+    }
+}
+
+/// Top-level view of the interrupt tree, enabling and disabling whole subtrees.
+pub(super) struct Top {
+    subtree_mask: u32,
+}
+
+impl Top {
+    /// Enables interrupt delivery for every implemented subtree (`TOP_EN_SET`).
+    pub(super) fn enable(self, bar: Bar0<'_>) {
+        bar.write(CPU_INTR_TOP_EN_SET, self.subtree_mask.into());
+    }
+
+    /// Disables interrupt delivery for every implemented subtree (`TOP_EN_CLEAR`).
+    pub(super) fn disable(self, bar: Bar0<'_>) {
+        bar.write(CPU_INTR_TOP_EN_CLEAR, self.subtree_mask.into());
+    }
+}
+
+/// One subtree of the interrupt tree, covering two adjacent leaves.
+#[derive(Clone, Copy)]
+pub(super) struct Subtree {
+    index: usize,
+}
+
+impl Subtree {
+    /// Yields the two [`Leaf`] handles covered by this subtree.
+    fn iter_leaves<'a>(self, tree: &'a Tree) -> impl Iterator<Item = Leaf<Idle>> + 'a {
+        // A `Subtree` is constructed only for an implemented index. `LeafIndex::try_new` drops
+        // any index beyond the leaf register arrays instead of panicking.
+        (0..2usize).filter_map(move |offset| {
+            let idx = self.index * 2 + offset;
+            LeafIndex::try_new(idx).map(|idx| tree.leaf(idx))
+        })
+    }
+
+    /// Like [`Self::iter_leaves`], but keeps only leaves with non-zero pending bits.
+    pub(super) fn iter_pending_leaves<'a>(
+        self,
+        tree: &'a Tree,
+        bar: Bar0<'a>,
+    ) -> impl Iterator<Item = Leaf<Pending>> + 'a {
+        self.iter_leaves(tree).filter_map(move |idle| {
+            let pending = idle.read_pending(bar);
+            (pending.pending_bits() != 0).then_some(pending)
+        })
+    }
+}
+
+/// View of a single interrupt leaf.
+pub(super) struct Leaf<S: State = Idle> {
+    index: LeafIndex,
+    state: S,
+}
+
+// The `try_at(...)` calls below cannot fail: `LeafIndex` is `Bounded<usize, 4>`, so its value is
+// in 0..16, and every leaf register array has 16 elements.
+impl Leaf<Idle> {
+    /// Creates a [`Leaf`] handle for `index`.
+    pub(super) fn from_index(index: LeafIndex) -> Self {
+        Leaf { index, state: Idle }
+    }
+
+    /// Enables the vectors set in `vectors` for this leaf (`LEAF_EN_SET`).
+    ///
+    /// This is the per-vector counterpart of [`Top::enable`], which enables a whole subtree.
+    pub(super) fn enable(&self, bar: Bar0<'_>, vectors: u32) {
+        if let Some(loc) = CPU_INTR_LEAF_EN_SET::try_at(self.index.get()) {
+            bar.write(loc, vectors.into());
+        }
+    }
+
+    /// Disables the vectors set in `vectors` for this leaf (`LEAF_EN_CLEAR`).
+    pub(super) fn disable(&self, bar: Bar0<'_>, vectors: u32) {
+        if let Some(loc) = CPU_INTR_LEAF_EN_CLEAR::try_at(self.index.get()) {
+            bar.write(loc, vectors.into());
+        }
+    }
+
+    /// Reads this leaf's pending bits and transitions to [`Pending`].
+    pub(super) fn read_pending(self, bar: Bar0<'_>) -> Leaf<Pending> {
+        let pending_bits = CPU_INTR_LEAF::try_at(self.index.get())
+            .map(|loc| bar.read(loc).into_raw())
+            .unwrap_or(0);
+        Leaf {
+            index: self.index,
+            state: Pending { pending_bits },
+        }
+    }
+}
+
+impl Leaf<Pending> {
+    /// Returns the pending bits read from hardware.
+    pub(super) fn pending_bits(&self) -> u32 {
+        self.state.pending_bits
+    }
+
+    /// Clears every pending vector by writing its bits back (write-1-to-clear).
+    pub(super) fn clear_pending(&self, bar: Bar0<'_>) {
+        if self.state.pending_bits != 0 {
+            if let Some(loc) = CPU_INTR_LEAF::try_at(self.index.get()) {
+                bar.write(loc, self.state.pending_bits.into());
+            }
+        }
+    }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index 68b5abfe494d..dfd11dfe562c 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -17,6 +17,7 @@
 mod fsp;
 mod gpu;
 mod gsp;
+#[expect(dead_code)]
 mod irq;
 mod mctp;
 #[macro_use]
-- 
2.55.0


  parent reply	other threads:[~2026-08-08  3:11 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-08  3:11 [PATCH 00/17] nova-core: GPU interrupt support and GSP event delivery John Hubbard
2026-08-08  3:11 ` [PATCH 01/17] rust: sync: completion: add wait_for_completion_timeout() John Hubbard
     [not found]   ` <DKK2DM3VK6TF.3KBBWP7S4A8T1@nvidia.com>
2026-08-09 21:43     ` John Hubbard
2026-08-08  3:11 ` [PATCH 02/17] rust: pci: expose the whole interrupt vector allocation John Hubbard
2026-08-09 13:27   ` Danilo Krummrich
2026-08-08  3:11 ` [PATCH 03/17] rust: pci: expose the allocated interrupt type John Hubbard
2026-08-09 13:24   ` Danilo Krummrich
2026-08-09 21:42     ` John Hubbard
2026-08-08  3:11 ` [PATCH 04/17] gpu: nova-core: allocate PCI MSI vector during probe John Hubbard
2026-08-08  3:11 ` [PATCH 05/17] gpu: nova-core: add the GIN CPU interrupt tree and MSI EOI registers John Hubbard
2026-08-08  3:11 ` John Hubbard [this message]
2026-08-08  3:11 ` [PATCH 07/17] gpu: nova-core: add the per-architecture GIN CPU interrupt HAL John Hubbard
2026-08-08  3:11 ` [PATCH 08/17] gpu: nova-core: allocate interrupt vectors for the serviced subtrees John Hubbard
2026-08-08  3:11 ` [PATCH 09/17] gpu: nova-core: add an interrupt delivery self-test John Hubbard
2026-08-08  3:11 ` [PATCH 10/17] gpu: nova-core: dispatch GSP events instead of discarding them John Hubbard
2026-08-08  3:11 ` [PATCH 11/17] gpu: nova-core: match GSP RPC replies by sequence, not just function John Hubbard
2026-08-08  3:11 ` [PATCH 12/17] gpu: nova-core: recover the GSP receive path from corrupt framing John Hubbard
2026-08-08  3:11 ` [PATCH 13/17] gpu: nova-core: bound a GSP wait by a single deadline John Hubbard
2026-08-08  3:11 ` [PATCH 14/17] gpu: nova-core: drive GSP events with the SWGEN0 interrupt John Hubbard
2026-08-08  3:11 ` [PATCH 15/17] gpu: nova-core: retrigger the GSP falcon and clear every latched cause John Hubbard
2026-08-08  3:11 ` [PATCH 16/17] gpu: nova-core: add KUnit tests for the interrupt tree and HALs John Hubbard
2026-08-08  3:11 ` [PATCH 17/17] gpu: nova-core: document the GIN interrupt controller and GSP events John Hubbard

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260808031120.363869-7-jhubbard@nvidia.com \
    --to=jhubbard@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=airlied@gmail.com \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=apopple@nvidia.com \
    --cc=bhelgaas@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=ecourtney@nvidia.com \
    --cc=gary@garyguo.net \
    --cc=joel@joelfernandes.org \
    --cc=joelagnelf@nvidia.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=nova-gpu@lists.linux.dev \
    --cc=ojeda@kernel.org \
    --cc=shashanks@nvidia.com \
    --cc=simona@ffwll.ch \
    --cc=tmgross@umich.edu \
    --cc=ttabi@nvidia.com \
    --cc=wpierce@nvidia.com \
    --cc=zhiw@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox