From: John Hubbard <jhubbard@nvidia.com>
To: Danilo Krummrich <dakr@kernel.org>,
Alexandre Courbot <acourbot@nvidia.com>
Cc: "Timur Tabi" <ttabi@nvidia.com>,
"Alistair Popple" <apopple@nvidia.com>,
"Eliot Courtney" <ecourtney@nvidia.com>,
"Zhi Wang" <zhiw@nvidia.com>, "David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Alex Gaynor" <alex.gaynor@gmail.com>,
"Boqun Feng" <boqun.feng@gmail.com>,
"Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
nova-gpu@lists.linux.dev, LKML <linux-kernel@vger.kernel.org>,
"John Hubbard" <jhubbard@nvidia.com>
Subject: [PATCH v4 17/17] gpu: nova-core: document the GIN interrupt controller and GSP events
Date: Fri, 11 Sep 2026 21:44:00 -0700 [thread overview]
Message-ID: <20260912044400.677097-18-jhubbard@nvidia.com> (raw)
In-Reply-To: <20260912044400.677097-1-jhubbard@nvidia.com>
The interrupt code rests on hardware behavior that the code cannot show
on its own: how GIN, the GPU's interrupt controller, records and
delivers interrupts, what edge-triggered delivery requires of a handler,
and how the GSP signals the CPU. Some of those requirements come from
Open RM rather than from the hardware manuals.
Add a design document that records that behavior, the rules nova-core
follows because of it, and the terms the code uses for it. The code
comments cite the document by section rather than repeating it.
Assisted-by: LLM
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
Documentation/gpu/nova/core/interrupts.rst | 674 +++++++++++++++++++++
Documentation/gpu/nova/index.rst | 1 +
2 files changed, 675 insertions(+)
create mode 100644 Documentation/gpu/nova/core/interrupts.rst
diff --git a/Documentation/gpu/nova/core/interrupts.rst b/Documentation/gpu/nova/core/interrupts.rst
new file mode 100644
index 000000000000..280dcf97688a
--- /dev/null
+++ b/Documentation/gpu/nova/core/interrupts.rst
@@ -0,0 +1,674 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+=============================================
+GPU interrupt handling: GIN and the GSP event
+=============================================
+
+This document describes how nova-core receives interrupts from the GPU on Turing
+and later parts. It covers the GPU Interrupt and Notification unit (GIN), which
+is the GPU's interrupt controller, and the GSP event, the interrupt that
+nova-core services in normal operation.
+
+Throughout, *CPU* means the CPU and the nova-core driver running on it. The GPU
+also has on-chip processors that run their own firmware and receive their own
+interrupts. The GSP (GPU System Processor) is one of them.
+
+Register names are the names from the GPU hardware reference headers. The
+pre-Hopper headers call the controller ``NV_CTRL`` and the Hopper-plus headers
+call it ``NV_GIN``. This document calls it GIN throughout, because the tree that
+nova-core services is the same on every supported part. "Register naming" at
+the end says how the names map onto the headers. Open RM, NVIDIA's open-source
+GPU kernel driver, is cited wherever nova-core follows it.
+
+Terminology
+===========
+
+The three levels of the controller, innermost first:
+
+leaf
+ One ``LEAF`` register. Each of its 32 bits is the pending bit of one
+ interrupt source. A Turing, Ampere, or Ada tree has 8 leaves. A Hopper or
+ Blackwell tree has 16.
+
+subtree
+ Two consecutive leaves, summarized by one bit of ``TOP``. A subtree is the
+ unit of enabling at ``TOP``, and under MSI-X it is the unit of delivery:
+ every interrupt from one subtree arrives on one MSI-X entry.
+
+tree
+ One ``TOP`` register and the leaves under it. Every PCIe function has its
+ own tree, and nova-core services the CPU tree of one function.
+
+The hardware headers, Open RM, and the Linux PCI API all use the word "vector",
+each for a different number. This document gives each one its own name, and a
+bare "vector" always means a GIN vector.
+
+GIN vector
+ The GPU-internal interrupt source number. It addresses one bit of one leaf.
+ A 16-leaf tree holds vectors 0 through 511, and an 8-leaf tree holds 0
+ through 255. The CPU doorbell is vector 129 and the GSP event is vector
+ 155.
+
+MSI-X entry
+ An index into the device's MSI-X table. One entry serves one subtree.
+
+PCI vector
+ One of the interrupts that ``pci_alloc_irq_vectors()`` allocates: an MSI-X
+ entry, or the single MSI message.
+
+Linux IRQ number
+ What ``request_irq()`` takes, obtained from ``pci_irq_vector()`` for a PCI
+ vector. Linux's ``struct msix_entry`` calls this number ``.vector`` as
+ well.
+
+The remaining terms, each named for the register or the specification that
+defines it:
+
+enable, disable a vector
+ Writes to ``LEAF_EN_SET`` and ``LEAF_EN_CLEAR``.
+
+enable, disable a subtree
+ Writes to ``TOP_EN_SET`` and ``TOP_EN_CLEAR``.
+
+serviced subtree
+ A subtree that nova-core enables and has a handler for.
+
+rearm
+ Restoring PCI interrupt delivery after servicing an interrupt. See
+ "Rearming PCI interrupt delivery".
+
+mask
+ Reserved for the two places where hardware and the PCI specification use
+ the word: the MSI-X per-entry Vector Control mask bit, which Linux
+ controls, and the falcon interrupt masks. It never names a GIN enable.
+
+latched, pending
+ Two names for one state, a set ``LEAF`` bit. The vector's source sets the
+ bit whether or not the vector is enabled.
+
+clear a vector
+ Write a 1 to the vector's bit in ``LEAF``. Open RM calls the same operation
+ ``intrClearLeafVector_HAL``.
+
+pending bits
+ The plain 32-bit value read from a ``LEAF`` register.
+
+notification
+ An interrupt whose only content is that something happened, such as a
+ posted message. Servicing a notification means reading what it announces.
+ The unit that raised it needs no attention. The GSP event is one.
+
+unit
+ Any block that raises an interrupt. "Engine" is reserved for the blocks
+ that do user work: GR, CE, NVDEC, and the like.
+
+falcon
+ One of the GPU's microcontrollers (see
+ Documentation/gpu/nova/core/falcon.rst). The GSP runs on the RISC-V core
+ inside its falcon. A falcon latches each of its interrupt causes and routes
+ it either to the host, meaning the CPU, or to its own core.
+
+The GIN controller
+==================
+
+A GPU has many interrupt sources: the GSP, the copy engines, the graphics
+engine, video decode and encode, the MMU fault path, timers, and others. GIN
+records which of them are pending and raises the PCI interrupt to the CPU.
+
+Trees
+-----
+
+GIN keeps one tree for each destination it can deliver an interrupt to. The CPU
+has one tree per PCIe function, so the physical function and each virtual
+function have their own. The GSP has a tree, and so do the other on-chip
+processors that receive interrupts. Every tree has the same two-level layout,
+and a function reaches its own tree through the per-function register aperture.
+
+nova-core services the CPU tree of one function. A virtual function's tree
+belongs to that function's driver, and a processor's tree belongs to the
+firmware running on that processor.
+
+The two-level tree
+------------------
+
+A tree is a set of ``LEAF`` registers and one ``TOP`` register.
+
+* ``LEAF(i)`` is a 32-bit register that holds the pending bits of vectors
+ ``32i`` through ``32i + 31``. A set bit is a pending vector.
+* ``TOP`` is a 32-bit read-only register. Bit ``N`` summarizes subtree ``N``,
+ which is ``LEAF(2N)`` and ``LEAF(2N + 1)``. The bit is set when an enabled
+ vector is pending in either leaf.
+
+A tree with L leaves has L / 2 subtrees and uses TOP bits 0 through L / 2 - 1.
+The other TOP bits read 0. The leaves and subtrees that a part has are its
+implemented leaves and subtrees, and "Per-architecture differences" gives the
+counts. For an 8-leaf tree::
+
+ TOP bit 0 -> subtree 0 -> LEAF(0), LEAF(1) vectors 0..63
+ TOP bit 1 -> subtree 1 -> LEAF(2), LEAF(3) vectors 64..127
+ TOP bit 2 -> subtree 2 -> LEAF(4), LEAF(5) vectors 128..191
+ TOP bit 3 -> subtree 3 -> LEAF(6), LEAF(7) vectors 192..255
+
+ LEAF(4), one bit per vector, holds vectors 128..159:
+
+ bit 1 = vector 129 (CPU doorbell)
+ bit 27 = vector 155 (GSP event)
+
+A vector's number fixes its place in the tree::
+
+ leaf = vector / 32
+ bit = vector % 32
+ subtree = leaf / 2
+
+Registers
+---------
+
+nova-core defines the tree's registers in the ``irq`` module's ``regs.rs``. The
+leaf registers are arrays indexed by leaf number.
+
+* ``LEAF(i)`` reads as the pending bits of leaf ``i``. Writing a 1 to a bit
+ clears that vector, and a 0 leaves the bit as it was.
+* ``LEAF_EN_SET(i)`` and ``LEAF_EN_CLEAR(i)`` enable and disable the vectors
+ of leaf ``i``, one bit per vector.
+* ``TOP_EN_SET`` and ``TOP_EN_CLEAR`` enable and disable subtrees, one bit per
+ subtree.
+* ``LEAF_TRIGGER`` takes a vector number and latches that vector, exactly as
+ the vector's own source would. It is write-only. The self-test uses it.
+
+nova-core does not read ``TOP``. "Servicing the tree" says why.
+
+Every set and clear register acts per bit: a 1 performs the action for that
+bit, and a 0 leaves the bit alone. No register needs a read-modify-write.
+
+GIN delivers a vector to the CPU only when its leaf enable bit and its
+subtree's TOP enable bit are both set. The enables do not affect the latch. The
+source of a disabled vector still sets its ``LEAF`` bit. ``TOP`` does not show
+that bit, so reading the leaf is the only way to see it.
+
+How a unit interrupt reaches the CPU
+------------------------------------
+
+A unit does not write a ``LEAF`` register. Each unit has an interrupt control
+register that GSP firmware programs. The control register holds the unit's
+vector, the GFID that identifies the PCIe function whose tree receives the
+interrupt, and one enable bit per destination: the CPU, the GSP, and the other
+on-chip processors. When the unit has an event::
+
+ 1. The unit sends GIN an interrupt message carrying the vector, the GFID,
+ and the destination enables from its control register.
+ 2. In the tree of each destination that the message selects, GIN sets bit
+ (vector % 32) of LEAF(vector / 32).
+ 3. If the vector and its subtree are enabled in the CPU tree, GIN raises
+ the PCI interrupt.
+
+Because firmware assigns the vectors, nova-core does not hardcode which vector
+belongs to which unit, with two exceptions. The hardware headers of every
+supported part define the GSP event as vector 155, and GSP firmware's own
+interrupt table uses that definition. The CPU doorbell is vector 129. Pre-Hopper
+hardware fixes that number, and GSP firmware keeps it on Hopper and later.
+nova-core names both by number. A driver can fetch the full unit-to-vector
+table from the GSP by RPC, and nouveau does. nova-core does not, because a
+fixed vector needs no lookup.
+
+Edge-triggered delivery
+-----------------------
+
+A ``LEAF`` bit is a latch. Its source sets it on a rising edge, and it stays set
+until the CPU clears it. A source that stays high does not set the bit again.
+
+GIN raises the PCI interrupt for a subtree when the subtree's enabled pending
+state goes from low to high::
+
+ Per vector, in leaf i at bit b:
+ LEAF(i)[b] AND LEAF_EN(i)[b]
+
+ Per subtree N, across leaves 2N and 2N + 1:
+ OR of every enabled pending bit -> TOP[N]
+
+ Delivery for subtree N:
+ TOP[N] AND TOP_EN[N] -> rising edge -> PCI interrupt
+
+``TOP_EN`` applies after the summary, so disabling a subtree stops delivery
+without changing what ``TOP`` reports.
+
+Three consequences:
+
+* Code that must find every pending vector reads the leaves. A vector that
+ latched while disabled is not in ``TOP``.
+* Writing ``TOP_EN_SET`` for a subtree with an enabled pending bit produces a
+ new edge. GIN delivers an interrupt for a pending bit left uncleared as soon
+ as its subtree is enabled again.
+* A source that holds its signal high produces no new edge after the CPU
+ clears the leaf bit. Such a source has to re-emit its interrupt. The falcons
+ do that through ``INTR_RETRIGGER`` (see "Retriggering a falcon").
+
+Delivery over PCI
+=================
+
+GIN delivers the tree's interrupts to the CPU as MSI or MSI-X, whichever Linux
+grants. nova-core requests MSI-X first and falls back to MSI, and never uses
+INTx.
+
+MSI has a single message, and every subtree raises that one message, so one
+Linux IRQ serves the whole tree.
+
+MSI-X gives each subtree its own table entry, at the index equal to the subtree
+number. Linux masks every entry until a driver requests its Linux IRQ number,
+and a masked entry sends no message: the GPU records the interrupt in the MSI-X
+pending bit array, where it stays until Linux unmasks the entry. An entry that
+the driver never requests is never unmasked. A driver that enables a subtree
+without requesting that subtree's entry loses every interrupt from that
+subtree, with nothing reported: the leaf and TOP registers show the vector
+pending and enabled while no handler runs.
+
+The serviced-subtree invariant
+------------------------------
+
+Every subtree enabled at ``TOP`` has an allocated PCI vector with a registered
+handler.
+
+MSI satisfies this with its single message. MSI-X needs one allocated entry per
+serviced subtree, and a PCI allocation cannot be sparse, so nova-core requests
+entries 0 through the highest serviced subtree::
+
+ MSI-X, with subtree 2 serviced:
+
+ subtree 0 -> entry 0 allocated, no handler, stays masked
+ subtree 1 -> entry 1 allocated, no handler, stays masked
+ subtree 2 -> entry 2 handler here, and its rearm covers subtree 2
+
+ MSI, with any serviced set:
+
+ every serviced subtree -> the one allocated PCI vector, whose
+ handler's rearm covers the whole serviced set
+
+An allocated entry whose subtree nova-core does not service costs nothing. The
+entry stays masked, and a disabled subtree raises no interrupt.
+
+nova-core services one subtree. The GSP event, vector 155, is in leaf 4, which
+is in subtree 2. Open RM's headers place its UVM_SHARED interrupt category in
+subtree 2 on every part nova-core supports. The self-test doorbell, vector 129,
+is in the same leaf, and the test allocates its own vectors for it (see
+"Self-test").
+
+Rearming PCI interrupt delivery
+-------------------------------
+
+A message-signaled interrupt is delivered once per edge, and the PCI side
+delivers no further interrupt until the CPU rearms it. The rearm operation
+depends on the GPU family and on the interrupt type Linux granted:
+
+================== ===== ===========================================
+Architecture Type Rearm operation
+================== ===== ===========================================
+Turing through Ada MSI write the configuration-mirror EOI register
+Hopper and later MSI clear then set the serviced TOP enables
+Any MSI-X clear then set the handler's own TOP enable
+================== ===== ===========================================
+
+The end-of-interrupt register is ``NV_XVE_CYA_2`` in the BAR0 mirror of PCI
+configuration space, and the value written does not matter. The ``TOP_EN``
+cycle produces a new delivery edge. The MSI forms cover every serviced subtree,
+because one message serves all of them. The MSI-X form covers one subtree,
+because each serviced subtree has its own entry and its own handler.
+
+A handler rearms once per delivered interrupt, on every path, including the
+path where it finds its vector not pending. A handler that skips the rearm
+receives no further interrupts.
+
+Open RM makes the same split. It writes the configuration-space EOI for MSI on
+pre-Hopper parts, and cycles the TOP enables of the subtrees it services for
+Hopper-plus MSI and for MSI-X.
+
+Servicing the tree
+==================
+
+Servicing a leaf has a required order: read its pending bits, then clear them.
+Clearing a leaf before reading it discards every vector latched in it, and
+nothing reports the loss. In nova-core, reading a leaf produces the handle that
+clears it, so the wrong order does not compile. The handle clears exactly the
+bits it read, so a vector that latched after the read stays pending.
+
+A handler clears its bit before it services the vector. Clearing afterwards
+would discard an interrupt that the source raised while the handler ran.
+
+nova-core services the tree in two ways.
+
+The notification path services one vector. It reads the vector's leaf, clears
+only the vector's bit, and rearms. The subtree stays enabled, and a vector
+pending beside it in the same leaf keeps its bit set for the code that services
+that vector. The GSP event handler takes this path, and so does the self-test
+handler.
+
+The startup drain walks the whole tree, because it must clear whatever is
+pending across every subtree rather than one known vector. It disables the
+serviced subtrees at ``TOP``, reads and clears every implemented leaf, and
+leaves the subtrees disabled for its caller to enable once the caller is ready
+for deliveries. The drain reads every leaf rather than descending from ``TOP``,
+because sources latch vectors during boot while those vectors are disabled, and
+``TOP`` does not show them. Open RM's stall-interrupt path reads every leaf for
+the same reason.
+
+The two paths as register operations::
+
+ Startup drain, run once during probe:
+ write TOP_EN_CLEAR = serviced stop new deliveries
+ for each implemented leaf i:
+ pending = read LEAF(i)
+ write LEAF(i) = pending clear what was read
+ (returns with TOP_EN still clear)
+
+ Notification, the subtree stays enabled:
+ pending = read LEAF(leaf) is the handler's bit set?
+ write LEAF(leaf) = bit clear that one bit
+ rearm PCI interrupt delivery
+
+The drain clears every pending bit, including bits that nova-core never
+services. An uncleared bit holds its subtree in the pending state, and enabling
+that subtree again would deliver an interrupt for a vector that no handler
+services.
+
+The drain's ``TOP_EN_CLEAR`` is not a rearm, and pre-Hopper MSI rearms through
+the configuration mirror, which the drain never writes. An interrupt delivered
+before probe had no handler to rearm it, so the startup sequence rearms
+explicitly after the drain.
+
+Nothing in nova-core serializes access to the tree. The GSP event handler
+touches only its own leaf, and the drain runs during probe, before that handler
+is registered.
+
+Per-architecture differences
+============================
+
+The tree is the same on every supported GPU except for its size, which changes
+at Hopper:
+
+=================== ====== ======== ====================
+GPUs Leaves Subtrees Implemented subtrees
+=================== ====== ======== ====================
+Turing, Ampere, Ada 8 4 ``0x0f``
+Hopper, Blackwell 16 8 ``0xff``
+=================== ====== ======== ====================
+
+The interrupt HAL provides the leaf count, and the subtree count and the
+implemented-subtree set derive from it. A subtree that the part does not
+implement has no TOP bit, so building a tree that services one fails with
+``EINVAL``. Vectors 129 and 155 are in the 8-leaf tree, so every supported part
+has them.
+
+Open RM's headers assign every interrupt category of a 16-leaf tree to leaves 0
+through 11. The drain reads all 16, because a vector can be latched in any
+implemented leaf.
+
+The HAL's other value is the rearm method (see "Rearming PCI interrupt
+delivery"). Two falcon properties also differ by family and have a HAL of their
+own. Turing falcons have no ``INTR_RETRIGGER``, and the RISC-V routing
+registers moved at GA102 (see "Retriggering a falcon").
+
+The GSP event
+=============
+
+When the GSP has output for the CPU, it writes messages into the GSP-to-CPU
+queue in shared memory and raises SWGEN0, one of the software-generated
+interrupt causes of the GSP falcon. SWGEN0 is routed to the host, at vector
+155, leaf 4 bit 27, in subtree 2.
+
+The queue carries notifications (log records, error records, lifecycle events)
+and command replies. A thread waiting for a reply reads the queue itself, so
+the interrupt is only the trigger to drain the queue (see "Draining the
+GSP-to-CPU queue").
+
+The falcon latches every cause it raises, SWGEN0 among them, in its
+``IRQSTAT`` register. The handler services the host-routed causes and clears
+their latches, and then it writes ``INTR_RETRIGGER`` so that the falcon
+re-emits any cause that latched in the meantime. "Retriggering a falcon" has
+the details.
+
+Draining the queue takes the command-queue mutex and walks shared memory, so it
+cannot run in hard interrupt context. nova-core registers a threaded handler,
+under the name ``nova-core`` in ``/proc/interrupts``. The top half runs in hard
+interrupt context and reads and writes only registers, and it wakes the IRQ
+thread to drain the queue::
+
+ GSP writes messages into the GSP-to-CPU queue
+ GSP raises SWGEN0
+ GIN sets bit 27 of LEAF(4), and subtree 2 becomes pending
+ PCI interrupt -> Linux IRQ -> top half, in hard interrupt context:
+ read LEAF(4), and if bit 27 is clear, rearm and return
+ clear bit 27 (the subtree stays enabled)
+ read the falcon causes routed to the host, clearing SWGEN0 if set
+ for every other host cause: log it, clear its latch, and read the
+ host causes back
+ if the clear ended all of them: retrigger the falcon
+ otherwise: disable vector 155 at its leaf and skip the retrigger
+ rearm PCI interrupt delivery
+ wake the IRQ thread if SWGEN0 was set
+ IRQ thread, which may sleep:
+ take the command-queue mutex and drain the GSP-to-CPU queue
+
+A halt and a posted message can be pending together, so the top half services
+every cause that the status reports.
+
+A drain fails when a message's framing or checksum is bad, which poisons the
+queue (see "Draining the GSP-to-CPU queue"). Every later event would fail the
+same way, so the IRQ thread disables vector 155 and logs the failure, which
+leaves the queue unserviced until the device is reset.
+
+The handler, the self-test, and the rest of the driver read BAR0 through one
+shared mapping. nova-core unregisters an interrupt handler when the device
+unbinds, so a handler runs only while the mapping exists.
+
+Retriggering a falcon
+---------------------
+
+A falcon signals the tree when its set of host-routed causes goes from empty to
+non-empty. A cause left latched keeps the set non-empty, so no later cause
+signals the tree, and the vector is lost. For a cause that stays latched, the
+handler can clear the tree leaf first or the falcon latch first, and the loss
+is the same.
+
+``IRQSTAT`` latches every cause in the falcon, including the causes routed to
+the falcon's own RISC-V core and owned by the firmware running on it. A host
+handler owns only the causes that ``PRISCV_RISCV_IRQMASK`` and
+``PRISCV_RISCV_IRQDEST`` both select, so it intersects ``IRQSTAT`` with both
+before it reads or clears a cause. Open RM computes the same intersection in
+``kflcnRiscvReadIntrStatus``. GA100 keeps the Turing offsets of the two routing
+registers and GA102 moves them, so the offsets change at GA102 rather than at
+the Ampere boundary. The handler masks no cause: ``PRISCV_RISCV_IRQMASK`` is
+read-only to the host, and ``FALCON_IRQMASK`` has no effect on host routing on
+a RISC-V falcon.
+
+``INTR_RETRIGGER`` makes the falcon re-emit its host-routed causes into the
+tree, which supplies the transition that clearing the leaf lost. The handler
+writes ``INTR_RETRIGGER`` only on a path where it ended every cause that it
+read, because a re-emitted cause that nothing clears arrives again at once and
+on every pass after that.
+
+``IRQSCLR`` ends a latch and does not end the source behind it, so a cause
+driven from outside the falcon stays set after the write. On Blackwell the
+fault-containment and ECC causes are driven that way: they appear in
+``IRQSTAT`` but come from ``PRISCV_RISCV_FAULT_CONTAINMENT_SRCSTAT`` and
+``PGSP_ECC_INTR_STATUS``, and only a device reset ends them. So the handler
+clears the latch of every host cause other than SWGEN0, reads the host causes
+back, and retriggers only when the read-back is empty. When a cause is still
+set, the handler disables vector 155 instead and reports that the device needs
+a reset. Disabling loses no notification: the cause that is still set holds the
+host-routed set non-empty, so the falcon would signal nothing further either
+way. Open RM makes the same choice, and ``kgspService_TU102`` skips
+``kflcnIntrRetrigger`` once it has recorded a fatal error.
+
+A fault cause that arrives after the clear cannot be told apart from one that
+the clear failed to end, so the handler disables the vector in that case too.
+Both mean the GSP has faulted.
+
+Turing falcons have no ``INTR_RETRIGGER``, so a Turing handler cannot re-create
+a transition it has lost. It must leave no host cause latched: it reads the
+host-routed status once and takes every cause that the status reports, rather
+than stopping at the first one it recognizes. One window stays open. A cause
+that arrives after the handler has read the status is not in the value the
+handler clears, so it stays latched after the leaf has been cleared, and no
+later cause from that falcon signals the tree. Open RM has the same window on
+Turing, where ``kflcnIntrRetrigger`` does nothing.
+
+Enabling the GSP event
+----------------------
+
+SWGEN0 is a latch, and the GSP drives no new edge into the tree while it stays
+set. nova-core's GSP boot code consumes the GSP's notifications by polling the
+queue, which leaves the latch set and leaves pending bits in the tree. The
+handoff from polling to interrupts has a required order::
+
+ disable every implemented vector drop enables left by boot, or by a
+ driver that ran before this one
+ drain the tree clear stale pending bits
+ rearm PCI interrupt delivery required under pre-Hopper MSI, where
+ nothing else does it
+ clear the SWGEN0 latch so the next message makes an edge
+ register the threaded handler nothing can reach it yet
+ enable subtree 2 at TOP the drain left it disabled
+ enable vector 155 at LEAF(4) deliveries become possible here
+ drain the GSP-to-CPU queue messages posted before the clear
+
+nova-core quiesces the tree before it registers the handler. Registering
+unmasks the PCI interrupt, and GIN would then deliver a vector that boot left
+enabled to a handler that services one vector and has no way to service any
+other. Open RM clears every leaf enable at the same point for the same reason.
+
+nova-core clears the latch after the drain. If nova-core cleared the latch
+first, a message posted before the drain could set it again, along with bit 27
+of LEAF(4). The drain would then clear the leaf bit while the latch stays set,
+and no later message would signal the tree. Clearing after the drain can
+instead leave the leaf bit pending with the latch already clear. Enabling the
+vector then delivers one interrupt whose ``IRQSTAT`` reads zero. The top half
+clears the leaf bit, rearms, and does not wake the IRQ thread, and the queue
+drain that follows reads the message.
+
+Clearing the latch makes the first interrupt possible. A message that the GSP
+posted before that clear produces no interrupt, so the sequence ends by
+draining the queue.
+
+The subtree is enabled at ``TOP`` once the handler is registered. The drain
+left it disabled, and under pre-Hopper MSI the rearm is a configuration-space
+write that does not enable it again, so the enable is explicit. On teardown
+nova-core disables the vector at its leaf, so that nothing in the subtree can
+be delivered, then calls ``free_irq()``, and disables the subtree last.
+Disabling the subtree earlier would let a handler still in flight enable it
+again through the ``TOP_EN`` cycle of its rearm, which would leave the subtree
+enabled with no handler registered. nova-core tears down the registration
+before it frees the queue that the handler drains and before it unloads the
+GSP.
+
+Draining the GSP-to-CPU queue
+-----------------------------
+
+The queue carries command replies and unsolicited events, and a message's
+function code says which it is.
+
+* A function code that matches the awaited reply: the message is decoded and
+ returned to the caller that sent the command.
+* Anything else is an event. An OS error record and a robust-channel record
+ are logged at error level, and an unrecognized function code at warning
+ level. The other known events (GSP logs, libos prints, assertion records,
+ lifecycle notices) need no action and get no line of their own, because the
+ receive trace at debug level already records every message's arrival with
+ its sequence number, function code, and length.
+
+The sequence number takes no part in the match, because the GSP does not echo
+the command's sequence number on every reply. On r570 the reply to
+``UnloadingGuestDriver`` carries sequence 0.
+
+The read pointer advances past every message, whether it matched, was an event,
+or matched but failed to decode, so a message is never left at the queue head
+for the next receive to parse again.
+
+Corrupt framing is the exception. A message's length is inside the region that
+the checksum covers, so once the framing or the checksum fails there is no
+trustworthy length with which to skip the message. Such a failure poisons the
+queue: nova-core logs it once, and every later receive fails with ``EIO`` until
+the device is reset.
+
+The polling path and the IRQ thread both read the queue under the command-queue
+mutex. Replies and events share one queue and one read pointer, so one lock is
+held across the whole drain. A thread waiting for a reply logs each event that
+arrives before the reply and keeps waiting. One deadline of 5 seconds applies
+to the whole wait, rather than a fresh timeout after each message, and the
+thread holds the mutex for the whole wait, so no other caller consumes the
+message it waits for.
+
+With one lock, a drain waits for an in-flight command's receive to finish or
+time out. For log and error records that delay does not matter.
+
+Self-test
+=========
+
+The self-test confirms that an interrupt injected at the GPU is delivered to a
+registered handler. It runs during probe, after the GPU's boot firmware, GFW,
+has completed, and before nova-core boots the GSP, because the test disables
+and drains the whole tree, including the GSP's leaf. The test is built only
+under ``CONFIG_NOVA_CORE_SELFTESTS``, like the other probe-time hardware tests.
+Those other tests log a failure and let probe continue. A failed delivery test
+fails probe, because an interrupt path that does not work leaves the driver
+unable to make progress later, in a place that says nothing about the cause.
+
+The test writes ``LEAF_TRIGGER`` with vector 129, the CPU doorbell, at leaf 4
+bit 1. The vector then takes the ordinary path to the CPU under the ordinary
+enables. The doorbell keeps the same number on every supported part, so the
+test names it without asking the GSP, which is not running yet.
+
+The test allocates PCI vectors for subtree 2, disables every vector, drains the
+tree, and checks that the doorbell bit starts out clear. It registers a
+non-threaded handler with a completion, under the name ``nova-core-selftest``
+in ``/proc/interrupts``, and enables the vector and the subtree. It triggers
+the doorbell, waits up to 1000 ms for the first delivery, triggers it again,
+and waits up to 1000 ms for the second. The handler takes the notification
+path: it clears only its own bit and rearms, and never walks the tree.
+
+The test triggers twice, and the second trigger waits for the first handler to
+finish. One delivery would prove nothing about the rearm, because the first
+message-signaled interrupt arrives whether the driver rearms or not, and two
+triggers in a row could coalesce into one delivery. A handler that walked the
+tree would prove nothing either: on every configuration except pre-Hopper MSI
+the rearm is a ``TOP_EN`` cycle, so a walk that enabled ``TOP`` again would
+rearm delivery whether the handler asked for it or not.
+
+The test passes only if both deliveries arrive, each finds the doorbell bit and
+nothing else pending in leaf 4, and the bit is clear once the vector is
+disabled. Anything else fails probe. Requiring the exact pending bits on the
+second delivery shows that the first handler's clear reached the hardware. No
+other vector in leaf 4 can be pending, because the test disabled every vector
+and runs before GSP boot.
+
+The test releases its PCI vectors and its handler before returning, so the
+driver's own allocation covers the GSP subtree and nothing else. Sharing the
+driver's allocation would have let the test pass only because the doorbell and
+the GSP event happen to be in the same subtree.
+
+The test exercises the path from the GPU to the handler without GSP firmware,
+which helps when bringing up PCI, MSI, MSI-X, and passthrough setups. Under
+MSI-X a pass also shows that the delivery arrived on the entry belonging to the
+serviced subtree.
+
+The parts with no hardware dependency have KUnit tests instead: the vector
+arithmetic, the leaf and subtree sets, the per-architecture leaf count and
+rearm method, and the falcon retrigger and routing HAL.
+
+Register naming
+===============
+
+nova-core uses the ``NV_VIRTUAL_FUNCTION_PRIV_CPU_INTR_*`` names for the CPU
+tree on every supported part. That is the per-function aperture: each PCIe
+function reaches its own tree through it, at the same offsets. The controller
+also has a central aperture that exposes every function's tree. The pre-Hopper
+headers name it ``NV_CTRL_CPU_INTR_*`` and the Hopper-plus headers
+``NV_GIN_CPU_INTR_*``. nova-core does not use it. Open RM's kernel-side code
+for the controller is the ``Intr`` object.
+
+Scope
+=====
+
+nova-core services the CPU tree of one function and nothing else. It implements
+no virtual-function tree management and no GFID routing, which belong to the
+physical function's driver or to firmware in a virtualized setup, and no MIG
+(multi-instance GPU) support. It services none of the subtrees that the
+hardware headers reserve for the stall interrupts of the host-driven engines.
diff --git a/Documentation/gpu/nova/index.rst b/Documentation/gpu/nova/index.rst
index 59b206238498..224caef9ea42 100644
--- a/Documentation/gpu/nova/index.rst
+++ b/Documentation/gpu/nova/index.rst
@@ -35,3 +35,4 @@ vGPU manager VFIO driver and the nova-drm driver.
core/falcon
core/tlv
core/pramin
+ core/interrupts
--
2.55.0
prev parent reply other threads:[~2026-09-12 4:44 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-12 4:43 [PATCH v4 00/17] nova-core: GPU interrupt support and GSP event delivery John Hubbard
2026-09-12 4:43 ` [PATCH v4 01/17] rust: pci: declare IrqType and IrqTypes with impl_flags John Hubbard
2026-09-12 4:43 ` [PATCH v4 02/17] rust: sync: completion: add wait_for_completion_timeout() John Hubbard
2026-09-12 4:43 ` [PATCH v4 03/17] gpu: nova-core: add the GIN vector, leaf and subtree types John Hubbard
2026-09-12 4:43 ` [PATCH v4 04/17] gpu: nova-core: add the GIN CPU interrupt tree and MSI EOI registers John Hubbard
2026-09-12 4:43 ` [PATCH v4 05/17] gpu: nova-core: add the per-architecture GIN CPU interrupt HAL John Hubbard
2026-09-12 4:43 ` [PATCH v4 06/17] gpu: nova-core: add the GIN interrupt tree and allocate its vectors John Hubbard
2026-09-12 4:43 ` [PATCH v4 07/17] gpu: nova-core: wait for GFW boot in probe, not in the Gpu constructor John Hubbard
2026-09-12 4:43 ` [PATCH v4 08/17] gpu: nova-core: add an interrupt delivery self-test John Hubbard
2026-09-12 4:43 ` [PATCH v4 09/17] gpu: nova-core: log GSP events instead of discarding them John Hubbard
2026-09-12 4:43 ` [PATCH v4 10/17] gpu: nova-core: stop re-parsing a bad GSP message John Hubbard
2026-09-12 4:43 ` [PATCH v4 11/17] gpu: nova-core: return ENOMSG for an unmatched " John Hubbard
2026-09-12 4:43 ` [PATCH v4 12/17] gpu: nova-core: bound a GSP wait by a single deadline John Hubbard
2026-09-12 4:43 ` [PATCH v4 13/17] gpu: nova-core: add a GSP message queue drain John Hubbard
2026-09-12 4:43 ` [PATCH v4 14/17] gpu: nova-core: add the falcon interrupt registers and their HAL John Hubbard
2026-09-12 4:43 ` [PATCH v4 15/17] gpu: nova-core: service GSP events from the SWGEN0 interrupt John Hubbard
2026-09-12 4:43 ` [PATCH v4 16/17] gpu: nova-core: add KUnit tests for the interrupt tree and HALs John Hubbard
2026-09-12 4:44 ` John Hubbard [this message]
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=20260912044400.677097-18-jhubbard@nvidia.com \
--to=jhubbard@nvidia.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=apopple@nvidia.com \
--cc=bhelgaas@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=dakr@kernel.org \
--cc=ecourtney@nvidia.com \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=simona@ffwll.ch \
--cc=tmgross@umich.edu \
--cc=ttabi@nvidia.com \
--cc=zhiw@nvidia.com \
/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
all inboxes | Powered by JetHome®