[PATCH 16/17] gpu: nova-core: add KUnit tests for the interrupt tree and HALs

John Hubbard <[email protected]>
Newsgroups dev.linux.lists.nova-gpu,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Neither the per-architecture interrupt policy nor the vector
arithmetic touches hardware, so KUnit can cover both without a GPU.

Add three suites:

    * nova_core_gin_tree covers the leaf index bounds, the
    subtree-to-leaf mapping and its out-of-range filtering, the vector
    encoding, the masking of subtrees an architecture does not
    implement, and that every supported chipset implements the subtree
    carrying the GSP notification.

    * nova_core_gin_hal covers the tree size on each family, and the
    rearm method for each combination of family and interrupt type.

    * nova_core_falcon_hal covers the falcon retrigger gate. It is keyed
    on the architecture rather than the HAL, because GA100 shares the
    Turing HAL but does have the register.

Assisted-by: Cursor:claude-opus-5
Reviewed-by: Will Pierce <[email protected]>
Signed-off-by: John Hubbard <[email protected]>
---
 drivers/gpu/nova-core/falcon/hal.rs         |  24 ++++
 drivers/gpu/nova-core/irq/hal.rs            | 106 ++++++++++++++++-
 drivers/gpu/nova-core/irq/interrupt_tree.rs | 121 ++++++++++++++++++++
 3 files changed, 250 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/nova-core/falcon/hal.rs b/drivers/gpu/nova-core/falcon/hal.rs
index f0828b32aebb..6bff9fea1a79 100644
--- a/drivers/gpu/nova-core/falcon/hal.rs
+++ b/drivers/gpu/nova-core/falcon/hal.rs
@@ -107,3 +107,27 @@ pub(super) fn falcon_hal<E: FalconEngine + 'static>(
 
     Ok(hal)
 }
+
+#[kunit_tests(nova_core_falcon_hal)]
+mod tests {
+    use super::*;
+
+    /// Only Turing falcons lack the interrupt retrigger register. GA100 has it even though
+    /// [`falcon_hal`] gives GA100 the Turing HAL, which is why the gate is keyed on the
+    /// architecture instead.
+    #[test]
+    fn intr_retrigger_gate_per_arch() {
+        assert!(!has_intr_retrigger(Chipset::TU102));
+
+        for chipset in [
+            Chipset::GA100,
+            Chipset::GA102,
+            Chipset::AD102,
+            Chipset::GH100,
+            Chipset::GB100,
+            Chipset::GB202,
+        ] {
+            assert!(has_intr_retrigger(chipset));
+        }
+    }
+}
diff --git a/drivers/gpu/nova-core/irq/hal.rs b/drivers/gpu/nova-core/irq/hal.rs
index cf2d1aa080fa..1993e2ef5143 100644
--- a/drivers/gpu/nova-core/irq/hal.rs
+++ b/drivers/gpu/nova-core/irq/hal.rs
@@ -8,7 +8,8 @@
 
 use kernel::{
     io::Io,
-    pci::IrqType, //
+    pci::IrqType,
+    prelude::*, //
 };
 
 use crate::{
@@ -109,3 +110,106 @@ pub(super) fn cpu_interrupt_hal(chipset: Chipset) -> &'static dyn CpuInterruptHa
         }
     }
 }
