blob: 590f921894b6cc2b94216807393c096dc0b3368d [file]
// SPDX-License-Identifier: GPL-2.0
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//! The GIN CPU interrupt tree for one PCIe function.
//!
//! A [`GinVector`] names an interrupt source, a [`LeafIndex`] the leaf register that latches it,
//! a [`LeafMask`] a set of vectors within one leaf, and a [`Subtree`] one `TOP` bit. The types
//! keep the four from being confused with one another.
//!
//! Servicing a leaf requires reading its pending bits before clearing them. Only
//! [`Tree::read_pending`] produces a [`LeafPending`], and only a [`LeafPending`] clears a leaf,
//! so that the wrong order does not compile. This module does not serialize access to the tree.
//!
//! See `Documentation/gpu/nova/core/interrupts.rst`.
use kernel::{
device::Bound,
io::{
register::Array,
Io, //
},
irq,
num::Bounded,
pci::{
self,
IrqType, //
},
prelude::*, //
};
use crate::{
driver::Bar0,
gpu::Chipset,
num, //
};
use super::{
hal::{
cpu_interrupt_hal,
CpuInterruptHal, //
},
regs::*,
MsiType, //
};
/// Number of vectors one leaf register carries, one per bit.
const VECTORS_PER_LEAF: u32 = u32::BITS;
/// Number of leaves one subtree covers.
const LEAVES_PER_SUBTREE: u32 = 2;
/// Number of subtrees the widest supported tree implements.
const MAX_NUM_SUBTREES: u32 = 8;
/// Number of leaves the widest supported tree implements.
const MAX_NUM_LEAVES: u32 = MAX_NUM_SUBTREES * LEAVES_PER_SUBTREE;
/// Number of bits needed to address every vector in the widest supported tree.
const VECTOR_BITS: u32 = (MAX_NUM_LEAVES * VECTORS_PER_LEAF).ilog2();
/// Width of the vector field in the leaf trigger register.
const TRIGGER_VECTOR_BITS: u32 = {
let range = NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_TRIGGER::VECTOR_RANGE;
num::u8_as_u32(*range.end() - *range.start() + 1)
};
/// Index of a leaf register within the widest supported tree. An 8-leaf tree implements only the
/// lower half of the range.
pub(super) type LeafIndex = Bounded<usize, { MAX_NUM_LEAVES.ilog2() }>;
/// Number of leaves a tree implements.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u32)]
pub(super) enum LeafCount {
/// Turing through Ada.
Eight = 8,
/// Hopper and later.
Sixteen = 16,
}
impl LeafCount {
pub(super) const fn into_u32(self) -> u32 {
// CAST: `LeafCount` is `repr(u32)`, so the cast is lossless.
self as u32
}
pub(super) const fn into_raw(self) -> usize {
num::u32_as_usize(self.into_u32())
}
/// Returns the number of subtrees a tree of this size implements.
pub(super) const fn subtree_count(self) -> u32 {
self.into_u32() / LEAVES_PER_SUBTREE
}
/// Returns the set of every subtree a tree of this size implements.
pub(super) const fn subtree_set(self) -> SubtreeSet {
SubtreeSet((1u32 << self.subtree_count()) - 1)
}
/// Returns the number of vectors a tree of this size carries.
pub(super) const fn vector_count(self) -> u32 {
self.into_u32() * VECTORS_PER_LEAF
}
/// Returns every leaf a tree of this size implements.
pub(super) fn iter(self) -> impl Iterator<Item = LeafIndex> {
(0..self.into_raw()).filter_map(LeafIndex::try_new)
}
}
// `VECTOR_BITS` and `LeafCount::Sixteen` are written separately. This assert keeps them in
// agreement about the widest supported tree.
static_assert!(1 << VECTOR_BITS == LeafCount::Sixteen.vector_count());
/// Set of vectors within one leaf, one bit per vector.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct LeafMask(u32);
impl LeafMask {
/// Returns the mask with every vector set.
pub(super) const fn all() -> Self {
Self(u32::MAX)
}
#[cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))]
pub(super) const fn from_raw(raw: u32) -> Self {
Self(raw)
}
#[cfg_attr(
not(any(CONFIG_NOVA_CORE_SELFTESTS, CONFIG_KUNIT = "y")),
expect(dead_code)
)]
pub(super) const fn into_raw(self) -> u32 {
self.0
}
pub(super) const fn is_empty(self) -> bool {
self.0 == 0
}
/// Returns whether every vector in `other` is also in this mask.
pub(super) const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
}
impl From<Bounded<u32, 32>> for LeafMask {
fn from(vectors: Bounded<u32, 32>) -> Self {
Self(vectors.get())
}
}
impl From<LeafMask> for Bounded<u32, 32> {
fn from(vectors: LeafMask) -> Self {
vectors.0.into()
}
}
/// One subtree, held as the `TOP` bit that covers it.
///
/// # Invariants
///
/// Exactly one bit is set.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Subtree(u32);
impl Subtree {
/// Returns the subtree at index `idx`.
const fn new(idx: u32) -> Self {
// INVARIANT: shifting `1` left leaves exactly one bit set.
Self(1 << idx)
}
/// Returns this subtree's index within the tree.
pub(super) const fn index(self) -> u32 {
self.0.trailing_zeros()
}
pub(super) const fn into_raw(self) -> u32 {
self.0
}
}
/// Set of subtrees, one bit per subtree, in the layout of the `TOP` registers.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct SubtreeSet(u32);
impl SubtreeSet {
pub(super) const fn contains(self, subtree: Subtree) -> bool {
self.0 & subtree.into_raw() != 0
}
pub(super) const fn is_empty(self) -> bool {
self.0 == 0
}
pub(super) const fn intersection(self, other: Self) -> Self {
Self(self.0 & other.0)
}
/// Returns one more than the highest index in this set, or `0` for an empty set. An MSI-X
/// allocation that covers the set needs this many entries.
pub(super) const fn span(self) -> u32 {
u32::BITS - self.0.leading_zeros()
}
/// Returns the subtrees of this set, lowest index first.
pub(super) fn iter(self) -> impl Iterator<Item = Subtree> {
(0..u32::BITS)
.map(Subtree::new)
.filter(move |subtree| self.contains(*subtree))
}
}
impl From<Subtree> for SubtreeSet {
fn from(subtree: Subtree) -> Self {
Self(subtree.into_raw())
}
}
impl From<Bounded<u32, 32>> for SubtreeSet {
fn from(subtrees: Bounded<u32, 32>) -> Self {
Self(subtrees.get())
}
}
impl From<SubtreeSet> for Bounded<u32, 32> {
fn from(subtrees: SubtreeSet) -> Self {
subtrees.0.into()
}
}
/// A GIN interrupt vector, bounded to the widest tree that any supported chipset implements.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(super) struct GinVector(Bounded<u32, VECTOR_BITS>);
impl GinVector {
/// Returns vector number `VECTOR`.
///
/// Fails to compile if `VECTOR` is beyond the widest supported tree.
pub(super) const fn new<const VECTOR: u32>() -> Self {
Self(Bounded::<u32, VECTOR_BITS>::new::<VECTOR>())
}
#[cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))]
pub(super) const fn into_raw(self) -> u32 {
self.0.get()
}
/// Returns this vector's leaf.
pub(super) fn leaf_index(self) -> LeafIndex {
// CALC: `self.0 / VECTORS_PER_LEAF`.
self.0.shr::<{ VECTORS_PER_LEAF.ilog2() }, _>().cast()
}
/// Returns this vector's bit within its leaf.
pub(super) const fn leaf_mask(self) -> LeafMask {
LeafMask(1 << (self.0.get() % VECTORS_PER_LEAF))
}
/// Returns this vector's subtree.
pub(super) const fn subtree(self) -> Subtree {
Subtree::new(self.0.get() / (VECTORS_PER_LEAF * LEAVES_PER_SUBTREE))
}
/// Checks that a tree with `leaves` leaves implements this vector.
///
/// # Errors
///
/// `EINVAL` if it does not.
pub(super) const fn validate(self, leaves: LeafCount) -> Result {
if self.0.get() >= leaves.vector_count() {
Err(EINVAL)
} else {
Ok(())
}
}
}
impl From<GinVector> for Bounded<u32, TRIGGER_VECTOR_BITS> {
fn from(vector: GinVector) -> Self {
vector.0.extend()
}
}
/// Disables `vectors` in `leaf`.
fn clear_leaf_enables(bar: Bar0<'_>, leaf: LeafIndex, vectors: LeafMask) {
bar.write(
Array::at(*leaf),
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_EN_CLEAR::zeroed().with_vectors(vectors),
);
}
/// Disables the subtrees in `serviced` at `TOP`.
fn clear_top_enables(bar: Bar0<'_>, serviced: SubtreeSet) {
bar.write_reg(NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_TOP_EN_CLEAR::zeroed().with_subtrees(serviced));
}
/// The CPU tree of one PCIe function, the subtrees that nova-core services, and the PCI vectors
/// that deliver them.
///
/// A subtree may be enabled at `TOP` only once a handler is registered on the PCI vector that
/// delivers it. See "The serviced-subtree invariant" in
/// `Documentation/gpu/nova/core/interrupts.rst`.
pub(crate) struct Tree<'a> {
bar: Bar0<'a>,
hal: &'static dyn CpuInterruptHal,
serviced: SubtreeSet,
/// The interrupt type that Linux granted for `vectors`.
pub(super) msi_type: MsiType,
vectors: pci::IrqVectorRegistration<'a>,
}
impl<'a> Tree<'a> {
/// Creates the tree of `chipset` and allocates the PCI vectors for the subtrees in `serviced`.
///
/// Requests MSI-X entries `0` through the highest subtree in `serviced`, since an allocation
/// cannot be sparse, and falls back to a single MSI message for the whole tree.
///
/// # Errors
///
/// `EINVAL` if `serviced` is empty or names a subtree that `chipset` does not implement.
/// Otherwise, when neither interrupt type could be allocated, the error from the MSI request.
pub(crate) fn new(
pdev: &'a pci::Device<Bound>,
bar: Bar0<'a>,
chipset: Chipset,
serviced: SubtreeSet,
) -> Result<Self> {
let hal = cpu_interrupt_hal(chipset);
let num_leaves = hal.leaf_count();
if serviced.is_empty() || serviced.intersection(num_leaves.subtree_set()) != serviced {
return Err(EINVAL);
}
let entries = serviced.span();
let (vectors, msi_type) = pdev
.alloc_irq_vectors(entries, entries, IrqType::MsiX.into())
.map(|vectors| (vectors, MsiType::MsiX))
// Every subtree raises the one MSI message, so one MSI vector serves the whole tree.
// See "Delivery over PCI" in `Documentation/gpu/nova/core/interrupts.rst`.
.or_else(|_| {
pdev.alloc_irq_vectors(1, 1, IrqType::Msi.into())
.map(|vectors| (vectors, MsiType::Msi))
})?;
Ok(Self {
bar,
hal,
serviced,
msi_type,
vectors,
})
}
/// Returns the [`irq::IrqRequest`] for the PCI vector that delivers `subtree`.
///
/// # Errors
///
/// `EINVAL` if `subtree` is not one of the serviced subtrees.
pub(super) fn request_for(&self, subtree: Subtree) -> Result<irq::IrqRequest<'_>> {
if !self.serviced.contains(subtree) {
return Err(EINVAL);
}
let entry = match self.msi_type {
MsiType::MsiX => num::u32_as_usize(subtree.index()),
MsiType::Msi => 0,
};
self.vectors.index(entry).map(Into::into)
}
/// Rearms PCI interrupt delivery to the CPU after servicing `subtree`, the one subtree that
/// the calling handler serves.
///
/// A handler must call this before returning, or it receives no further interrupts.
pub(super) fn rearm_pci_irq(&self, subtree: Subtree) {
self.hal
.rearm_pci_irq(self.bar, self.msi_type, self.serviced, subtree);
}
/// Enables the serviced subtrees at `TOP`.
///
/// Each of them must have a handler registered on its PCI vector.
pub(super) fn enable_top(&self) {
self.bar.write_reg(
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_TOP_EN_SET::zeroed().with_subtrees(self.serviced),
);
}
/// Disables the serviced subtrees at `TOP`.
pub(super) fn disable_top(&self) {
clear_top_enables(self.bar, self.serviced);
}
/// Enables the serviced subtrees at `TOP` until the returned guard drops.
pub(crate) fn enable_top_guarded(&self) -> TopEnableGuard<'a> {
self.enable_top();
TopEnableGuard {
bar: self.bar,
serviced: self.serviced,
}
}
/// Enables `vectors` in `leaf`.
pub(super) fn enable_leaf(&self, leaf: LeafIndex, vectors: LeafMask) {
self.bar.write(
Array::at(*leaf),
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_EN_SET::zeroed().with_vectors(vectors),
);
}
/// Disables `vectors` in `leaf`.
pub(super) fn disable_leaf(&self, leaf: LeafIndex, vectors: LeafMask) {
clear_leaf_enables(self.bar, leaf, vectors);
}
/// Enables `vectors` in `leaf` until the returned guard drops.
pub(super) fn enable_leaf_guarded(
&self,
leaf: LeafIndex,
vectors: LeafMask,
) -> LeafEnableGuard<'a> {
self.enable_leaf(leaf, vectors);
LeafEnableGuard {
bar: self.bar,
leaf,
vectors,
}
}
/// Reads the pending bits of `leaf`, and returns the handle that clears them.
pub(super) fn read_pending(&self, leaf: LeafIndex) -> LeafPending<'a> {
let pending = self
.bar
.read(NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF::at(*leaf))
.vectors();
LeafPending {
bar: self.bar,
leaf,
pending,
}
}
/// Sets the pending bit of `vector` in its leaf, as if the vector's source had raised it.
///
/// # Errors
///
/// `EINVAL` if this tree does not implement `vector`.
#[cfg_attr(not(CONFIG_NOVA_CORE_SELFTESTS), expect(dead_code))]
pub(super) fn trigger(&self, vector: GinVector) -> Result {
vector.validate(self.hal.leaf_count())?;
self.bar.write_reg(
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF_TRIGGER::zeroed().with_vector(vector),
);
Ok(())
}
/// Disables every vector in every implemented leaf, including the subtrees that nova-core does
/// not service. Call this only during probe.
pub(super) fn disable_all_leaves(&self) {
for leaf in self.hal.leaf_count().iter() {
self.disable_leaf(leaf, LeafMask::all());
}
}
/// Clears every pending bit in every implemented leaf, including the subtrees that nova-core
/// does not service.
///
/// The serviced subtrees are disabled at `TOP` on return. Call this only during probe, with no
/// interrupt handler registered.
pub(super) fn drain(&self) {
self.disable_top();
// A vector that latched while disabled does not show in `TOP`, so read every leaf rather
// than descending from it.
for leaf in self.hal.leaf_count().iter() {
self.read_pending(leaf).clear();
}
}
/// Disables every vector, clears every pending bit, and rearms PCI interrupt delivery.
///
/// The serviced subtrees are disabled at `TOP` on return. Call this only during probe, with
/// no interrupt handler registered.
pub(super) fn reset(&self) {
self.disable_all_leaves();
self.drain();
for subtree in self.serviced.iter() {
self.rearm_pci_irq(subtree);
}
// A `TOP`-enable rearm leaves the subtrees that it cycles enabled, and a subtree may be
// enabled only once a handler is registered on its vector.
self.disable_top();
}
}
/// The pending bits of one leaf as they were read, and the handle that clears them.
pub(super) struct LeafPending<'a> {
bar: Bar0<'a>,
leaf: LeafIndex,
pending: LeafMask,
}
impl LeafPending<'_> {
pub(super) fn vectors(&self) -> LeafMask {
self.pending
}
/// Clears the vectors that were pending at the read. A vector that latched since stays pending.
pub(super) fn clear(&self) {
self.clear_vectors(self.pending);
}
/// Clears `vectors` and no other bit.
pub(super) fn clear_vectors(&self, vectors: LeafMask) {
if !vectors.is_empty() {
self.bar.write(
Array::at(*self.leaf),
NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_LEAF::zeroed().with_vectors(vectors),
);
}
}
}
/// Disables a set of vectors in one leaf when dropped.
pub(super) struct LeafEnableGuard<'a> {
bar: Bar0<'a>,
leaf: LeafIndex,
vectors: LeafMask,
}
impl Drop for LeafEnableGuard<'_> {
fn drop(&mut self) {
clear_leaf_enables(self.bar, self.leaf, self.vectors);
}
}
/// Disables the serviced subtrees at `TOP` when dropped.
pub(crate) struct TopEnableGuard<'a> {
bar: Bar0<'a>,
serviced: SubtreeSet,
}
impl Drop for TopEnableGuard<'_> {
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));
}
}
}