public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
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


  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