+
+#[kunit_tests(nova_core_gin_hal)]
+mod tests {
+    use super::*;
+
+    use crate::gpu::Chipset;
+
+    /// Pre-Hopper parts have an 8-leaf tree, so 4 subtrees and `0x0f`.
+    #[test]
+    fn pre_hopper_tree_size() {
+        for chipset in [Chipset::TU102, Chipset::GA102, Chipset::AD102] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(hal.num_leaves(), 8);
+            assert_eq!(hal.implemented_subtrees(), 0x0f);
+        }
+    }
+
+    /// Hopper and later implement a 16-leaf tree, so 8 subtrees and `0xff`.
+    #[test]
+    fn hopper_plus_tree_size() {
+        for chipset in [Chipset::GH100, Chipset::GB100, Chipset::GB202] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(hal.num_leaves(), 16);
+            assert_eq!(hal.implemented_subtrees(), 0xff);
+        }
+    }
+
+    /// The implemented subtrees always number exactly `num_leaves / 2`, one per subtree.
+    #[test]
+    fn implemented_subtrees_matches_leaf_count() {
+        for chipset in [
+            Chipset::TU102,
+            Chipset::GA102,
+            Chipset::AD102,
+            Chipset::GH100,
+            Chipset::GB100,
+            Chipset::GB202,
+        ] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(
+                hal.implemented_subtrees().count_ones() as usize,
+                hal.num_leaves() / 2
+            );
+        }
+    }
+
+    /// Only pre-Hopper MSI rearms through the configuration-space mirror. MSI on Hopper and later
+    /// cycles the `TOP` enables of every serviced subtree.
+    #[test]
+    fn msi_rearm_method_per_arch() {
+        for chipset in [Chipset::TU102, Chipset::GA102, Chipset::AD102] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(
+                hal.pci_irq_rearm_method(IrqType::Msi),
+                Some(PciIrqRearmMethod::ConfigMirrorEoi)
+            );
+        }
+
+        for chipset in [Chipset::GH100, Chipset::GB100, Chipset::GB202] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(
+                hal.pci_irq_rearm_method(IrqType::Msi),
+                Some(PciIrqRearmMethod::TopEnableCycleServiced)
+            );
+        }
+    }
+
+    /// MSI-X gives each subtree its own table entry, so on every architecture its rearm cycles
+    /// only the subtree the handler serves.
+    #[test]
+    fn msix_rearms_one_subtree_on_every_arch() {
+        for chipset in [
+            Chipset::TU102,
+            Chipset::GA102,
+            Chipset::AD102,
+            Chipset::GH100,
+            Chipset::GB100,
+            Chipset::GB202,
+        ] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(
+                hal.pci_irq_rearm_method(IrqType::MsiX),
+                Some(PciIrqRearmMethod::TopEnableCycleSubtree)
+            );
+        }
+    }
+
+    /// `INTx` is level-triggered and needs no rearm write on any architecture.
+    #[test]
+    fn intx_needs_no_rearm() {
+        for chipset in [
+            Chipset::TU102,
+            Chipset::GA102,
+            Chipset::AD102,
+            Chipset::GH100,
+            Chipset::GB100,
+            Chipset::GB202,
+        ] {
+            let hal = cpu_interrupt_hal(chipset);
+            assert_eq!(hal.pci_irq_rearm_method(IrqType::Intx), None);
+        }
+    }
+}
diff --git a/drivers/gpu/nova-core/irq/interrupt_tree.rs b/drivers/gpu/nova-core/irq/interrupt_tree.rs
index f4f1494cddba..42e72fa8089e 100644
--- a/drivers/gpu/nova-core/irq/interrupt_tree.rs
+++ b/drivers/gpu/nova-core/irq/interrupt_tree.rs
@@ -302,3 +302,124 @@ pub(super) fn clear_vectors(&self, bar: Bar0<'_>, vectors: u32) {
         }
     }
 }
