gpu: nova-core: add KUnit tests for the interrupt tree

The vector arithmetic and the leaf and subtree sets touch no hardware,
so KUnit can cover them without a GPU. A wrong leaf, bit or subtree for
a vector would otherwise show up only as a lost interrupt on hardware.

Add the nova_core_gin_tree suite. It checks that a leaf index stops at
the widest supported tree, that a leaf count implies the right number of
subtrees and vectors, that a leaf count enumerates every leaf in order,
that a vector maps to the right leaf, bit and subtree, and that a vector
beyond an 8-leaf tree is rejected there and accepted in a 16-leaf tree.
It also exercises the subtree set operations and checks that every
supported chipset implements the subtree that carries the GSP event.

Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
Acked-by: Danilo Krummrich <dakr@kernel.org>
Link: https://patch.msgid.link/20260930034148.590687-15-jhubbard@nvidia.com
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
This commit is contained in:
John Hubbard 2026-09-29 20:41:47 -07:00 committed by Alexandre Courbot
parent 476abf9f66
commit 306279ce97

View File

@ -129,7 +129,10 @@ pub(super) const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))]
#[cfg_attr(
not(any(CONFIG_NOVA_CORE_SELFTESTS, CONFIG_KUNIT = "y")),
expect(dead_code)
)]
pub(super) const fn into_raw(self) -> u32 {
self.0
}
@ -554,3 +557,126 @@ fn drop(&mut self) {
clear_top_enables(self.bar, self.serviced);
}
}
#[kunit_tests(nova_core_gin_tree)]
mod tests {
use super::*;
/// A leaf index cannot name a leaf beyond the widest supported tree.
#[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());
}
/// The subtree count, the implemented-subtree set, and the vector count follow the leaf count.
#[test]
fn leaf_count_derives_subtrees_and_vectors() {
assert_eq!(LeafCount::Eight.subtree_count(), 4);
assert_eq!(
Bounded::<u32, 32>::from(LeafCount::Eight.subtree_set()).get(),
0x0f
);
assert_eq!(LeafCount::Eight.vector_count(), 256);
assert_eq!(LeafCount::Sixteen.subtree_count(), 8);
assert_eq!(
Bounded::<u32, 32>::from(LeafCount::Sixteen.subtree_set()).get(),
0xff
);
assert_eq!(LeafCount::Sixteen.vector_count(), 512);
}
/// A tree enumerates every leaf that it implements, in order, and no more.
#[test]
fn leaf_count_iter_covers_the_tree() {
for (count, expected) in [(LeafCount::Eight, 8usize), (LeafCount::Sixteen, 16)] {
let mut seen = 0;
for (index, leaf) in count.iter().enumerate() {
assert_eq!(leaf.get(), index);
seen += 1;
}
assert_eq!(seen, expected);
}
}
/// A vector maps to its leaf, its bit within that leaf, and its subtree. The doorbell (129)
/// and the GSP event (155) share a subtree.
#[test]
fn vector_maps_to_leaf_bit_and_subtree() {
let doorbell = GinVector::new::<129>();
let gsp = GinVector::new::<155>();
assert_eq!(doorbell.leaf_index().get(), 4);
assert_eq!(doorbell.leaf_mask().into_raw(), 1 << 1);
assert_eq!(doorbell.subtree().index(), 2);
assert_eq!(gsp.leaf_index().get(), 4);
assert_eq!(gsp.leaf_mask().into_raw(), 1 << 27);
assert_eq!(gsp.subtree().index(), 2);
assert_eq!(doorbell.subtree(), gsp.subtree());
}
/// Both fixed vectors are within the 8-leaf tree, so every supported chipset implements them.
#[test]
fn fixed_vectors_fit_the_narrowest_tree() {
assert!(GinVector::new::<129>().validate(LeafCount::Eight).is_ok());
assert!(GinVector::new::<155>().validate(LeafCount::Eight).is_ok());
// The first vector beyond an 8-leaf tree.
assert!(GinVector::new::<256>().validate(LeafCount::Eight).is_err());
assert!(GinVector::new::<256>().validate(LeafCount::Sixteen).is_ok());
}
/// A subtree set reports membership, intersection, and its span from subtree 0.
#[test]
fn subtree_set_operations() {
let gsp = GinVector::new::<155>().subtree();
assert!(LeafCount::Eight.subtree_set().contains(gsp));
assert!(!LeafCount::Eight.subtree_set().is_empty());
// The GSP needs no subtree above 2, so an MSI-X request covers entries 0 through 2.
assert_eq!(SubtreeSet::from(gsp).span(), 3);
// A 16-leaf tree implements every subtree that an 8-leaf tree does.
assert_eq!(
LeafCount::Sixteen
.subtree_set()
.intersection(LeafCount::Eight.subtree_set()),
LeafCount::Eight.subtree_set()
);
}
/// Iterating a subtree set yields each subtree once, lowest index first, and nothing for an
/// empty set.
#[test]
fn subtree_set_iterates_its_members() {
assert!(LeafCount::Eight
.subtree_set()
.iter()
.map(Subtree::index)
.eq([0u32, 1, 2, 3]));
let gsp = SubtreeSet::from(GinVector::new::<155>().subtree());
assert!(gsp.iter().map(Subtree::index).eq([2u32]));
let empty = SubtreeSet::from(Bounded::<u32, 32>::new::<0>());
assert_eq!(empty.iter().count(), 0);
}
/// Every supported chipset implements the subtree that carries the GSP event.
#[test]
fn gsp_subtree_is_implemented_everywhere() {
for &chipset in Chipset::ALL {
assert!(cpu_interrupt_hal(chipset)
.leaf_count()
.subtree_set()
.contains(crate::irq::gsp::GSP_SUBTREE));
}
}
}