From: Joel Fernandes <joelagnelf@nvidia.com>
To: linux-kernel@vger.kernel.org
Cc: "Danilo Krummrich" <dakr@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"John Hubbard" <jhubbard@nvidia.com>,
"Alice Ryhl" <aliceryhl@google.com>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org,
rust-for-linux@vger.kernel.org, linux-doc@vger.kernel.org,
"Joel Fernandes" <joelagnelf@nvidia.com>
Subject: [PATCH v1 5/7] gpu: nova-core: add INTR_CTRL interrupt controller API
Date: Fri, 1 May 2026 16:58:23 -0400 [thread overview]
Message-ID: <20260501205825.73614-6-joelagnelf@nvidia.com> (raw)
In-Reply-To: <20260501205825.73614-1-joelagnelf@nvidia.com>
Create the irq/ module with a type-state INTR_CTRL interrupt
controller API. The IntrCtrl struct provides factory methods for Top
and Leaf objects that use a sealed State trait with Idle/Pending types
to enforce correct usage at compile time.
The type-state pattern ensures ack() is only callable after
read_pending() has cached the hardware state, preventing mismatched
masks at compile time.
The later CPU doorbell self-test will make use of it.
Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
drivers/gpu/nova-core/irq.rs | 2 +
drivers/gpu/nova-core/irq/intr_ctrl.rs | 281 +++++++++++++++++++++++++
drivers/gpu/nova-core/nova_core.rs | 1 +
3 files changed, 284 insertions(+)
create mode 100644 drivers/gpu/nova-core/irq/intr_ctrl.rs
diff --git a/drivers/gpu/nova-core/irq.rs b/drivers/gpu/nova-core/irq.rs
index 3a2a40519f11..01ae638bf494 100644
--- a/drivers/gpu/nova-core/irq.rs
+++ b/drivers/gpu/nova-core/irq.rs
@@ -10,6 +10,8 @@
prelude::*,
};
+mod intr_ctrl;
+
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/intr_ctrl.rs b/drivers/gpu/nova-core/irq/intr_ctrl.rs
new file mode 100644
index 000000000000..dde77cc1f42f
--- /dev/null
+++ b/drivers/gpu/nova-core/irq/intr_ctrl.rs
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! GPU interrupt controller support (INTR_CTRL).
+//!
+//! Each PCIe function (PF and each VF, also known as a GFID) has its own
+//! interrupt tree. In this module, we only interact with the PF tree.
+//! The VF interacts with its own tree (which appears as a PF tree to it).
+//!
+//! See `Documentation/gpu/nova/core/intr-ctrl.rst` for detailed documentation
+//! of the INTR_CTRL architecture.
+
+use kernel::{
+ io::{
+ register::Array,
+ Io, //
+ },
+ num::Bounded,
+};
+
+use crate::{driver::Bar0, gpu::Chipset, regs};
+
+/// Type alias for a leaf interrupt index, bounded to valid values 0-15.
+pub(super) type LeafIndex = Bounded<usize, 4>;
+
+// Type-state for `Top` and `Leaf`.
+//
+// `Top` follows Idle -> Unarmed -> Pending -> consumed (rearmed).
+// `Leaf` uses Idle -> Pending to catch.
+//
+// This catches issues at compile time where we perform an operation
+// on an object in the wrong state (example, rearming `Top` without reading
+// pending bits first).
+/// Sealed trait representing the interrupt controller state.
+pub(super) trait State: private::Sealed {}
+
+/// Idle state: TOP_EN may or may not be armed; no snapshot held.
+pub(super) struct Idle;
+impl State for Idle {}
+
+/// Unarmed state: TOP_EN was just cleared by this Top handle, snapshot not yet read.
+pub(super) struct Unarmed;
+impl State for Unarmed {}
+
+/// Pending state: interrupt mask has been read from hardware.
+pub(super) struct Pending {
+ mask: u32,
+}
+impl State for Pending {}
+
+mod private {
+ pub(in crate::irq) trait Sealed {}
+ impl Sealed for super::Idle {}
+ impl Sealed for super::Unarmed {}
+ impl Sealed for super::Pending {}
+}
+
+/// Interrupt controller for a single PCIe function's interrupt tree.
+#[derive(Clone)]
+pub(super) struct IntrCtrl {
+ subtree_mask: u8,
+}
+
+impl IntrCtrl {
+ /// Create an `IntrCtrl` configured for the given chipset's interrupt tree width.
+ pub(super) fn new(chipset: Chipset) -> Self {
+ // Each TOP bit covers 2 leaves; subtree_mask has one bit per subtree.
+ // Pre-Hopper: 8 leaves / 2 = 4 subtrees -> 0x0f (bits [3:0])
+ // Hopper+: 16 leaves / 2 = 8 subtrees -> 0xff (bits [7:0])
+ Self {
+ subtree_mask: if chipset.arch().is_pre_hopper() {
+ 0xf
+ } else {
+ 0xff
+ },
+ }
+ }
+
+ /// Return a [`Top`] handle in the [`Idle`] state for this controller.
+ pub(super) fn top(&self) -> Top<Idle> {
+ Top {
+ subtree_mask: self.subtree_mask,
+ state: Idle,
+ }
+ }
+
+ /// Return a [`Leaf`] handle in the [`Idle`] state for the given leaf index.
+ pub(super) fn leaf(&self, index: LeafIndex) -> Leaf<Idle> {
+ Leaf::from_index(index)
+ }
+
+ /// Trigger a CPU doorbell interrupt for the given MSI vector number.
+ pub(super) fn trigger(&self, bar: &Bar0, vector: u32) {
+ bar.write(regs::NV_VF_INTR_LEAF_TRIGGER, vector.into());
+ }
+
+ /// Drain any pending interrupts on this controller.
+ ///
+ /// Walks all enabled subtrees, reads each leaf's pending mask, and acks
+ /// any pending bits. Useful for clearing stale interrupt state, e.g.,
+ /// state leftover when GSP booted.
+ pub(super) fn drain(&self, bar: &Bar0) {
+ let top = self.top().unarm(bar).read_pending(bar);
+
+ for subtree in top.iter_subtrees() {
+ for leaf in subtree.iter_pending_leaves(self, bar) {
+ leaf.ack(bar);
+ }
+ }
+
+ top.rearm(bar);
+ }
+}
+
+/// Top-level interrupt controller view.
+pub(super) struct Top<S: State = Idle> {
+ subtree_mask: u8,
+ state: S,
+}
+
+impl Top<Idle> {
+ /// Arm the controller (write TOP_EN_SET). Use for one-shot initial
+ /// setup before any interrupts are expected. The ISR's normal
+ /// re-arm path goes through `unarm()` -> `read_pending()` ->
+ /// `Top<Pending>::rearm()` instead.
+ pub(super) fn arm(self, bar: &Bar0) {
+ bar.write(
+ regs::NV_VF_INTR_TOP_EN_SET,
+ u32::from(self.subtree_mask).into(),
+ );
+ }
+
+ /// Unarm the controller (write TOP_EN_CLEAR). MSI is edge-triggered,
+ /// so this stops the GPU from firing redundant MSI writes over PCIe
+ /// while the host drains the tree. Consumes self and transitions to
+ /// `Top<Unarmed>`, which can then `read_pending()`.
+ pub(super) fn unarm(self, bar: &Bar0) -> Top<Unarmed> {
+ bar.write(
+ regs::NV_VF_INTR_TOP_EN_CLEAR,
+ u32::from(self.subtree_mask).into(),
+ );
+ Top {
+ subtree_mask: self.subtree_mask,
+ state: Unarmed,
+ }
+ }
+}
+
+impl Top<Unarmed> {
+ /// Read the TOP register's pending bitmask. Consumes self and
+ /// returns a `Top<Pending>` carrying the snapshot.
+ pub(super) fn read_pending(self, bar: &Bar0) -> Top<Pending> {
+ let mask = bar.read(regs::NV_VF_INTR_TOP).into_raw();
+ Top {
+ subtree_mask: self.subtree_mask,
+ state: Pending { mask },
+ }
+ }
+}
+
+/// One subtree in the INTR_TOP pending mask (covers two adjacent leaf indices).
+#[derive(Clone, Copy)]
+pub(super) struct Subtree {
+ index: usize,
+}
+
+impl Subtree {
+ /// Yields the two [`Leaf`] slots covered by this subtree's TOP bit.
+ fn iter_leaves<'a>(
+ self,
+ ctrl: &'a IntrCtrl,
+ ) -> impl Iterator<Item = Leaf<Idle>> + 'a {
+ // Each subtree covers two adjacent leaf indices for all architectures.
+ (0..2usize).filter_map(move |offset| {
+ // self.index is 0-31 and offset is 0-1, so idx is at most 63.
+ let idx = self.index * 2 + offset;
+ LeafIndex::try_new(idx).map(|idx| ctrl.leaf(idx))
+ })
+ }
+
+ /// Like [`Self::iter_leaves`], but keeps only leaves with a non-zero pending mask.
+ pub(super) fn iter_pending_leaves<'a>(
+ self,
+ ctrl: &'a IntrCtrl,
+ bar: &'a Bar0,
+ ) -> impl Iterator<Item = Leaf<Pending>> + 'a {
+ self.iter_leaves(ctrl).filter_map(move |idle| {
+ let pending = idle.read_pending(bar);
+ (pending.mask() != 0).then_some(pending)
+ })
+ }
+}
+
+impl Top<Pending> {
+ /// Return the raw TOP pending bitmask snapshot.
+ pub(super) fn mask(&self) -> u32 {
+ self.state.mask
+ }
+
+ /// Iterate over all subtrees with a pending TOP bit set in the snapshot.
+ pub(super) fn iter_subtrees(&self) -> impl Iterator<Item = Subtree> + '_ {
+ (0..32usize)
+ .filter(move |&bit| self.state.mask & (1u32 << bit) != 0)
+ .map(|index| Subtree { index })
+ }
+
+ /// Re-arm the controller (write TOP_EN_SET). Consumes self so the
+ /// pending snapshot cannot be consulted or re-iterated afterwards.
+ pub(super) fn rearm(self, bar: &Bar0) {
+ bar.write(
+ regs::NV_VF_INTR_TOP_EN_SET,
+ u32::from(self.subtree_mask).into(),
+ );
+ }
+}
+
+/// Leaf interrupt controller view for one interrupt leaf.
+pub(super) struct Leaf<S: State = Idle> {
+ index: LeafIndex,
+ state: S,
+}
+
+impl<Left: State, Right: State> PartialEq<Leaf<Right>> for Leaf<Left> {
+ fn eq(&self, other: &Leaf<Right>) -> bool {
+ self.index == other.index
+ }
+}
+
+impl<S: State> Eq for Leaf<S> {}
+
+// All `try_at().unwrap()` calls below are safe: `LeafIndex` is `Bounded<usize, 4>`,
+// guaranteeing values 0-15, and all INTR_CTRL leaf register arrays have 16 elements.
+impl Leaf<Idle> {
+ /// Construct a [`Leaf`] handle for the given leaf index.
+ pub(super) fn from_index(index: LeafIndex) -> Self {
+ Leaf { index, state: Idle }
+ }
+
+ /// Enable the bits in `mask` in this leaf's EN_SET register.
+ pub(super) fn allow(&self, bar: &Bar0, mask: u32) {
+ bar.write(
+ regs::NV_VF_INTR_LEAF_EN_SET::try_at(self.index.get()).unwrap(),
+ mask.into(),
+ );
+ }
+
+ /// Disable the bits in `mask` in this leaf's EN_CLEAR register.
+ pub(super) fn block(&self, bar: &Bar0, mask: u32) {
+ bar.write(
+ regs::NV_VF_INTR_LEAF_EN_CLEAR::try_at(self.index.get()).unwrap(),
+ mask.into(),
+ );
+ }
+
+ /// Read this leaf's pending interrupt mask and transition to [`Pending`].
+ pub(super) fn read_pending(self, bar: &Bar0) -> Leaf<Pending> {
+ let mask = bar
+ .read(regs::NV_VF_INTR_LEAF::try_at(self.index.get()).unwrap())
+ .into_raw();
+ Leaf {
+ index: self.index,
+ state: Pending { mask },
+ }
+ }
+}
+
+impl Leaf<Pending> {
+ /// Return the raw pending interrupt bitmask read from hardware.
+ pub(super) fn mask(&self) -> u32 {
+ self.state.mask
+ }
+
+ /// Acknowledge all pending bits by writing the mask back to the leaf register.
+ pub(super) fn ack(&self, bar: &Bar0) {
+ if self.state.mask != 0 {
+ bar.write(
+ regs::NV_VF_INTR_LEAF::try_at(self.index.get()).unwrap(),
+ self.state.mask.into(),
+ );
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index 837aa2d36a0e..6d0e4b2f53c7 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -19,6 +19,7 @@
mod firmware;
mod gpu;
mod gsp;
+#[expect(dead_code)]
mod irq;
#[macro_use]
mod num;
--
2.34.1
next prev parent reply other threads:[~2026-05-01 20:58 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-01 20:58 [PATCH v1 0/7] gpu: nova-core: add INTR_CTRL interrupt controller and CPU doorbell self-test Joel Fernandes
2026-05-01 20:58 ` [PATCH v1 1/7] rust: sync: completion: add wait_for_completion_timeout() Joel Fernandes
2026-05-05 12:17 ` Miguel Ojeda
2026-05-05 20:19 ` Joel Fernandes
2026-05-01 20:58 ` [PATCH v1 2/7] gpu: nova-core: allocate PCI MSI vector during probe Joel Fernandes
2026-05-01 20:58 ` [PATCH v1 3/7] gpu: nova-core: add interrupt controller register definitions Joel Fernandes
2026-05-01 20:58 ` [PATCH v1 4/7] gpu: nova-core: add Architecture::is_pre_hopper() helper Joel Fernandes
2026-05-01 20:58 ` Joel Fernandes [this message]
2026-05-01 20:58 ` [PATCH v1 6/7] gpu: nova-core: add CPU doorbell IRQ self-test Joel Fernandes
2026-05-01 20:58 ` [PATCH v1 7/7] gpu: nova-core: document INTR_CTRL interrupt tree Joel Fernandes
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=20260501205825.73614-6-joelagnelf@nvidia.com \
--to=joelagnelf@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=corbet@lwn.net \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=jhubbard@nvidia.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=skhan@linuxfoundation.org \
--cc=tmgross@umich.edu \
--cc=tzimmermann@suse.de \
/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