+
+#[kunit_tests(nova_core_gin_tree)]
+mod tests {
+    use super::*;
+
+    /// A leaf index is a `Bounded<usize, 4>`, so it accepts 0..=15 and rejects 16.
+    #[test]
+    fn leaf_index_bounds() {
+        assert!(LeafIndex::try_new(0).is_some());
+        assert!(LeafIndex::try_new(15).is_some());
+        assert!(LeafIndex::try_new(16).is_none());
+    }
+
+    /// Subtree `N` covers the two adjacent leaves `2N` and `2N + 1`.
+    #[test]
+    fn subtree_covers_two_adjacent_leaves() {
+        let tree = Tree {
+            num_leaves: 16,
+            serviced_subtrees: 0xff,
+            rearm_method: None,
+        };
+
+        for index in 0..8usize {
+            let mut leaves = Subtree { index }.iter_leaves(&tree);
+            assert_eq!(leaves.next().map(|leaf| leaf.index.get()), Some(index * 2));
+            assert_eq!(
+                leaves.next().map(|leaf| leaf.index.get()),
+                Some(index * 2 + 1)
+            );
+            assert!(leaves.next().is_none());
+        }
+    }
+
+    /// Leaves that fall outside the addressable range are filtered out, never panicking. The
+    /// filter is the [`LeafIndex`] bound, not the tree's leaf count, so this holds even on the
+    /// widest tree.
+    #[test]
+    fn subtree_leaves_out_of_range_are_filtered() {
+        let tree = Tree {
+            num_leaves: 16,
+            serviced_subtrees: 0xff,
+            rearm_method: None,
+        };
+
+        // Subtree 8 would cover leaves 16 and 17, both beyond the leaf index range.
+        assert!(Subtree { index: 8 }.iter_leaves(&tree).next().is_none());
+    }
+
+    /// The production [`vector_leaf_bit`] maps every vector to a `(leaf, bit)` pair, valid leaves
+    /// stay within [`LeafIndex`], and the fixed doorbell (129) and GSP (155) vectors land where
+    /// the handlers expect.
+    #[test]
+    fn vector_maps_to_leaf_and_bit() {
+        // Every vector of a 16-leaf tree maps to an addressable leaf and a bit in 0..32.
+        for vector in 0u32..(16 * 32) {
+            let (leaf, bit) = vector_leaf_bit(vector);
+
+            assert!(LeafIndex::try_new(leaf).is_some());
+            assert!(bit < 32);
+            assert_eq!(leaf as u32 * 32 + bit, vector);
+        }
+
+        // The fixed vectors the handlers rely on: CPU doorbell 129 and GSP notification 155, both
+        // in leaf 4, which is present on both the 8-leaf (pre-Hopper) and 16-leaf trees.
+        assert_eq!(vector_leaf_bit(129), (4, 1));
+        assert_eq!(vector_leaf_bit(155), (4, 27));
+        assert!(LeafIndex::try_new(vector_leaf_bit(155).0).is_some());
+
+        // The first vector beyond the 16-leaf tree lands in leaf 16, which is out of range.
+        assert!(LeafIndex::try_new(vector_leaf_bit(16 * 32).0).is_none());
+    }
+
+    /// [`vector_subtree_mask`] agrees with [`vector_leaf_bit`] on which subtree holds a vector,
+    /// and the doorbell (129) and GSP (155) vectors share one, so a single allocation and a single
+    /// enabled subtree serve both.
+    #[test]
+    fn vector_maps_to_subtree() {
+        for vector in 0u32..(16 * 32) {
+            let (leaf, _) = vector_leaf_bit(vector);
+
+            assert_eq!(vector_subtree_mask(vector), 1u32 << (leaf / 2));
+        }
+
+        assert_eq!(vector_subtree_mask(155), 1 << 2);
+        assert_eq!(vector_subtree_mask(129), vector_subtree_mask(155));
+    }
+
+    /// [`Tree::new`] drops subtrees the architecture does not implement, so a caller cannot enable
+    /// a `TOP` bit with no leaves behind it.
+    #[test]
+    fn tree_new_masks_unimplemented_subtrees() {
+        assert_eq!(
+            Tree::new(Chipset::TU102, IrqType::Msi, 0xff).serviced_subtrees,
+            0x0f
+        );
+        assert_eq!(
+            Tree::new(Chipset::GH100, IrqType::Msi, 0xff).serviced_subtrees,
+            0xff
+        );
+    }
+
+    /// Every supported chipset implements the subtree that carries the GSP notification.
+    #[test]
+    fn serviced_subtree_is_implemented_everywhere() {
+        let serviced = crate::irq::gsp::GSP_SUBTREE;
+
+        for chipset in [
+            Chipset::TU102,
+            Chipset::GA102,
+            Chipset::AD102,
+            Chipset::GH100,
+            Chipset::GB100,
+            Chipset::GB202,
+        ] {
+            assert_eq!(
+                serviced & !cpu_interrupt_hal(chipset).implemented_subtrees(),
+                0
+            );
+        }
+    }
+}
-- 
2.55.0
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.