Skip to contentSkip to conversation

Introduce NVIDIA vGPU manager

PATCH In review Alok Kumar 2 participants 36 messages Nov 6 – Nov 11

Subsystems (9)
lore
Share & tools
lore

Alok Kumar

author
#0 lore raw

v4 cover Introduce NVIDIA vGPU manager

Add the nova-core support needed to create and manage NVIDIA vGPU instances on the current whole-GPU SR-IOV path.

The overall control path is shown below. This posting contains the nova-core manager and PF-side lifecycle APIs. Typed PF-service publication and the VFIO variant driver follow separately.

 +-----------------------+         +-----------------------+
 | Linux guest           |         | Windows guest         |
 | NVIDIA guest driver   |         | NVIDIA guest driver   |
 +-----------+-----------+         +-----------+-----------+
             |                                 |
 +-----------v-----------+         +-----------v-----------+
 | QEMU (VFIO user)      |         | QEMU (VFIO user)      |
 +-----------+-----------+         +-----------+-----------+
             +----------------+----------------+
                              |
                      +-------v-------+
                      |   VFIO core   |
                      +-------+-------+
                              |
        +---------------------v---------------------+
        | NVIDIA vGPU VFIO variant driver           |
        +---------------------+---------------------+
                              |
              +---------------+----------------------+
              | binds PCI VF                         | PF lifecycle API
              |                                      | open / close / reset
              v                                      v
     +--------+--------+               +-------------+-------------+
     |    PCI VF(s)    |               | nova-core (PF driver)     |
     |  (virtual GPU)  |               |       vGPU Manager        |
     +--------+--------+               |     VRAM / channels ...   |
              |                        +-------------+-------------+
              |                                      |
+-------------v--------------------------------------v----------------+
|                      NVIDIA physical GPU                            |
|            VF resources                 PF / GSP / VRAM             |
+---------------------------------------------------------------------+

The manager queries the type assigned to a VF by GSP-RM, reserves its channel and VRAM resources, boots and configures the GSP plugin, and handles shutdown. It also scrubs guest VRAM before reuse and exposes per-instance plugin logs through debugfs.

Host resources remain owned until firmware teardown and BAR unmapping complete. Failed creation rolls back through the instance owner; if firmware ownership is uncertain or teardown fails, the manager retains the affected resources until device removal.

This continues the rework of the earlier vGPU RFC [2]: firmware-facing management belongs to nova-core, while the VFIO driver binds to the VF and uses the PF's services. The companion VFIO driver does not need its own copies of the RM firmware headers.

The patches are organized as follows:

  1     Retain BAR1 reservations after an unmap failure.
  2-8   Boot state, runtime parameters and the 48-VM WPR2 heap.
  9-12  VRAM allocation, region views and BAR mappings.
 13-14  Synchronous GMC transactions and completion events.
 15-19  Plugin bindings, property queries and instance allocation.
 20-28  Plugin boot, shutdown and RPC.
 29-31  CeUtils scrubbing and plugin logs.
 32     SR-IOV PF lifecycle APIs.

Changes since v3:
* Include the BAR1 unmap-failure fix as patch 1.
* Rebase onto r000 firmware stack and drop changes already supplied by the prerequisites.
* Adapt boot-state/VF information handling, firmware encoding and GMC transactions to the interfaces supplied by that newer stack.

Dependencies:
This series is based on drm-rust-next at 41871f4ddf710339863f957bcfcab5094c234771 plus the r000 GSP firmware and other integration prerequisites.

The complete integration tree, including the prerequisites and the companion VFIO driver, is available at:

    https://github.com/alokk-nvidia/nova-core/tree/nova-vgpu-wip-20261105

Previous versions:
v3: [1]
v2: https://lore.kernel.org/nova-gpu/[email protected]/
v1: https://lore.kernel.org/nova-gpu/[email protected]/

[1] https://lore.kernel.org/nova-gpu/[email protected]/
[2] https://lore.kernel.org/kvm/[email protected]/

Alok Kumar (2):
gpu: nova-core: gsp: decode the FIFO engine table
gpu: nova-core: vgpu: add VRAM slot allocator

Zhi Wang (30):
gpu: nova-core: mm: retain BAR1 reservations after unmap failure
gpu: nova-core: gsp: pass boot context through setup helpers
gpu: nova-core: gsp: decouple boot context from VgpuManager
gpu: nova-core: vgpu: detect boot state independently
gpu: nova-core: gpu: add a channel ID pool for vGPU
gpu: nova-core: vgpu: initialize runtime parameters after GSP boot
gpu: nova-core: vgpu: reserve the 48-VM WPR2 heap
gpu: nova-core: mm: borrow BarUser for temporary BAR1 access
gpu: nova-core: mm: add VramBlock
gpu: nova-core: mm: add VramRegion
gpu: nova-core: mm: add BarMapping
gpu: nova-core: gsp: add synchronous GMC transactions
gpu: nova-core: gsp: wait for GMC completion events
gpu: nova-core: vgpu: add r000 plugin bindings
gpu: nova-core: gsp: factor out NVKV payload conversion
gpu: nova-core: vgpu: query VF assignments and properties
gpu: nova-core: vgpu: add instance create/destroy
gpu: nova-core: vgpu: encode vGPU boot requests
gpu: nova-core: vgpu: add GSP plugin communication buffers
gpu: nova-core: vgpu: add instance boot
gpu: nova-core: vgpu: add instance shutdown
gpu: nova-core: vgpu: initialize GSP plugin RPC buffers
gpu: nova-core: vgpu: add GSP plugin RPC transactions
gpu: nova-core: vgpu: negotiate the GSP plugin RPC version
gpu: nova-core: vgpu: send GSP plugin configuration parameters
gpu: nova-core: vgpu: update the GSP plugin BME state
gpu: nova-core: vgpu: add CeUtils commands
gpu: nova-core: vgpu: scrub guest VRAM with CeUtils
gpu: nova-core: vgpu: export plugin log buffers via debugfs
gpu: nova-core: vgpu: introduce SR-IOV PF APIs

 Documentation/gpu/nova/core/interrupts.rst    |  20 +-
 drivers/gpu/nova-core/fb.rs                   |  28 +-
 drivers/gpu/nova-core/gpu.rs                  | 111 ++-
 drivers/gpu/nova-core/gpu/channel.rs          |   4 +
 drivers/gpu/nova-core/gsp.rs                  |  30 +-
 drivers/gpu/nova-core/gsp/boot.rs             |   8 +-
 drivers/gpu/nova-core/gsp/cmdq.rs             |  28 +-
 drivers/gpu/nova-core/gsp/cmdq/gmc.rs         | 223 +++++-
 drivers/gpu/nova-core/gsp/commands.rs         |  11 +
 drivers/gpu/nova-core/gsp/commands/gmc.rs     |  34 +-
 drivers/gpu/nova-core/gsp/fw.rs               |  44 +-
 drivers/gpu/nova-core/gsp/fw/commands.rs      | 108 ++-
 .../gpu/nova-core/gsp/fw/r000_00/bindings.rs  | 175 +++++
 drivers/gpu/nova-core/gsp/hal/gh100.rs        |   2 +-
 drivers/gpu/nova-core/gsp/hal/tu102.rs        |   2 +-
 drivers/gpu/nova-core/gsp/nvkv.rs             |  42 +-
 drivers/gpu/nova-core/gsp/nvkv/encode.rs      |  12 +
 drivers/gpu/nova-core/mm.rs                   |   1 +
 drivers/gpu/nova-core/mm/bar_user.rs          | 209 +++++-
 drivers/gpu/nova-core/mm/vmm.rs               |  18 +-
 drivers/gpu/nova-core/mm/vram.rs              | 131 ++++
 drivers/gpu/nova-core/nova_core.rs            |   6 +
 drivers/gpu/nova-core/regs.rs                 |  31 +-
 drivers/gpu/nova-core/vgpu.rs                 | 111 ++-
 drivers/gpu/nova-core/vgpu/commands.rs        | 321 +++++++++
 drivers/gpu/nova-core/vgpu/fw.rs              |  70 ++
 drivers/gpu/nova-core/vgpu/fw/commands.rs     | 470 +++++++++++++
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs | 496 ++++++++++++++
 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs  | 170 +++++
 drivers/gpu/nova-core/vgpu/instance.rs        | 642 ++++++++++++++++++
 drivers/gpu/nova-core/vgpu/log.rs             | 171 +++++
 drivers/gpu/nova-core/vgpu/scrubber.rs        | 173 +++++
 drivers/gpu/nova-core/vgpu/vgpu_api.rs        | 158 +++++
 drivers/gpu/nova-core/vgpu/vram.rs            | 179 +++++
 34 files changed, 4085 insertions(+), 154 deletions(-)
 create mode 100644 drivers/gpu/nova-core/mm/vram.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/commands.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/fw.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/fw/commands.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/instance.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/log.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/scrubber.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/vgpu_api.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/vram.rs

base-commit: ccfd86490ddf6fd9c8f59a1a3171c652afb44c7f

Signature · 1 line
2.53.0
author
#1 lore raw

PATCH v4 1/32 gpu: nova-core: mm: retain BAR1 reservations after unmap failure

From: Zhi Wang <[email protected]>

Vmm::unmap_pages currently returns a virtual address range to its pool even when PTE invalidation or its TLB flush fails. Hardware can still use that mapping, so reusing the range can alias a live mapping.

Borrow MappedRange during unmap and release its VA reservation only after invalidation succeeds. Disarm its drop guard at that point and reject a second unmap of a range whose guard is already disarmed.

Keep BarUserAccess's mapped state until unmap succeeds, and update its callers to pass a borrowed range. A failed unmap leaves the VA reserved; callers must retain the backing storage while hardware may still access it.

Extend the MM self-test to unmap an old range after its virtual address has been reused. Require the stale token to fail without invalidating the new mapping.

---
 drivers/gpu/nova-core/mm/bar_user.rs | 35 +++++++++++++++++++---------
 drivers/gpu/nova-core/mm/vmm.rs      | 18 +++++++-------
 2 files changed, 34 insertions(+), 19 deletions(-)
drivers/gpu/nova-core/mm/bar_user.rs +24 -11
index 6e9384f06f43..622a79f1cc22 100644
@@ -83,8 +83,7 @@ pub(crate) fn map(
 /// Borrows the [`BarUser`] managing the mapping for the lifetime of the access object.
 pub(crate) struct BarUserAccess<'a> {
     bar_user: &'a BarUser<'a>,
-    /// [`BarUserAccess::release`] [`Option::take`]s this; `Some` at
-    /// drop time means `release()` was never called.
+    /// Cleared only after PTE invalidation and its TLB flush succeed.
     mapped: Option<MappedRange>,
 }
 
@@ -92,16 +91,22 @@ pub(crate) struct BarUserAccess<'a> {
 impl BarUserAccess<'_> {
     /// Tear down the BAR1 mapping.
     pub(crate) fn release(mut self, mm: &mut GpuMm<'_>) -> Result {
-        let mapped = self.mapped.take().ok_or(EINVAL)?;
+        self.unmap(mm)
+    }
+
+    fn unmap(&mut self, mm: &mut GpuMm<'_>) -> Result {
+        let Some(mapped) = self.mapped.as_ref() else {
+            return Ok(());
+        };
         let mut vmm = self.bar_user.vmm.lock();
         vmm.unmap_pages(mm, mapped)?;
+        self.mapped = None;
         Ok(())
     }
 
     /// Returns the active mapping.
     fn mapped(&self) -> &MappedRange {
-        // `mapped` is only `None` after `take()` in `release`; hence unwrap()
-        // cannot panic here.
+        // `release` consumes the access, so no accessor can run after successful release.
         self.mapped.as_ref().unwrap()
     }
 
@@ -167,7 +172,7 @@ impl Drop for BarUserAccess<'_> {
     fn drop(&mut self) {
         if self.mapped.is_some() {
             kernel::pr_warn!(
-                "BarUserAccess dropped without calling release(). BarUser address space will leak.\n"
+                "BarUserAccess dropped with a live mapping; BAR1 VA remains reserved.\n"
             );
         }
         // The inner `MappedRange`'s own `MustUnmapGuard` will also fire,
@@ -266,19 +271,27 @@ pub(crate) fn run_self_test(
     };
 
     // Cleanup - invalidate PTE.
-    vmm.unmap_pages(mm, mapped)?;
+    vmm.unmap_pages(mm, &mapped)?;
 
     // Test 2: Two-phase prepare/execute API.
     let prepared = vmm.prepare_map(mm, 1, None)?;
     let mapped2 = vmm.execute_map(mm, prepared, &[test_pfn], true)?;
+    // An old token must not invalidate a new mapping that reused its address.
+    let repeated_unmap = vmm.unmap_pages(mm, &mapped);
     let readback = vmm.read_mapping(mm, mapped2.vfn_start)?;
-    let test2_passed = if readback == Some(test_pfn) {
+    let test2_passed = if mapped2.vfn_start == test_vfn
+        && repeated_unmap == Err(EINVAL)
+        && readback == Some(test_pfn)
+    {
         true
     } else {
-        dev_err!(dev, "MM: Test 2 FAILED - Two-phase map readback mismatch\n");
+        dev_err!(
+            dev,
+            "MM: Test 2 FAILED - Remapped page or stale-token rejection mismatch\n"
+        );
         false
     };
-    vmm.unmap_pages(mm, mapped2)?;
+    vmm.unmap_pages(mm, &mapped2)?;
 
     // Test 3: Range-constrained allocation with a hole — exercises block.size()-driven
     // BAR1 mapping. A 4K hole is punched at base+16K, then a single 32K allocation
@@ -370,7 +383,7 @@ pub(crate) fn run_self_test(
             }
         }
 
-        vmm.unmap_pages(mm, mapped)?;
+        vmm.unmap_pages(mm, &mapped)?;
     }
 
     // Verify aggregate: all returned block sizes must sum to allocation size.
drivers/gpu/nova-core/mm/vmm.rs +10 -8
index 51b500a27233..49b1e5db1498 100644
@@ -324,23 +324,25 @@ pub(crate) fn map_pages(
     }
 
     /// Unmap all pages in a [`MappedRange`] with a single TLB flush.
-    pub(crate) fn unmap_pages(&mut self, mm: &mut GpuMm<'_>, range: MappedRange) -> Result {
-        let result = self
-            .pt_map
-            .invalidate_ptes(mm, range.vfn_start, range.num_pages);
+    ///
+    /// A failed invalidation retains the VA reservation. The caller must keep
+    /// the mapped storage alive while hardware may still access it.
+    pub(crate) fn unmap_pages(&mut self, mm: &mut GpuMm<'_>, range: &MappedRange) -> Result {
+        if !range._drop_guard.armed.get() {
+            return Err(EINVAL);
+        }
+        self.pt_map
+            .invalidate_ptes(mm, range.vfn_start, range.num_pages)?;
 
         // TODO: Internal page table pages (PDE, PTE pages) are still kept around.
         // This is by design as repeated maps/unmaps will be fast. As a future TODO,
         // we can add a reclaimer here to reclaim if VRAM is short. For now, the PT
         // pages are dropped once the `Vmm` is dropped.
 
-        // Free the VA range regardless of PTE invalidation success, so that the VA
-        // range is recovered even on failure (PTEs may be stale, but that is better
-        // than leaking both PTEs and VA range).
         self.free_vfn(range.vfn_start);
 
         // Unmap complete, safe to drop `MappedRange`.
         range._drop_guard.disarm();
-        result
+        Ok(())
     }
 }
Signature · 1 line
2.53.0
author
#2 lore raw

PATCH v4 2/32 gpu: nova-core: gsp: pass boot context through setup helpers

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The GSP boot paths pass the device, chipset, BAR and vGPU state to the VRAM layout and GSP_INIT helpers as separate arguments. These values are already available in GspBootContext, and some are forwarded again to the WPR2 heap and VF topology helpers.

Pass the boot context through these helpers so each can read the fields it needs. This simplifies the helper signatures and their call sites.

No functional change is intended.

---
 drivers/gpu/nova-core/fb.rs               | 26 +++++++++-----------
 drivers/gpu/nova-core/gsp/boot.rs         |  8 +++---
 drivers/gpu/nova-core/gsp/commands/gmc.rs | 17 ++++---------
 drivers/gpu/nova-core/gsp/fw/commands.rs  | 30 ++++++++++-------------
 drivers/gpu/nova-core/gsp/hal/gh100.rs    |  2 +-
 drivers/gpu/nova-core/gsp/hal/tu102.rs    |  2 +-
 6 files changed, 34 insertions(+), 51 deletions(-)
drivers/gpu/nova-core/fb.rs +11 -15
index 5ceb7760c78e..26e94d908b77 100644
@@ -174,13 +174,9 @@ pub(crate) struct FbRanges {
 
 impl FbRanges {
     /// Computes concrete framebuffer ranges required on non-FSP booting architectures.
-    pub(crate) fn new(
-        chipset: Chipset,
-        bar: Bar0<'_>,
-        gsp_fw: &GspFirmware<'_>,
-        vgpu_state: VgpuState,
-    ) -> Result<Self> {
-        let hal = hal::fb_hal(chipset);
+    pub(crate) fn new(ctx: &gsp::GspBootContext<'_, '_>, gsp_fw: &GspFirmware<'_>) -> Result<Self> {
+        let bar = ctx.bar;
+        let hal = hal::fb_hal(ctx.chipset);
 
         let fb = {
             let fb_size = hal.vidmem_size(bar);
@@ -242,7 +238,7 @@ pub(crate) fn new(
             FbRange(fw_image_addr..fw_image_addr + fw_image_size)
         };
 
-        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb.end)?;
+        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(ctx, fb.end)?;
 
         let wpr2_heap = {
             const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::SZ_1M;
@@ -298,11 +294,11 @@ pub(crate) fn wpr2_range(bar: Bar0<'_>) -> Option<Range<u64>> {
 }
 
 /// Computes the number of VF partitions and the WPR2 heap size from the vGPU state.
-fn wpr2_heap_params(chipset: Chipset, vgpu_state: VgpuState, fb_size: u64) -> Result<(u8, u64)> {
-    Ok(match vgpu_state {
+fn wpr2_heap_params(ctx: &gsp::GspBootContext<'_, '_>, fb_size: u64) -> Result<(u8, u64)> {
+    Ok(match ctx.vgpu.state() {
         VgpuState::Disabled => (
             0,
-            gsp::LibosParams::from_chipset(chipset).wpr_heap_size(chipset, fb_size)?,
+            gsp::LibosParams::from_chipset(ctx.chipset).wpr_heap_size(ctx.chipset, fb_size)?,
         ),
         VgpuState::Enabled { total_vfs } => (
             u8::try_from(total_vfs.get()).map_err(|_| EINVAL)?,
@@ -328,10 +324,10 @@ pub(crate) struct FbSizes {
 
 impl FbSizes {
     /// Computes the framebuffer region sizes for GSP-FMC boot.
-    pub(crate) fn new(chipset: Chipset, bar: Bar0<'_>, vgpu_state: VgpuState) -> Result<Self> {
-        let hal = hal::fb_hal(chipset);
-        let fb_size = hal.vidmem_size(bar);
-        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(chipset, vgpu_state, fb_size)?;
+    pub(crate) fn new(ctx: &gsp::GspBootContext<'_, '_>) -> Result<Self> {
+        let hal = hal::fb_hal(ctx.chipset);
+        let fb_size = hal.vidmem_size(ctx.bar);
+        let (vf_partition_count, wpr2_heap_size) = wpr2_heap_params(ctx, fb_size)?;
 
         Ok(Self {
             frts_size: hal.frts_size(),
drivers/gpu/nova-core/gsp/boot.rs +3 -5
index 072b00e9c2bf..72353270fce6 100644
@@ -526,11 +526,9 @@ pub(crate) fn boot(
             bootloader_app_version: gsp_fw.bootloader.app_version,
             libos_dma_handle: self.libos.dma_address(),
         };
-        let static_info = self
-            .cmdq
-            .send_gmc_command(GspInit::new(pdev, chipset, ctx.vgpu.state())?, |event| {
-                load_exec.dispatch_gmc_boot_event(event)
-            })?;
+        let static_info = self.cmdq.send_gmc_command(GspInit::new(ctx)?, |event| {
+            load_exec.dispatch_gmc_boot_event(event)
+        })?;
 
         Ok(super::BootResult {
             unload_bundle: unload_guard.dismiss().1,
drivers/gpu/nova-core/gsp/commands/gmc.rs +5 -12
index 3ff44c867288..890462beb11f 100644
@@ -6,14 +6,11 @@
 use core::convert::Infallible;
 
 use kernel::{
-    device,
-    pci,
     prelude::*,
     transmute::AsBytes, //
 };
 
 use crate::{
-    gpu::Chipset,
     gsp::{
         cmdq::{
             gmc::{
@@ -38,9 +35,9 @@
             Schema,
             UnknownKeyPolicy, //
         },
+        GspBootContext, //
     },
-    sbuffer::SBufferIter,
-    vgpu::VgpuState, //
+    sbuffer::SBufferIter, //
 };
 
 /// The `GSP_INIT` command, which carries the system information and registry keys to GSP-RM.
@@ -52,7 +49,7 @@ pub(crate) struct GspInit {
 }
 
 impl GspInit {
-    /// Builds the request for `pdev`.
+    /// Builds the request using the device, chipset and vGPU state from `ctx`.
     ///
     /// # Errors
     ///
@@ -60,13 +57,9 @@ impl GspInit {
     /// - `ENODEV` if vGPU mode is enabled but the SR-IOV capability is missing.
     ///
     /// Errors reading the PCI configuration or decoding the VF BAR layout are propagated as-is.
-    pub(crate) fn new(
-        pdev: &pci::Device<device::Bound>,
-        chipset: Chipset,
-        vgpu_state: VgpuState,
-    ) -> Result<Self> {
+    pub(crate) fn new(ctx: &GspBootContext<'_, '_>) -> Result<Self> {
         let mut encoder = Encoder::new();
-        GspInitRequest::new(pdev, chipset, vgpu_state)?.encode(&mut encoder)?;
+        GspInitRequest::new(ctx)?.encode(&mut encoder)?;
 
         Ok(Self {
             payload: encoder.finish(),
drivers/gpu/nova-core/gsp/fw/commands.rs +13 -17
index f196a56dd7ee..689b7417597a 100644
@@ -6,7 +6,6 @@
 use kernel::{
     alloc::ArrayVec,
     bitfield,
-    device,
     pci,
     prelude::*,
     transmute::{
@@ -16,7 +15,6 @@
 };
 
 use crate::{
-    gpu::Chipset,
     gsp::{
         commands::GspStaticInfo,
         nvkv::{
@@ -29,6 +27,7 @@
             Required,
             TruncatingArray, //
         },
+        GspBootContext, //
     },
     vgpu::VgpuState, //
 };
@@ -229,16 +228,14 @@ struct DomainBusDevice(u64) {
 ];
 
 impl GspInitRequest {
-    fn build_vf_info(
-        dev: &pci::Device<device::Bound>,
-        vgpu_state: VgpuState,
-    ) -> Result<Option<VfInfo>> {
-        let total_vfs = match vgpu_state {
+    fn build_vf_info(ctx: &GspBootContext<'_, '_>) -> Result<Option<VfInfo>> {
+        let total_vfs = match ctx.vgpu.state() {
             VgpuState::Disabled => return Ok(None),
             VgpuState::Enabled { total_vfs } => total_vfs,
         };
 
-        let sriov = dev
+        let sriov = ctx
+            .pdev
             .config_space_extended()?
             .find_ext_capability::<pci::ExtSriovRegs>()?
             .ok_or(ENODEV)?;
@@ -261,10 +258,10 @@ fn build_vf_info(
         }))
     }
 
-    /// Creates the request for `dev`.
+    /// Creates the request using the device, chipset and vGPU state from `ctx`.
     ///
-    /// The registry keys are [`REGISTRY_ENTRIES`], plus `RMSetSriovMode` when `vgpu_state` reports
-    /// that vGPU is enabled.
+    /// The registry keys are [`REGISTRY_ENTRIES`], plus `RMSetSriovMode` when the boot context
+    /// reports that vGPU is enabled.
     ///
     /// # Errors
     ///
@@ -272,11 +269,10 @@ fn build_vf_info(
     /// - `ENODEV` if vGPU mode is enabled but the SR-IOV capability is missing.
     ///
     /// Errors reading the PCI configuration or decoding the VF BAR layout are propagated as-is.
-    pub(crate) fn new(
-        dev: &pci::Device<device::Bound>,
-        chipset: Chipset,
-        vgpu_state: VgpuState,
-    ) -> Result<Self> {
+    pub(crate) fn new(ctx: &GspBootContext<'_, '_>) -> Result<Self> {
+        let dev = ctx.pdev;
+        let chipset = ctx.chipset;
+        let vgpu_state = ctx.vgpu.state();
         let mut regkeys = KVVec::new();
         for &(name, value) in REGISTRY_ENTRIES {
             regkeys.push(RegKey::new(name, value), GFP_KERNEL)?;
@@ -304,7 +300,7 @@ pub(crate) fn new(
             host_arch: HostArch::host().into(),
             bus_device_func: u64::from(bus_device_func).into(),
             regkeys,
-            vf_info: Self::build_vf_info(dev, vgpu_state)?,
+            vf_info: Self::build_vf_info(ctx)?,
         })
     }
 }
drivers/gpu/nova-core/gsp/hal/gh100.rs +1 -1
index 91201b51030e..f91cdd061706 100644
@@ -151,7 +151,7 @@ fn boot<'gpu>(
         let chipset = ctx.chipset;
         let gsp_falcon = ctx.gsp_falcon;
 
-        let fb_sizes = FbSizes::new(chipset, ctx.bar, ctx.vgpu.state())?;
+        let fb_sizes = FbSizes::new(ctx)?;
         dev_dbg!(dev, "{:#x?}\n", fb_sizes);
 
         let wpr_meta =
drivers/gpu/nova-core/gsp/hal/tu102.rs +1 -1
index f8a7b8d9f36f..a7639a98e60a 100644
@@ -260,7 +260,7 @@ fn boot<'gpu>(
         let gsp_falcon = ctx.gsp_falcon;
         let sec2_falcon = ctx.sec2_falcon;
 
-        let fb_ranges = FbRanges::new(chipset, bar, gsp_fw, ctx.vgpu.state())?;
+        let fb_ranges = FbRanges::new(ctx, gsp_fw)?;
         dev_dbg!(dev, "{:#x?}\n", fb_ranges);
 
         // Declared before the unload guard so that if Booter fails while running, SEC2 is reset
Signature · 1 line
2.53.0
maintainer Reviewed-by on v4 2/32
#3 lore raw

Re: [PATCH v4 02/32] gpu: nova-core: gsp: pass boot context through setup helpers

Quoted 7 lines · Alok Kumar

From: Zhi Wang <[email protected]>

The GSP boot paths pass the device, chipset, BAR and vGPU state to the VRAM layout and GSP_INIT helpers as separate arguments. These values are already available in GspBootContext, and some are forwarded again to the WPR2 heap and VF topology helpers.

Quoted 8 lines

Pass the boot context through these helpers so each can read the fields it needs. This simplifies the helper signatures and their call sites.

No functional change is intended.

Makes sense as the GSP HAL methods already take GspBootContext anyway.

(I would have merged it directly, but this depends on the r000 series so marking as reviewed for now)

author
#4 lore raw

PATCH v4 3/32 gpu: nova-core: gsp: decouple boot context from VgpuManager

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

GSP boot uses the detected vGPU mode to size firmware resources and populate GSP_INIT. Carrying the whole manager in the boot and unload context couples these operations to its runtime interface.

The boot code only needs read access to the mode, and unloading does not use the manager. A short state borrow is sufficient for both context construction sites.

Make GspBootContext borrow VgpuState directly and read it in the VRAM and GSP_INIT helpers. Return a state reference from the manager without changing mode detection or the values passed to firmware.

No functional change is intended.

---
 drivers/gpu/nova-core/fb.rs              | 2 +-
 drivers/gpu/nova-core/gpu.rs             | 4 ++--
 drivers/gpu/nova-core/gsp.rs             | 4 ++--
 drivers/gpu/nova-core/gsp/fw/commands.rs | 4 ++--
 drivers/gpu/nova-core/vgpu.rs            | 4 ++--
 5 files changed, 9 insertions(+), 9 deletions(-)
drivers/gpu/nova-core/fb.rs +1 -1
index 26e94d908b77..882827bcfd3a 100644
@@ -295,7 +295,7 @@ pub(crate) fn wpr2_range(bar: Bar0<'_>) -> Option<Range<u64>> {
 
 /// Computes the number of VF partitions and the WPR2 heap size from the vGPU state.
 fn wpr2_heap_params(ctx: &gsp::GspBootContext<'_, '_>, fb_size: u64) -> Result<(u8, u64)> {
-    Ok(match ctx.vgpu.state() {
+    Ok(match *ctx.vgpu_state {
         VgpuState::Disabled => (
             0,
             gsp::LibosParams::from_chipset(ctx.chipset).wpr_heap_size(ctx.chipset, fb_size)?,
drivers/gpu/nova-core/gpu.rs +2 -2
index e44f8cd8bdc2..d29e83f50137 100644
@@ -447,7 +447,7 @@ fn drop(self: Pin<&mut Self>) {
                     gsp_falcon: &*this.gsp_falcon,
                     sec2_falcon: &*this.sec2_falcon,
                     fsp: this.fsp.as_mut(),
-                    vgpu: &*this.vgpu,
+                    vgpu_state: this.vgpu.state(),
                 },
                 bundle,
             )
@@ -522,7 +522,7 @@ pub(crate) fn new<'a>(
                     gsp_falcon,
                     sec2_falcon,
                     fsp: fsp.as_mut(),
-                    vgpu,
+                    vgpu_state: vgpu.state(),
                 })?,
             }),
 
drivers/gpu/nova-core/gsp.rs +2 -2
index 9a6065bdac96..cac123cbeac4 100644
@@ -66,7 +66,7 @@
         fw::GspArgumentsPadded, //
     },
     num,
-    vgpu::VgpuManager, //
+    vgpu::VgpuState, //
 };
 
 pub(crate) const GSP_PAGE_SHIFT: usize = 12;
@@ -85,7 +85,7 @@ pub(crate) struct GspBootContext<'ctx, 'gpu> {
     pub(crate) gsp_falcon: &'ctx Falcon<'gpu, GspFalcon>,
     pub(crate) sec2_falcon: &'ctx Falcon<'gpu, Sec2Falcon>,
     pub(crate) fsp: Option<&'ctx mut Fsp<'gpu>>,
-    pub(crate) vgpu: &'ctx VgpuManager,
+    pub(crate) vgpu_state: &'ctx VgpuState,
 }
 
 impl<'ctx, 'gpu> GspBootContext<'ctx, 'gpu> {
drivers/gpu/nova-core/gsp/fw/commands.rs +2 -2
index 689b7417597a..bda6a7ef5ef7 100644
@@ -229,7 +229,7 @@ struct DomainBusDevice(u64) {
 
 impl GspInitRequest {
     fn build_vf_info(ctx: &GspBootContext<'_, '_>) -> Result<Option<VfInfo>> {
-        let total_vfs = match ctx.vgpu.state() {
+        let total_vfs = match *ctx.vgpu_state {
             VgpuState::Disabled => return Ok(None),
             VgpuState::Enabled { total_vfs } => total_vfs,
         };
@@ -272,7 +272,7 @@ fn build_vf_info(ctx: &GspBootContext<'_, '_>) -> Result<Option<VfInfo>> {
     pub(crate) fn new(ctx: &GspBootContext<'_, '_>) -> Result<Self> {
         let dev = ctx.pdev;
         let chipset = ctx.chipset;
-        let vgpu_state = ctx.vgpu.state();
+        let vgpu_state = *ctx.vgpu_state;
         let mut regkeys = KVVec::new();
         for &(name, value) in REGISTRY_ENTRIES {
             regkeys.push(RegKey::new(name, value), GFP_KERNEL)?;
drivers/gpu/nova-core/vgpu.rs +2 -2
index 6b7e045acea8..905b3a7331fd 100644
@@ -85,7 +85,7 @@ fn detect_state(
     }
 
     /// Returns the detected vGPU state for this boot.
-    pub(crate) fn state(&self) -> VgpuState {
-        self.state
+    pub(crate) fn state(&self) -> &VgpuState {
+        &self.state
     }
 }
Signature · 1 line
2.53.0
#5 lore raw

Re: [PATCH v4 03/32] gpu: nova-core: gsp: decouple boot context from VgpuManager

Quoted 10 lines · Alok Kumar

From: Zhi Wang <[email protected]>

GSP boot uses the detected vGPU mode to size firmware resources and populate GSP_INIT. Carrying the whole manager in the boot and unload context couples these operations to its runtime interface.

The boot code only needs read access to the mode, and unloading does not use the manager. A short state borrow is sufficient for both context construction sites.

Quoted 5 lines

Make GspBootContext borrow VgpuState directly and read it in the VRAM and GSP_INIT helpers. Return a state reference from the manager without changing mode detection or the values passed to firmware.

No functional change is intended.

This patch changes the signature of state right before it is removed by the next patch, which looks a bit like churn. Both patches are actually a single logical change and can be squashed together into a single one - the diff actually reads better that way imho.

<...>

Quoted 14 lines
drivers/gpu/nova-core/gsp.rs +1 -1
index 9a6065bdac96..cac123cbeac4 100644
@@ -66,7 +66,7 @@
         fw::GspArgumentsPadded, //
     },
     num,
-    vgpu::VgpuManager, //
+    vgpu::VgpuState, //
 };
 
 pub(crate) const GSP_PAGE_SHIFT: usize = 12;

drivers/gpu/nova-core/gsp.rs pub(crate) struct GspBootContext<'ctx, 'gpu> { · line 88

Quoted 5 lines
drivers/gpu/nova-core/gsp.rs +1 -1
@@ -85,7 +85,7 @@ pub(crate) struct GspBootContext<'ctx, 'gpu> {
     pub(crate) gsp_falcon: &'ctx Falcon<'gpu, GspFalcon>,
     pub(crate) sec2_falcon: &'ctx Falcon<'gpu, Sec2Falcon>,
     pub(crate) fsp: Option<&'ctx mut Fsp<'gpu>>,
-    pub(crate) vgpu: &'ctx VgpuManager,
+    pub(crate) vgpu_state: &'ctx VgpuState,

VgpuState is Copy, so let's store it by value. This will also remove the dereferences done through this patch.

author
#6 lore raw

PATCH v4 4/32 gpu: nova-core: vgpu: detect boot state independently

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The vGPU manager currently contains only the detected boot mode. Its construction couples mode detection to an object with no runtime management responsibilities.

Firmware setup needs the mode before allocating GSP resources, even when vGPU is disabled. Keeping that value with GspResources also makes it available throughout the firmware lifecycle without a manager.

Move detection to VgpuState and store its result in GspResources. Borrow it directly when constructing the boot and unload contexts, and remove the state-only manager wrapper. Preserve the FSP query order, existing diagnostics and disabled fallback.

No functional change is intended.

---
 drivers/gpu/nova-core/gpu.rs  | 11 +++++------
 drivers/gpu/nova-core/vgpu.rs | 25 ++++++++-----------------
 2 files changed, 13 insertions(+), 23 deletions(-)
drivers/gpu/nova-core/gpu.rs +5 -6
index d29e83f50137..fd0442f9a40b 100644
@@ -52,7 +52,7 @@
         GpuMm,
         VramAddress, //
     },
-    vgpu::VgpuManager, //
+    vgpu::VgpuState, //
 };
 
 #[cfg_attr(not(CONFIG_KUNIT = "y"), expect(dead_code))]
@@ -337,8 +337,7 @@ struct GspResources<'gpu> {
     // TODO: use different resource types for each boot method, and make the relevant Gsp methods
     // generic against them.
     fsp: Option<Fsp<'gpu>>,
-    /// vGPU state detected before GSP boot.
-    vgpu: VgpuManager,
+    vgpu_state: VgpuState,
     /// GSP runtime data.
     #[pin]
     gsp: Gsp<'gpu>,
@@ -447,7 +446,7 @@ fn drop(self: Pin<&mut Self>) {
                     gsp_falcon: &*this.gsp_falcon,
                     sec2_falcon: &*this.sec2_falcon,
                     fsp: this.fsp.as_mut(),
-                    vgpu_state: this.vgpu.state(),
+                    vgpu_state: this.vgpu_state,
                 },
                 bundle,
             )
@@ -508,7 +507,7 @@ pub(crate) fn new<'a>(
 
                 fsp: Fsp::try_new(dev, bar, spec.chipset)?,
 
-                vgpu: VgpuManager::new(pdev, spec.chipset, fsp.as_mut()),
+                vgpu_state: VgpuState::detect(pdev, spec.chipset, fsp.as_mut()),
 
                 gsp <- Gsp::new(pdev, *spec, bar),
 
@@ -522,7 +521,7 @@ pub(crate) fn new<'a>(
                     gsp_falcon,
                     sec2_falcon,
                     fsp: fsp.as_mut(),
-                    vgpu_state: vgpu.state(),
+                    vgpu_state,
                 })?,
             }),
 
drivers/gpu/nova-core/vgpu.rs +8 -17
index 905b3a7331fd..b405ba49c490 100644
@@ -30,19 +30,16 @@ pub(crate) enum VgpuState {
     },
 }
 
-/// vGPU state manager.
-pub(crate) struct VgpuManager {
-    state: VgpuState,
-}
-
-impl VgpuManager {
-    /// Creates a vGPU manager by querying SR-IOV and the FSP PRC vGPU knob.
-    pub(crate) fn new(
+impl VgpuState {
+    /// Detects the boot mode, falling back to disabled if querying the device fails.
+    ///
+    /// Call after creating the FSP and before allocating GSP firmware resources.
+    pub(crate) fn detect(
         pdev: &pci::Device<device::Core<'_>>,
         chipset: Chipset,
         fsp: Option<&mut Fsp<'_>>,
     ) -> Self {
-        let state = Self::detect_state(pdev, chipset, fsp).unwrap_or_else(|e| {
+        let state = Self::query_state(pdev, chipset, fsp).unwrap_or_else(|e| {
             dev_warn!(
                 pdev,
                 "vGPU state detection failed: {:?}; disabling vGPU\n",
@@ -51,12 +48,11 @@ pub(crate) fn new(
             VgpuState::Disabled
         });
         dev_dbg!(pdev, "vGPU state: {:?}\n", state);
-
-        Self { state }
+        state
     }
 
     /// Detects the vGPU state from the chipset, SR-IOV capability and FSP PRC knob.
-    fn detect_state(
+    fn query_state(
         pdev: &pci::Device<device::Core<'_>>,
         chipset: Chipset,
         fsp: Option<&mut Fsp<'_>>,
@@ -83,9 +79,4 @@ fn detect_state(
             VgpuMode::Disabled => Ok(VgpuState::Disabled),
         }
     }
-
-    /// Returns the detected vGPU state for this boot.
-    pub(crate) fn state(&self) -> &VgpuState {
-        &self.state
-    }
 }
Signature · 1 line
2.53.0
#7 lore raw

Re: [PATCH v4 04/32] gpu: nova-core: vgpu: detect boot state independently

On Fri Nov 6, 2026 at 2:39 PM JST, Alok Kumar wrote:
<...>

Quoted 11 lines
drivers/gpu/nova-core/vgpu.rs +0 -3
index 905b3a7331fd..b405ba49c490 100644
@@ -30,19 +30,16 @@ pub(crate) enum VgpuState {
     },
 }
 
-/// vGPU state manager.
-pub(crate) struct VgpuManager {
-    state: VgpuState,

drivers/gpu/nova-core/vgpu.rs pub(crate) enum VgpuState { · line 37

Quoted 10 lines
drivers/gpu/nova-core/vgpu.rs +5 -5
-}
-
-impl VgpuManager {
-    /// Creates a vGPU manager by querying SR-IOV and the FSP PRC vGPU knob.
-    pub(crate) fn new(
+impl VgpuState {
+    /// Detects the boot mode, falling back to disabled if querying the device fails.
+    ///
+    /// Call after creating the FSP and before allocating GSP firmware resources.
+    pub(crate) fn detect(

I think new was a fine method name here as it is a constructor...

Quoted 11 lines
drivers/gpu/nova-core/vgpu.rs +1 -1
         pdev: &pci::Device<device::Core<'_>>,
         chipset: Chipset,
         fsp: Option<&mut Fsp<'_>>,
     ) -> Self {
-        let state = Self::detect_state(pdev, chipset, fsp).unwrap_or_else(|e| {
+        let state = Self::query_state(pdev, chipset, fsp).unwrap_or_else(|e| {
             dev_warn!(
                 pdev,
                 "vGPU state detection failed: {:?}; disabling vGPU\n",
@@ -51,12 +48,11 @@ pub(crate) fn new(
             VgpuState::Disabled

drivers/gpu/nova-core/vgpu.rs pub(crate) fn new( · line 55

Quoted 10 lines
drivers/gpu/nova-core/vgpu.rs +2 -3
         });
         dev_dbg!(pdev, "vGPU state: {:?}\n", state);
-
-        Self { state }
+        state
     }
 
     /// Detects the vGPU state from the chipset, SR-IOV capability and FSP PRC knob.
-    fn detect_state(
+    fn query_state(

Here as well, I don't see a benefit in renaming this method.

author
#8 lore raw

PATCH v4 5/32 gpu: nova-core: gpu: add a channel ID pool for vGPU

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Channel ID reservations need a GPU-owned pool shared by vGPU instances.

The pool must remain at a stable address while its reservations exist. Its storage also needs to remain available throughout GPU cleanup.

Create a private pinned ChannelIdPool with a single 2048-channel capacity constant.

---
 drivers/gpu/nova-core/gpu.rs         | 9 +++++++++
 drivers/gpu/nova-core/gpu/channel.rs | 4 ++++
 2 files changed, 13 insertions(+)
drivers/gpu/nova-core/gpu.rs +9 -0
index fd0442f9a40b..3f8bdbb9a5b2 100644
@@ -52,6 +52,7 @@
         GpuMm,
         VramAddress, //
     },
+    num,
     vgpu::VgpuState, //
 };
 
@@ -60,6 +61,10 @@
 mod hal;
 mod regs;
 
+use self::channel::TOTAL_CHANNELS;
+
+pub(crate) use self::channel::ChannelIdPool;
+
 macro_rules! define_chipset {
     ({ $($variant:ident),* $(,)* }) =>
     {
@@ -410,6 +415,8 @@ pub(crate) struct Gpu<'gpu> {
     /// GSP and its resources.
     #[pin]
     gsp_resources: GspResources<'gpu>,
+    #[pin]
+    chid_pool: ChannelIdPool,
     /// System memory page required for flushing all pending GPU-side memory writes done through
     /// PCIE into system memory, via sysmembar (A GPU-initiated HW memory-barrier operation).
     ///
@@ -494,6 +501,8 @@ pub(crate) fn new<'a>(
             // Initialize this early because `gsp_resources` depends on it.
             sysmem_flush: SysmemFlush::register(dev, bar, spec.chipset)?,
 
+            chid_pool <- ChannelIdPool::new(cv!(num::u32_as_usize(TOTAL_CHANNELS))),
+
             gsp_resources <- try_pin_init!(GspResources {
                 device: pdev,
 
drivers/gpu/nova-core/gpu/channel.rs +4 -0
index 485efaba059d..75666c6768e6 100644
@@ -21,6 +21,10 @@
     }, //
 };
 
+// TODO: Query the channel capacity through GMCAPI once an equivalent of
+// NV2080_CTRL_CMD_INTERNAL_FIFO_GET_NUM_CHANNELS is available.
+pub(super) const TOTAL_CHANNELS: u32 = 2048;
+
 /// Pool for tracking reservations of channel IDs.
 #[pin_data]
 pub(crate) struct ChannelIdPool {
Signature · 1 line
2.53.0
author
#9 lore raw

PATCH v4 6/32 gpu: nova-core: gsp: decode the FIFO engine table

In reply to Alok Kumar

GSP_INIT reports the FIFO engine topology used to build the channel map for vGPU instances.

Decode the count, GMC IDs and flags into GspStaticInfo and expose host-driven engines in firmware FIFO order, retaining repeated IDs. Reject counts and indexed entries beyond the driver's 64-entry table capacity rather than silently accepting a prefix. An omitted count remains zero.

---
 drivers/gpu/nova-core/gsp/commands.rs     | 10 +++
 drivers/gpu/nova-core/gsp/commands/gmc.rs |  4 +-
 drivers/gpu/nova-core/gsp/fw/commands.rs  | 77 +++++++++++++++++++++++
 3 files changed, 90 insertions(+), 1 deletion(-)
drivers/gpu/nova-core/gsp/commands.rs +10 -0
index 8b047744c36d..f5ec27995627 100644
@@ -11,6 +11,8 @@
 
 use kernel::prelude::*;
 
+use super::fw::commands::FifoEngineList;
+
 /// The static GPU configuration, which GSP-RM reports in reply to [`GspInit`](gmc::GspInit).
 pub struct GspStaticInfo {
     gpu_name: [u8; Self::MAX_GPU_NAME_LEN],
@@ -23,6 +25,8 @@ pub struct GspStaticInfo {
     pub(crate) usable_fb_regions: KVec<Range<u64>>,
     /// Exclusive end of the FB physical address space.
     pub(crate) total_fb_end: u64,
+    /// Host-driven engines in firmware FIFO order.
+    fifo_engine_list: FifoEngineList,
 }
 
 /// Error type for [`GspStaticInfo::gpu_name`].
@@ -63,6 +67,12 @@ pub fn gpu_short_name(&self) -> core::result::Result<&str, GpuNameError> {
             .map_err(GpuNameError::InvalidUtf8)
     }
 
+    /// Returns the host-driven engines in their firmware FIFO order.
+    #[expect(dead_code)]
+    pub(crate) fn fifo_engine_list(&self) -> FifoEngineList {
+        self.fifo_engine_list
+    }
+
     /// Returns the total usable VRAM size in bytes, i.e. the summed lengths of all usable FB
     /// regions.
     pub fn vram_size(&self) -> u64 {
drivers/gpu/nova-core/gsp/commands/gmc.rs +3 -1
index 890462beb11f..c9258ee9e059 100644
@@ -145,7 +145,8 @@ impl MessageFromGsp for GspStaticInfo {
     /// # Errors
     ///
     /// - `EINVAL` if the payload is not a whole number of NVKV words, if the stream is malformed
-    ///   or omits a required key, or if GSP-RM reported no framebuffer region.
+    ///   or omits a required key, if GSP-RM reported no framebuffer region, or if the FIFO engine
+    ///   count or indexed entries exceed the supported table capacity.
     /// - `ENOMEM` if the words or the decoded regions cannot be allocated.
     fn read(payload: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>) -> Result<Self> {
         let bytes = payload.flush_into_kvec(GFP_KERNEL)?;
@@ -184,6 +185,7 @@ fn read(payload: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>) -> Result<Se
             bar1_pde_base: reply.bar1_pde_base(),
             usable_fb_regions,
             total_fb_end: reply.total_fb_end().ok_or(EINVAL)?,
+            fifo_engine_list: reply.fifo_engine_list(),
         })
     }
 }
drivers/gpu/nova-core/gsp/fw/commands.rs +77 -0

109 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#10 lore raw

PATCH v4 7/32 gpu: nova-core: vgpu: initialize runtime parameters after GSP boot

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The vGPU manager needs the VMMU segment size and FIFO engine topology returned by a successful GSP boot to divide instance resources.

Construct VgpuManager only for a vGPU-enabled boot. Retain the GPU-owned channel pool, the ordered host-driven engine
list and the VMMU segment size. Expose the existing decoded VMMU value from GspStaticInfo at its first consumer, preserving zero when firmware omits the key.

Keep the runtime values in the manager for its instance module, and drop the manager before the pool and memory resources it borrows.

---
 drivers/gpu/nova-core/gpu.rs              | 23 ++++++++++++++-
 drivers/gpu/nova-core/gsp/commands.rs     |  5 ++--
 drivers/gpu/nova-core/gsp/commands/gmc.rs |  1 +
 drivers/gpu/nova-core/gsp/fw/commands.rs  |  2 +-
 drivers/gpu/nova-core/vgpu.rs             | 36 ++++++++++++++++++++++-
 5 files changed, 62 insertions(+), 5 deletions(-)
drivers/gpu/nova-core/gpu.rs +22 -1
index 3f8bdbb9a5b2..002c5ba134a6 100644
@@ -53,7 +53,10 @@
         VramAddress, //
     },
     num,
-    vgpu::VgpuState, //
+    vgpu::{
+        VgpuManager,
+        VgpuState, //
+    },
 };
 
 #[cfg_attr(not(CONFIG_KUNIT = "y"), expect(dead_code))]
@@ -397,6 +400,7 @@ unsafe fn new(
 #[pin_data]
 pub(crate) struct Gpu<'gpu> {
     pub(crate) spec: Spec,
+    vgpu: Option<VgpuManager<'gpu>>,
     /// GSP event interrupt registration, and the enable of its subtree.
     ///
     /// Must be kept declared *before* `gsp_resources`, so that the handler is unregistered, and
@@ -534,6 +538,23 @@ pub(crate) fn new<'a>(
                 })?,
             }),
 
+            vgpu: {
+                let info = &gsp_resources.boot_result.static_info;
+                match gsp_resources.vgpu_state {
+                    VgpuState::Disabled => None,
+                    VgpuState::Enabled { .. } => Some(VgpuManager::new(
+                        // SAFETY: `chid_pool` is initialized above at its final pinned address.
+                        // The private manager and its pool borrow cannot escape this `Gpu`.
+                        // Completed field drop order drops the manager before the pool; on failure,
+                        // pin-init drops it before the earlier-initialized pool.
+                        unsafe { &*core::ptr::from_ref(chid_pool.as_ref().get_ref()) },
+                        &info.fifo_engine_list(),
+                        info.vmmu_segment_size,
+                        TOTAL_CHANNELS,
+                    )),
+                }
+            },
+
             _: {
                 irq::gsp::quiesce(tree_ref, &gsp_resources.gsp_falcon);
             },
drivers/gpu/nova-core/gsp/commands.rs +3 -2
index f5ec27995627..799f26e0ae74 100644
@@ -11,7 +11,7 @@
 
 use kernel::prelude::*;
 
-use super::fw::commands::FifoEngineList;
+pub(crate) use super::fw::commands::FifoEngineList;
 
 /// The static GPU configuration, which GSP-RM reports in reply to [`GspInit`](gmc::GspInit).
 pub struct GspStaticInfo {
@@ -25,6 +25,8 @@ pub struct GspStaticInfo {
     pub(crate) usable_fb_regions: KVec<Range<u64>>,
     /// Exclusive end of the FB physical address space.
     pub(crate) total_fb_end: u64,
+    /// VMMU segment size in bytes, or zero if GSP-RM omitted it.
+    pub(crate) vmmu_segment_size: u64,
     /// Host-driven engines in firmware FIFO order.
     fifo_engine_list: FifoEngineList,
 }
@@ -68,7 +70,6 @@ pub fn gpu_short_name(&self) -> core::result::Result<&str, GpuNameError> {
     }
 
     /// Returns the host-driven engines in their firmware FIFO order.
-    #[expect(dead_code)]
     pub(crate) fn fifo_engine_list(&self) -> FifoEngineList {
         self.fifo_engine_list
     }
drivers/gpu/nova-core/gsp/commands/gmc.rs +1 -0
index c9258ee9e059..6f88c99349ac 100644
@@ -186,6 +186,7 @@ fn read(payload: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>) -> Result<Se
             usable_fb_regions,
             total_fb_end: reply.total_fb_end().ok_or(EINVAL)?,
             fifo_engine_list: reply.fifo_engine_list(),
+            vmmu_segment_size: reply.vmmu_segment_size,
         })
     }
 }
drivers/gpu/nova-core/gsp/fw/commands.rs +1 -1
index 07115ccf3e95..bdc0764bb898 100644
@@ -378,7 +378,7 @@ pub(crate) struct GspInitResponse {
     gpu_gid: ArrayVec<u8, { GspStaticInfo::GID_LEN }>,
     fb_regions: KVVec<FbRegion>,
     bar1_pde_base: u64,
-    vmmu_segment_size: u64,
+    pub(crate) vmmu_segment_size: u64,
     fifo_engine_count: FifoEngineCount,
     fifo_engine_gmc_ids: [u32; MAX_FIFO_ENGINES],
     fifo_engine_flags: [u32; MAX_FIFO_ENGINES],
drivers/gpu/nova-core/vgpu.rs +35 -1
index b405ba49c490..5cd82adeb0f8 100644
@@ -13,7 +13,11 @@
         Fsp,
         VgpuMode, //
     },
-    gpu::Chipset, //
+    gpu::{
+        ChannelIdPool,
+        Chipset, //
+    },
+    gsp::commands::FifoEngineList, //
 };
 
 mod hal;
@@ -80,3 +84,33 @@ fn query_state(
         }
     }
 }
+
+/// Runtime resources for an enabled vGPU boot.
+pub(crate) struct VgpuManager<'gpu> {
+    #[expect(dead_code)]
+    chid_pool: &'gpu ChannelIdPool,
+    /// VMMU segment size in bytes, or zero if GSP-RM omitted it.
+    #[expect(dead_code)]
+    vmmu_segment_size: u64,
+    #[expect(dead_code)]
+    total_channels: u32,
+    #[expect(dead_code)]
+    fifo_engine_list: FifoEngineList,
+}
+
+impl<'gpu> VgpuManager<'gpu> {
+    /// Retains runtime parameters from a completed vGPU-enabled GSP boot.
+    pub(crate) fn new(
+        chid_pool: &'gpu ChannelIdPool,
+        fifo_engine_list: &FifoEngineList,
+        vmmu_segment_size: u64,
+        total_channels: u32,
+    ) -> Self {
+        Self {
+            chid_pool,
+            vmmu_segment_size,
+            total_channels,
+            fifo_engine_list: *fifo_engine_list,
+        }
+    }
+}
Signature · 1 line
2.53.0
author
#11 lore raw

PATCH v4 8/32 gpu: nova-core: vgpu: reserve the 48-VM WPR2 heap

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

GSP-RM needs the larger r000 vGPU WPR2 heap on devices that advertise more than 32 VFs. The default heap is too small for the 48-VF GB202 configuration and GSP-RM fails to start.

Select the firmware-defined 48-VM size from the detected VF count while retaining the default size for configurations with 2 through 32 VFs.

---
 drivers/gpu/nova-core/fb.rs                      | 2 +-
 drivers/gpu/nova-core/gsp/fw.rs                  | 7 +++++--
 drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs | 1 +
 3 files changed, 7 insertions(+), 3 deletions(-)
drivers/gpu/nova-core/fb.rs +1 -1
index 882827bcfd3a..ba68a812b095 100644
@@ -302,7 +302,7 @@ fn wpr2_heap_params(ctx: &gsp::GspBootContext<'_, '_>, fb_size: u64) -> Result<(
         ),
         VgpuState::Enabled { total_vfs } => (
             u8::try_from(total_vfs.get()).map_err(|_| EINVAL)?,
-            gsp::LibosParams::vgpu_wpr_heap_size(),
+            gsp::LibosParams::vgpu_wpr_heap_size(total_vfs.get()),
         ),
     })
 }
drivers/gpu/nova-core/gsp/fw.rs +5 -2
index f1efd3c66d2d..dfe7aafcf31f 100644
@@ -138,8 +138,11 @@ pub(crate) fn from_chipset(chipset: Chipset) -> &'static LibosParams {
     }
 
     /// Returns the WPR heap size to reserve when vGPU is enabled.
-    pub(crate) fn vgpu_wpr_heap_size() -> u64 {
-        u64::from(bindings::GSP_FW_HEAP_SIZE_VGPU_DEFAULT)
+    pub(crate) fn vgpu_wpr_heap_size(total_vfs: u16) -> u64 {
+        u64::from(match total_vfs {
+            2..=32 => bindings::GSP_FW_HEAP_SIZE_VGPU_DEFAULT,
+            _ => r000_00::GSP_FW_HEAP_SIZE_VGPU_48VMS,
+        })
     }
 
     /// Returns the amount of memory (in bytes) to allocate for the WPR heap for a framebuffer size
drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs +1 -0
index 454d0ea9360f..fa17d3e2edf6 100644
@@ -84,6 +84,7 @@ impl<T> ::core::cmp::Eq for __BindgenUnionField<T> {}
 pub const GSP_FW_HEAP_PARAM_SIZE_PER_GB: u32 = 98304;
 pub const GSP_FW_HEAP_PARAM_CLIENT_ALLOC_SIZE: u32 = 100663296;
 pub const GSP_FW_HEAP_SIZE_VGPU_DEFAULT: u32 = 609222656;
+pub const GSP_FW_HEAP_SIZE_VGPU_48VMS: u32 = 1436549120;
 pub const GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MIN_MB: u32 = 64;
 pub const GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MAX_MB: u32 = 256;
 pub const GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MIN_MB: u32 = 88;
Signature · 1 line
2.53.0
author
#12 lore raw

PATCH v4 9/32 gpu: nova-core: mm: borrow BarUser for temporary BAR1 access

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Temporary BAR1 access needs a borrow of the GPU-owned BarUser. Store BarUser inline in Gpu and borrow it in BarUserAccess, avoiding a separate allocation and reference counting for these accesses. Update the existing self-test entry points to accept the borrowed interface.

No functional change is intended.

---
 drivers/gpu/nova-core/gpu.rs         |  2 +-
 drivers/gpu/nova-core/mm/bar_user.rs | 16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)
drivers/gpu/nova-core/gpu.rs +1 -1
index 002c5ba134a6..98772868979d 100644
@@ -666,7 +666,7 @@ pub(crate) fn run_selftests(self: Pin<&mut Self>, pdev: &pci::Device<device::Bou
             dev,
             this.mm,
             &info.usable_fb_regions,
-            &this.bar_user,
+            this.bar_user.as_ref().get_ref(),
             info.bar1_pde_base,
             this.spec.chipset,
         ) {
drivers/gpu/nova-core/mm/bar_user.rs +8 -8
index 622a79f1cc22..7f389aea7ca3 100644
@@ -59,12 +59,12 @@ pub(crate) fn new(
     }
 
     /// Map physical pages to a contiguous BAR1 virtual range.
-    pub(crate) fn map(
-        &self,
+    pub(crate) fn map<'access>(
+        &'access self,
         mm: &mut GpuMm<'_>,
         pfns: &[Pfn],
         writable: bool,
-    ) -> Result<BarUserAccess<'_>> {
+    ) -> Result<BarUserAccess<'access, 'a>> {
         if pfns.is_empty() {
             return Err(EINVAL);
         }
@@ -81,14 +81,14 @@ pub(crate) fn map(
 /// Access object for a mapped BAR1 region.
 ///
 /// Borrows the [`BarUser`] managing the mapping for the lifetime of the access object.
-pub(crate) struct BarUserAccess<'a> {
-    bar_user: &'a BarUser<'a>,
+pub(crate) struct BarUserAccess<'access, 'gpu> {
+    bar_user: &'access BarUser<'gpu>,
     /// Cleared only after PTE invalidation and its TLB flush succeed.
     mapped: Option<MappedRange>,
 }
 
 #[expect(dead_code)]
-impl BarUserAccess<'_> {
+impl BarUserAccess<'_, '_> {
     /// Tear down the BAR1 mapping.
     pub(crate) fn release(mut self, mm: &mut GpuMm<'_>) -> Result {
         self.unmap(mm)
@@ -168,7 +168,7 @@ pub(crate) fn try_write64(&self, value: u64, offset: usize) -> Result {
     }
 }
 
-impl Drop for BarUserAccess<'_> {
+impl Drop for BarUserAccess<'_, '_> {
     fn drop(&mut self) {
         if self.mapped.is_some() {
             kernel::pr_warn!(
@@ -186,7 +186,7 @@ fn drop(&mut self) {
 /// address space. Uses the `GpuMm`'s buddy allocator to allocate page tables
 /// and test pages as needed.
 #[cfg(CONFIG_NOVA_CORE_SELFTESTS)]
-pub(crate) fn run_self_test(
+pub(super) fn run_self_test(
     dev: &device::Device<device::Bound>,
     mm: &mut GpuMm<'_>,
     bar_user: &BarUser<'_>,
Signature · 1 line
2.53.0
author
#13 lore raw

PATCH v4 10/32 gpu: nova-core: mm: add VramBlock

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

vGPU memory slots need allocations at fixed offsets within usable VRAM. Add GpuMm::alloc_vram_range and VramBlock to own these exact allocations and return their storage to the buddy allocator on drop.

Use the buddy allocator's range validation and retain the checks for absolute physical address overflow and alignment.

---
 drivers/gpu/nova-core/mm.rs      |  2 +
 drivers/gpu/nova-core/mm/vram.rs | 75 ++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)
 create mode 100644 drivers/gpu/nova-core/mm/vram.rs
drivers/gpu/nova-core/mm.rs +2 -0
index 8a934c55169f..016d99608aeb 100644
@@ -67,6 +67,8 @@ macro_rules! impl_pfn_bounded {
 mod regs;
 pub(super) mod tlb;
 pub(super) mod vmm;
+#[expect(dead_code)]
+pub(crate) mod vram;
 
 /// GPU Memory Manager - owns all core MM components.
 ///
drivers/gpu/nova-core/mm/vram.rs new file +75 -0

75 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#14 lore raw

PATCH v4 11/32 gpu: nova-core: mm: add VramRegion

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

BAR mappings do not necessarily cover an entire VRAM block. A region layer lets a mapping select a byte range while keeping the underlying allocation alive.

For vGPU, communication buffers occupy only part of the management heap and need a BAR mapping of that range.

Add VramRegion to describe ranges within a shared VramBlock and support bounds-checked subregions.

---
 drivers/gpu/nova-core/mm/vram.rs | 56 ++++++++++++++++++++++++++++++++
 1 file changed, 56 insertions(+)
drivers/gpu/nova-core/mm/vram.rs +56 -0
index b6cc177825d7..bed90a3de8bd 100644
@@ -34,6 +34,62 @@ impl VramBlock {
     pub(crate) const fn address(&self) -> u64 {
         self.address
     }
+
+    /// Creates a byte range relative to this allocation, retaining its backing storage.
+    pub(crate) fn region(self: &Arc<Self>, range: Range<u64>) -> Result<VramRegion> {
+        VramRegion::new(self.clone(), range)
+    }
+}
+
+/// A byte range that keeps its VRAM allocation alive.
+#[derive(Clone)]
+pub(crate) struct VramRegion {
+    backing: Arc<VramBlock>,
+    address: u64,
+    size: u64,
+}
+
+impl VramRegion {
+    fn new(backing: Arc<VramBlock>, range: Range<u64>) -> Result<Self> {
+        if range.start >= range.end || range.end > backing.size {
+            return Err(EINVAL);
+        }
+
+        let size = range.end - range.start;
+        let address = backing.address.checked_add(range.start).ok_or(EOVERFLOW)?;
+        backing.address.checked_add(range.end).ok_or(EOVERFLOW)?;
+
+        Ok(Self {
+            backing,
+            address,
+            size,
+        })
+    }
+
+    pub(crate) const fn address(&self) -> u64 {
+        self.address
+    }
+
+    pub(crate) const fn size(&self) -> u64 {
+        self.size
+    }
+
+    /// Creates a nonempty subregion relative to this view's start.
+    pub(crate) fn subregion(&self, range: Range<u64>) -> Result<Self> {
+        if range.start >= range.end || range.end > self.size {
+            return Err(EINVAL);
+        }
+
+        let size = range.end - range.start;
+        let address = self.address.checked_add(range.start).ok_or(EOVERFLOW)?;
+        address.checked_add(size).ok_or(EOVERFLOW)?;
+
+        Ok(Self {
+            backing: self.backing.clone(),
+            address,
+            size,
+        })
+    }
 }
 
 impl GpuMm<'_> {
Signature · 1 line
2.53.0
author
#15 lore raw

PATCH v4 12/32 gpu: nova-core: mm: add BarMapping

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

vGPU communication buffers need CPU access to regions within their VramBlocks. Add BarMapping to map these regions through BAR1, including regions that start or end within a page.

Retain the backing region and borrow BarUser and the MM mutex for the mapping's lifetime. Restrict CPU accesses to the region's byte range, and unmap on drop using the retained MM dependency.

Provide an explicit unmap operation so owners can observe errors before releasing resources. Keep the mapping and backing region on failure, remember the first error and reject subsequent accesses. Owners must retain failed mappings until device teardown; drop does not retry an unmap that already failed.

---
 drivers/gpu/nova-core/mm/bar_user.rs | 145 +++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)
drivers/gpu/nova-core/mm/bar_user.rs +145 -0
index 7f389aea7ca3..d4364f94b13e 100644
@@ -7,6 +7,10 @@
     io::Io,
     new_mutex,
     prelude::*,
+    ptr::{
+        Alignable,
+        Alignment, //
+    },
     sync::Mutex, //
 };
 
@@ -18,6 +22,7 @@
             MappedRange,
             Vmm, //
         },
+        vram::VramRegion,
         GpuMm,
         Pfn,
         Vfn,
@@ -180,6 +185,146 @@ fn drop(&mut self) {
     }
 }
 
+/// A BAR mapping that retains its backing VRAM region.
+#[must_use]
+pub(crate) struct BarMapping<'map, 'gpu> {
+    access: BarUserAccess<'map, 'gpu>,
+    mm: &'map Mutex<GpuMm<'gpu>>,
+    region: VramRegion,
+    region_offset: usize,
+    region_size: usize,
+    unmap_error: Option<Error>,
+}
+
+#[expect(dead_code)]
+impl<'map, 'gpu> BarMapping<'map, 'gpu> {
+    /// Maps the containing pages while restricting CPU access to the requested byte range.
+    pub(crate) fn new(
+        bar_user: &'map BarUser<'gpu>,
+        mm: &'map Mutex<GpuMm<'gpu>>,
+        region: VramRegion,
+        writable: bool,
+    ) -> Result<Self> {
+        let page_size: u64 = PAGE_SIZE.into_safe_cast();
+        let page_align = Alignment::new::<PAGE_SIZE>();
+
+        let region_start = region.address();
+        let region_end = region_start.checked_add(region.size()).ok_or(EOVERFLOW)?;
+
+        let map_start = region_start.align_down(page_align);
+        let map_end = region_end.align_up(page_align).ok_or(EOVERFLOW)?;
+        let map_size = map_end.checked_sub(map_start).ok_or(EINVAL)?;
+        let num_pages = usize::try_from(map_size / page_size).map_err(|_| EOVERFLOW)?;
+        if num_pages == 0 {
+            return Err(EINVAL);
+        }
+
+        let region_offset = usize::try_from(region_start - map_start).map_err(|_| EOVERFLOW)?;
+        let region_size = usize::try_from(region.size()).map_err(|_| EOVERFLOW)?;
+
+        let mut pfns = KVec::with_capacity(num_pages, GFP_KERNEL)?;
+        for page in 0..num_pages {
+            let page = u64::try_from(page).map_err(|_| EOVERFLOW)?;
+            let byte_offset = page.checked_mul(page_size).ok_or(EOVERFLOW)?;
+            let address = map_start.checked_add(byte_offset).ok_or(EOVERFLOW)?;
+            pfns.push(Pfn::from(VramAddress::from_raw(address)), GFP_KERNEL)?;
+        }
+
+        let access = bar_user.map(&mut mm.lock(), &pfns, writable)?;
+
+        Ok(Self {
+            access,
+            mm,
+            region,
+            region_offset,
+            region_size,
+            unmap_error: None,
+        })
+    }
+
+    pub(crate) fn region(&self) -> &VramRegion {
+        &self.region
+    }
+
+    pub(crate) fn gpu_va_addr(&self) -> Result<u64> {
+        self.access()?
+            .base()
+            .into_raw()
+            .checked_add(u64::try_from(self.region_offset).map_err(|_| EOVERFLOW)?)
+            .ok_or(EOVERFLOW)
+    }
+
+    pub(crate) const fn size(&self) -> usize {
+        self.region_size
+    }
+
+    fn access(&self) -> Result<&BarUserAccess<'map, 'gpu>> {
+        if self.access.mapped.is_none() {
+            return Err(ENODEV);
+        }
+        if let Some(error) = self.unmap_error {
+            return Err(error);
+        }
+        Ok(&self.access)
+    }
+
+    fn access_offset(&self, offset: usize, width: usize) -> Result<usize> {
+        let region_end = offset.checked_add(width).ok_or(EOVERFLOW)?;
+        if region_end > self.region_size {
+            return Err(EINVAL);
+        }
+
+        let access_offset = self.region_offset.checked_add(offset).ok_or(EOVERFLOW)?;
+        if !access_offset.is_multiple_of(width) {
+            return Err(EINVAL);
+        }
+
+        Ok(access_offset)
+    }
+
+    pub(crate) fn try_read32(&self, offset: usize) -> Result<u32> {
+        self.access()?
+            .try_read32(self.access_offset(offset, size_of::<u32>())?)
+    }
+
+    pub(crate) fn try_write32(&self, value: u32, offset: usize) -> Result {
+        self.access()?
+            .try_write32(value, self.access_offset(offset, size_of::<u32>())?)
+    }
+
+    pub(crate) fn try_write64(&self, value: u64, offset: usize) -> Result {
+        self.access()?
+            .try_write64(value, self.access_offset(offset, size_of::<u64>())?)
+    }
+
+    /// Unmap while retaining the mapping and its backing storage on failure.
+    ///
+    /// The first error is retained; later calls do not retry the operation.
+    pub(crate) fn unmap(&mut self) -> Result {
+        if let Some(error) = self.unmap_error {
+            return Err(error);
+        }
+        let result = self.access.unmap(&mut self.mm.lock());
+        if let Err(error) = result {
+            self.unmap_error = Some(error);
+        }
+        result
+    }
+}
+
+impl Drop for BarMapping<'_, '_> {
+    /// Drop unmaps the region and may sleep. Call [`Self::unmap`] to observe errors;
+    /// on failure the owner must retain this object until device teardown.
+    fn drop(&mut self) {
+        if self.unmap_error.is_some() {
+            return;
+        }
+        if let Err(error) = self.unmap() {
+            kernel::pr_err!("failed to unmap BAR1 region: {:?}\n", error);
+        }
+    }
+}
+
 /// Run MM subsystem self-tests during probe.
 ///
 /// Tests page table infrastructure and `BAR1` MMIO access using the `BAR1`
Signature · 1 line
2.53.0
author
#16 lore raw

PATCH v4 13/32 gpu: nova-core: gsp: add synchronous GMC transactions

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

vGPU management queries need an owned response payload after the receive queue releases its slot. Sending and receiving must share one queue guard so another receiver cannot consume the reply.

Match the response flag, command ID and request sequence, then copy the payload and return the raw firmware status. Support the default receive timeout and a caller-selected timeout without converting firmware rejection into a transport error.

Share the receive deadline and retry loop in CmdqInner with the existing GMC boot response wait. Preserve each caller's matching and status policy, consume every valid element once even when its callback fails, and keep a single deadline after sending. Document unmatched-message consumption and distinguish a zero response payload capacity from a command that sends no reply.

---
 Documentation/gpu/nova/core/interrupts.rst |  19 ++-
 drivers/gpu/nova-core/gsp/cmdq.rs          |  28 +++-
 drivers/gpu/nova-core/gsp/cmdq/gmc.rs      | 162 ++++++++++++++++++++-
 drivers/gpu/nova-core/gsp/fw.rs            |  26 +++-
 4 files changed, 215 insertions(+), 20 deletions(-)
Documentation/gpu/nova/core/interrupts.rst +12 -7
index 3fcc886e22c1..7f61e5d6334e 100644
@@ -580,9 +580,12 @@ command reply or an unsolicited event, and the two differ in the function code.
   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.
-* A GMC message carries a command id in place of a function code. Only the
-  ``GSP_INIT`` wait during boot claims GMC messages, so one that arrives
-  anywhere else is logged at warning level and dropped.
+* A GMC message carries a command id in place of a function code. The
+  ``GSP_INIT`` wait and synchronous GMC transactions claim responses with the
+  expected command id and sequence number. These waits consume interleaved RPC
+  messages as events. Unmatched GMC messages are handled by the wait's callback
+  or logged and consumed; a queue drain with no waiting caller logs unclaimed
+  responses and unknown commands at warning level and drops them.
 
 A command's reply must carry the RPC sequence number that nova-core wrote into
 the command, as well as its function code. A message with the awaited function
@@ -602,10 +605,12 @@ element stays at the queue head, and every later receive fails on it with
 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 that no other caller consumes the message
-that it waits for.
+arrives before the reply and keeps waiting. One receive deadline applies to
+all messages in the wait. The default timeout is 5 seconds; GMC transactions
+can specify another timeout. For send-and-wait operations the receive deadline
+starts after sending, so it does not bound waiting for the mutex or for
+command-queue space. The thread holds the mutex from sending through receiving,
+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.
drivers/gpu/nova-core/gsp/cmdq.rs +23 -5
index 4b25bd58ab93..70de84079d20 100644
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 
 pub(super) mod gmc;
+
 // No RPC command is sent since the switch to the r000 firmware: GSP_INIT and GSP_SUSPEND replaced
 // the RPC commands that the driver used during boot and shutdown. The RPC send and reply paths
 // stay for the commands that later series add, and are built as dead code until then.
@@ -67,6 +68,9 @@
 
 use super::regs;
 
+#[expect(unused_imports)]
+pub(crate) use gmc::GmcResponse;
+
 /// Marker type representing the absence of a reply for a command. Commands using this as their
 /// reply type are sent using [`Cmdq::send_rpc_command_no_wait`].
 pub(crate) struct NoReply;
@@ -722,15 +726,29 @@ fn consume_element<R>(
     /// - Any error returned by `f` with the exception of `ENOMSG`.
     fn await_msg_with<R>(&mut self, f: impl Fn(GspParsedMessage<'_>) -> Result<R>) -> Result<R> {
         let deadline = Instant::<Monotonic>::now() + Cmdq::RECEIVE_TIMEOUT;
+        self.await_msg_with_deadline(deadline, |message| match f(message) {
+            Ok(value) => Ok(Some(value)),
+            Err(ENOMSG) => Ok(None),
+            Err(error) => Err(error),
+        })
+    }
+
+    /// Waits under the queue guard until `f` claims a message, using one receive deadline.
+    ///
+    /// Every valid element is consumed even if `f` fails. Unclaimed messages do not extend the
+    /// deadline. Framing errors stall the queue and are returned without consuming the element.
+    fn await_msg_with_deadline<R>(
+        &mut self,
+        deadline: Instant<Monotonic>,
+        mut f: impl FnMut(GspParsedMessage<'_>) -> Result<Option<R>>,
+    ) -> Result<R> {
         loop {
             let remaining = deadline - Instant::<Monotonic>::now();
             if remaining.is_negative() {
-                break Err(ETIMEDOUT);
+                return Err(ETIMEDOUT);
             }
-            match self.consume_element(remaining, &f)? {
-                Ok(m) => break Ok(m),
-                Err(ENOMSG) => continue,
-                Err(e) => break Err(e),
+            if let Some(value) = self.consume_element(remaining, &mut f)?? {
+                return Ok(value);
             }
         }
     }
drivers/gpu/nova-core/gsp/cmdq/gmc.rs +157 -5

187 lines not on this page · raw message

drivers/gpu/nova-core/gsp/fw.rs +23 -3
index dfe7aafcf31f..c75488e7a787 100644
@@ -834,8 +834,8 @@ impl GmcApiHeader {
     /// Creates the header of a request for `command`, sent with sequence number `sequence` and
     /// followed by `payload_size` bytes of payload.
     ///
-    /// `max_reply_size` is the largest reply the sender accepts, and zero for a request that
-    /// GSP-RM does not answer.
+    /// `max_reply_size` is the response payload capacity in bytes. Zero permits a status-only
+    /// response; whether GSP-RM sends a reply is determined by the command protocol.
     ///
     /// # Errors
     ///
@@ -845,9 +845,22 @@ pub(crate) fn init(
         sequence: u64,
         payload_size: usize,
         max_reply_size: u32,
+    ) -> impl Init<Self, Error> {
+        // CAST: `GmcCommandId` is `repr(u32)`, so this cast is lossless.
+        Self::init_raw(command as u32, sequence, payload_size, max_reply_size)
+    }
+
+    /// Creates a request header for a runtime command ID, masking off command flags.
+    ///
+    /// Returns `EOVERFLOW` if `payload_size` does not fit its 32-bit field.
+    pub(super) fn init_raw(
+        command: u32,
+        sequence: u64,
+        payload_size: usize,
+        max_reply_size: u32,
     ) -> impl Init<Self, Error> {
         let init_inner = try_init!(bindings::GMCAPI_HEADER {
-            command: GmcCommandWord::zeroed().with_id(command).into(),
+            command: command & 0x00ff_ffff,
             size: payload_size.try_into().map_err(|_| EOVERFLOW)?,
             sequence,
             __bindgen_anon_1: bindings::GMCAPI_HEADER__bindgen_ty_1 {
@@ -863,6 +876,13 @@ pub(crate) fn init(
         })
     }
 
+    /// Matches the response flag, low 24 command-ID bits, and full request sequence number.
+    pub(crate) fn is_response_to(&self, command_id: u32, sequence: u32) -> bool {
+        self.command().is_response()
+            && GmcCommand::from(self.command()).0 == (command_id & 0x00ff_ffff)
+            && self.sequence() == u64::from(sequence)
+    }
+
     /// Returns the `command` word of the header.
     pub(crate) fn command(&self) -> GmcCommandWord {
         GmcCommandWord::from(self.inner.command)
Signature · 1 line
2.53.0
author
#17 lore raw

PATCH v4 14/32 gpu: nova-core: gsp: wait for GMC completion events

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

GSP plugin shutdown completes through a separate event carrying the GFID. Releasing the queue guard after sending would let another receiver consume that completion.

Keep the guard from sending through the matching event and use the shared Inner receive deadline. Offer GMC events to the completion predicate, pass unmatched events to the caller's handler, and consume interleaved responses and RPC messages. Represent completion with a value and all continuing cases with None.

Propagate callback errors after consuming the current valid element. Document callback non-reentrancy and that the receive timeout starts after sending, excluding mutex acquisition and command-queue space waits.

---
 Documentation/gpu/nova/core/interrupts.rst |  9 +--
 drivers/gpu/nova-core/gsp/cmdq/gmc.rs      | 65 ++++++++++++++++++++++
 drivers/gpu/nova-core/gsp/fw.rs            |  9 +++
 3 files changed, 79 insertions(+), 4 deletions(-)
Documentation/gpu/nova/core/interrupts.rst +5 -4
index 7f61e5d6334e..d686d8004aea 100644
@@ -582,7 +582,8 @@ command reply or an unsolicited event, and the two differ in the function code.
   arrival with its sequence number, function code, and length.
 * A GMC message carries a command id in place of a function code. The
   ``GSP_INIT`` wait and synchronous GMC transactions claim responses with the
-  expected command id and sequence number. These waits consume interleaved RPC
+  expected command id and sequence number. A GMC completion-event wait uses its
+  caller's predicate to select an event. These waits consume interleaved RPC
   messages as events. Unmatched GMC messages are handled by the wait's callback
   or logged and consumed; a queue drain with no waiting caller logs unclaimed
   responses and unknown commands at warning level and drops them.
@@ -607,9 +608,9 @@ 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 receive deadline applies to
 all messages in the wait. The default timeout is 5 seconds; GMC transactions
-can specify another timeout. For send-and-wait operations the receive deadline
-starts after sending, so it does not bound waiting for the mutex or for
-command-queue space. The thread holds the mutex from sending through receiving,
+and completion-event waits can specify another timeout. For send-and-wait
+operations the receive deadline starts after sending, so it does not bound
+waiting for the mutex or for command-queue space. The thread holds the mutex from sending through receiving,
 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
drivers/gpu/nova-core/gsp/cmdq/gmc.rs +65 -0

71 lines not on this page · raw message

drivers/gpu/nova-core/gsp/fw.rs +9 -0
index c75488e7a787..ce4a7c45caf2 100644
@@ -905,6 +905,15 @@ pub(crate) fn payload_size(&self) -> usize {
         num::u32_as_usize(self.inner.size)
     }
 
+    /// Returns the raw `max_resp_or_status` word carried by a GMC header.
+    ///
+    /// Its meaning for an event is defined by that event's command.
+    pub(crate) fn raw_status_word(&self) -> u32 {
+        // SAFETY: Both union views contain a `u32` at the same offset, so every bit pattern is
+        // valid regardless of whether the header represents a request, response, or event.
+        unsafe { self.inner.__bindgen_anon_1.request.max_response_size }
+    }
+
     /// Returns the `NV_STATUS` carried by a response to a command.
     ///
     /// Returns `Some` with the `NV_STATUS` code if the `response` flag of [`Self::command`] is set;
Signature · 1 line
2.53.0
author
#18 lore raw

PATCH v4 15/32 gpu: nova-core: vgpu: add r000 plugin bindings

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The GSP plugin communication area and RPC protocol are defined by the r000 firmware interface. Keeping local copies of that ABI risks letting the vGPU manager drift from the firmware layout.

Extend the existing r000 binding set with the nova-core subset of dev_vgpu_gsp_shared.h. The bindings expose the control and response region types, message IDs, and firmware-defined region sizes. Keep the raw declarations private and expose only the symbols needed by the GSP plugin communication layer.

---
 .../gpu/nova-core/gsp/fw/r000_00/bindings.rs  | 173 ++++++++++++++++++
 1 file changed, 173 insertions(+)
drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs +173 -0
index fa17d3e2edf6..3cc718bb2ab8 100644
@@ -1,4 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 
 #[repr(C)]
 #[derive(Default)]
@@ -850,3 +851,175 @@ fn default() -> Self {
         }
     }
 }
+pub const GSP_PLUGIN_BOOTLOADED: u32 = 1315261039;
+pub const VGPU_CPU_GSP_CTRL_BUFF_VERSION: u32 = 2;
+pub const VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE: u32 = 4096;
+pub const VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE: u32 = 4096;
+pub const VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE: u32 = 4096;
+pub const VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE: u32 = 2097152;
+pub const VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE: u32 = 4096;
+pub const VGPU_CPU_GSP_INIT_TASK_LOG_BUFF_REGION_SIZE: u32 = 131072;
+pub const VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE: u32 = 262144;
+pub const VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE: u32 = 65536;
+pub const VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE: u32 = 65536;
+pub const VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE: u32 = 2637824;
+pub type VGPU_CPU_GSP_BOOL = u32_;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_VGX_VERSION {
+    pub major_number: u32_,
+    pub minor_number: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_GUEST_INFO {
+    pub vgx_version: VGPU_CPU_GSP_VGX_VERSION,
+    pub guest_driver_version_buffer_length: u32_,
+    pub guest_version_buffer_length: u32_,
+    pub guest_title_buffer_length: u32_,
+    pub guest_changelist_number: u32_,
+    pub guest_driver_version_buffer: [ffi::c_char; 256usize],
+    pub guest_version_buffer: [ffi::c_char; 256usize],
+    pub guest_title_buffer: [ffi::c_char; 256usize],
+    pub guest_branch_buffer: [ffi::c_char; 256usize],
+}
+impl Default for VGPU_CPU_GSP_GUEST_INFO {
+    fn default() -> Self {
+        let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union VGPU_CPU_GSP_CTRL_BUFF_REGION {
+    pub buf: [u8_; 4096usize],
+    pub __bindgen_anon_1: VGPU_CPU_GSP_CTRL_BUFF_REGION__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_CTRL_BUFF_REGION__bindgen_ty_1 {
+    pub version: u32_,
+    pub message_type: u32_,
+    pub message_seq_num: u32_,
+    pub __bindgen_padding_0: [u8; 4usize],
+    pub response_buff_offset: u64_,
+    pub message_buff_offset: u64_,
+    pub migration_buff_offset: u64_,
+    pub error_buff_offset: u64_,
+    pub guest_rpc_trace_buff_offset: u64_,
+    pub migration_buf_cpu_access_offset: u32_,
+    pub is_migration_in_progress: u8_,
+    pub __bindgen_padding_1: [u8; 3usize],
+    pub error_buff_cpu_get_idx: u32_,
+    pub guest_rpc_trace_buff_cpu_get_idx: u32_,
+    pub attached_vgpu_count: u32_,
+    pub is_gr_init_done: u8_,
+    pub __bindgen_padding_2: [u8; 3usize],
+    pub host_info: [VGPU_CPU_GSP_CTRL_BUFF_REGION__bindgen_ty_1__bindgen_ty_1; 16usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_CTRL_BUFF_REGION__bindgen_ty_1__bindgen_ty_1 {
+    pub vgpu_type_id: u32_,
+    pub host_gpu_pci_id: u32_,
+    pub pci_dev_id: u32_,
+    pub vgpu_uuid: [u8_; 16usize],
+}
+impl Default for VGPU_CPU_GSP_CTRL_BUFF_REGION {
+    fn default() -> Self {
+        let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_VERSION_NEGOTIATION: MESSAGE = 1;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_SETUP_CONFIG_PARAMS_AND_INIT: MESSAGE = 2;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_RESET: MESSAGE = 3;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_STOP_WORK: MESSAGE = 4;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_CANCEL_STOP: MESSAGE = 5;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_SAVE_STATE: MESSAGE = 6;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_CANCEL_SAVE: MESSAGE = 7;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_RESTORE_STATE: MESSAGE = 8;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_RESTORE_DEFERRED_STATE: MESSAGE = 9;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MIGRATION_RESUME_WORK: MESSAGE = 10;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_CONSOLE_VNC_STATE: MESSAGE = 11;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_VF_BAR0_REG_ACCESS: MESSAGE = 12;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_UPDATE_BME_STATE: MESSAGE = 13;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_RESET_MIGRATION_BUFFER_PTR: MESSAGE = 14;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_RELEASE_CLIENT_DATABASE: MESSAGE = 15;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_CHECK_IS_ALIVE: MESSAGE = 16;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_SEND_STATIC_INFO: MESSAGE = 17;
+pub const MESSAGE_NV_VGPU_CPU_RPC_MSG_MAX: MESSAGE = 18;
+pub type MESSAGE = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_DISPLAYLESS_SURFACE {
+    pub sequence_update_start: u64_,
+    pub sequence_update_end: u64_,
+    pub effective_fb_page_size: u32_,
+    pub rect_width: u32_,
+    pub rect_height: u32_,
+    pub surface_width: u32_,
+    pub surface_height: u32_,
+    pub surface_size: u32_,
+    pub surface_offset: u32_,
+    pub surface_format: u32_,
+    pub surface_kind: u32_,
+    pub surface_pitch: u32_,
+    pub surface_type: u32_,
+    pub surface_block_height: u8_,
+    pub __bindgen_padding_0: [u8; 3usize],
+    pub is_blanking_enabled: VGPU_CPU_GSP_BOOL,
+    pub is_flip_pending: VGPU_CPU_GSP_BOOL,
+    pub is_free_pending: VGPU_CPU_GSP_BOOL,
+    pub is_memory_blocklinear: VGPU_CPU_GSP_BOOL,
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union VGPU_CPU_GSP_RESPONSE_BUFF_REGION {
+    pub buf: [u8_; 4096usize],
+    pub __bindgen_anon_1: VGPU_CPU_GSP_RESPONSE_BUFF_REGION__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct VGPU_CPU_GSP_RESPONSE_BUFF_REGION__bindgen_ty_1 {
+    pub message_seq_num_received: u32_,
+    pub message_seq_num_processed: u32_,
+    pub result_code: u32_,
+    pub guest_rpc_version: u32_,
+    pub migration_buf_gsp_access_offset: u32_,
+    pub migration_state_save_complete: u32_,
+    pub is_migration_allowed: VGPU_CPU_GSP_BOOL,
+    pub __bindgen_padding_0: [u8; 4usize],
+    pub surface: [VGPU_CPU_GSP_DISPLAYLESS_SURFACE; 4usize],
+    pub error_buff_gsp_put_idx: u32_,
+    pub grid_license_state: u32_,
+    pub guest_os_type: u32_,
+    pub frl_config: u32_,
+    pub guest_info: VGPU_CPU_GSP_GUEST_INFO,
+    pub is_guest_info_populated: VGPU_CPU_GSP_BOOL,
+    pub guest_rpc_trace_buff_gsp_put_idx: u32_,
+}
+impl Default for VGPU_CPU_GSP_RESPONSE_BUFF_REGION__bindgen_ty_1 {
+    fn default() -> Self {
+        let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
+impl Default for VGPU_CPU_GSP_RESPONSE_BUFF_REGION {
+    fn default() -> Self {
+        let mut s = ::core::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            ::core::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+            s.assume_init()
+        }
+    }
+}
Signature · 1 line
2.53.0
author
#19 lore raw

PATCH v4 16/32 gpu: nova-core: vgpu: add VRAM slot allocator

In reply to Alok Kumar

A vGPU type specifies the guest VRAM and GSP plugin management-heap sizes for each instance. Allocating these regions independently fragments VRAM and does not preserve a stable layout for the vGPU type.

Add a standalone slot allocator that reserves one exact VRAM range and tracks assignments in a shared, mutex-protected bitmap. Validate the vGPU type's layout and reject an allocation request whose layout differs from the active pool.

Lay out all guest VRAM slots first and all management-heap slots second, pairing them by bitmap index. Require the guest VRAM stride and pool base to satisfy the required VMMU-segment alignment.

Return an owned slot that clears its bitmap entry on drop. Keep the bitmap alive while slots exist and release the entry if constructing the VRAM regions fails. Require users to stop device accesses and remove mappings before dropping a slot.

---
 drivers/gpu/nova-core/vgpu.rs      |   1 +
 drivers/gpu/nova-core/vgpu/vram.rs | 181 +++++++++++++++++++++++++++++
 2 files changed, 182 insertions(+)
 create mode 100644 drivers/gpu/nova-core/vgpu/vram.rs
drivers/gpu/nova-core/vgpu.rs +1 -0
index 5cd82adeb0f8..1402e37541b7 100644
@@ -21,6 +21,7 @@
 };
 
 mod hal;
+mod vram;
 
 /// vGPU state detected during GPU construction.
 #[derive(Debug, Clone, Copy)]
drivers/gpu/nova-core/vgpu/vram.rs new file +181 -0

181 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#20 lore raw

PATCH v4 17/32 gpu: nova-core: gsp: factor out NVKV payload conversion

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Both GSP initialization and vGPU property queries decode their replies as NVKV streams.

Move the byte-to-word conversion helper into the NVKV module and make that module accessible to the vGPU firmware interface.

No functional change is intended.

---
 drivers/gpu/nova-core/gsp.rs              |  2 +-
 drivers/gpu/nova-core/gsp/commands/gmc.rs | 12 ++-------
 drivers/gpu/nova-core/gsp/nvkv.rs         | 31 ++++++++++++++++++++++-
 3 files changed, 33 insertions(+), 12 deletions(-)
drivers/gpu/nova-core/gsp.rs +1 -1
index cac123cbeac4..8424aa3937e7 100644
@@ -28,7 +28,7 @@
 pub(crate) mod cmdq;
 pub(crate) mod commands;
 mod fw;
-mod nvkv;
+pub(crate) mod nvkv;
 mod regs;
 
 pub(crate) use fw::{
drivers/gpu/nova-core/gsp/commands/gmc.rs +2 -10
index 6f88c99349ac..33aea614cf72 100644
@@ -29,6 +29,7 @@
             GmcCommandId, //
         },
         nvkv::{
+            nvkv_words,
             Decoder,
             Encodable,
             Encoder,
@@ -151,16 +152,7 @@ impl MessageFromGsp for GspStaticInfo {
     fn read(payload: &mut SBufferIter<core::array::IntoIter<&[u8], 2>>) -> Result<Self> {
         let bytes = payload.flush_into_kvec(GFP_KERNEL)?;
 
-        let chunks = bytes.chunks_exact(size_of::<u64>());
-        if !chunks.remainder().is_empty() {
-            return Err(EINVAL);
-        }
-        let mut words = KVVec::with_capacity(chunks.len(), GFP_KERNEL)?;
-        for chunk in chunks {
-            // `chunks_exact` yields slices of exactly `size_of::<u64>()` bytes.
-            let word = chunk.try_into().map_err(|_| EINVAL)?;
-            words.push(u64::from_le_bytes(word), GFP_KERNEL)?;
-        }
+        let words = nvkv_words(&bytes, &[])?;
 
         let decoder = Decoder::new(&words, UnknownKeyPolicy::Ignore);
         let mut schema = KBox::init(GspInitResponseSchema::init(), GFP_KERNEL)?;
drivers/gpu/nova-core/gsp/nvkv.rs +30 -1
index f81a9e60d89c..5887a61af02a 100644
@@ -20,11 +20,40 @@
 use kernel::{
     alloc::ArrayVec,
     bitfield,
-    num::Bounded, //
+    num::Bounded,
+    prelude::*, //
 };
 
 use crate::bounded_enum;
 
+use crate::sbuffer::SBufferIter;
+
+/// Joins the two halves of a wrapped payload into the `u64` words an NVKV stream is made of.
+///
+/// # Errors
+///
+/// - `EINVAL` if the combined length is not a whole number of words.
+/// - `ENOMEM` if the buffer cannot be allocated.
+pub(crate) fn nvkv_words(payload_0: &[u8], payload_1: &[u8]) -> Result<KVVec<u64>> {
+    const WORD_SIZE: usize = size_of::<u64>();
+
+    // Each byte slice is at most `isize::MAX` bytes, so their sum fits in `usize`.
+    let len = payload_0.len() + payload_1.len();
+    if len % WORD_SIZE != 0 {
+        return Err(EINVAL);
+    }
+
+    let mut words = KVVec::with_capacity(len / WORD_SIZE, GFP_KERNEL)?;
+    let mut bytes = SBufferIter::new_reader([payload_0, payload_1]);
+    for _ in 0..len / WORD_SIZE {
+        let mut word = [0u8; WORD_SIZE];
+        bytes.read_exact(&mut word)?;
+        words.push(u64::from_le_bytes(word), GFP_KERNEL)?;
+    }
+
+    Ok(words)
+}
+
 mod encode;
 pub(crate) use encode::*;
 
Signature · 1 line
2.53.0
author
#21 lore raw

PATCH v4 18/32 gpu: nova-core: vgpu: query VF assignments and properties

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Creating an instance requires its VF assignment and the corresponding vGPU properties before reserving resources.

Call Cmdq's synchronous GMC interface with each query's command ID, request payload and receive capacity. Check the matching reply's firmware status before decoding it, and report the command and raw NV_STATUS when firmware rejects the request.

Decode the VF assignment and NVKV property replies at their query entry points. Validate the returned type ID and nonzero instance limit, and initialize the decoded properties on the heap. Keep the raw queue interface available to firmware-control clients.

---
 drivers/gpu/nova-core/gsp.rs              |  1 +
 drivers/gpu/nova-core/gsp/cmdq/gmc.rs     |  3 -
 drivers/gpu/nova-core/gsp/fw.rs           |  2 +-
 drivers/gpu/nova-core/vgpu.rs             |  2 +
 drivers/gpu/nova-core/vgpu/commands.rs    | 92 ++++++++++++++++++++++
 drivers/gpu/nova-core/vgpu/fw.rs          | 17 ++++
 drivers/gpu/nova-core/vgpu/fw/commands.rs | 95 +++++++++++++++++++++++
 7 files changed, 208 insertions(+), 4 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/commands.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/fw.rs
 create mode 100644 drivers/gpu/nova-core/vgpu/fw/commands.rs
drivers/gpu/nova-core/gsp.rs +1 -0
index 8424aa3937e7..7e10a1deeafe 100644
@@ -32,6 +32,7 @@
 mod regs;
 
 pub(crate) use fw::{
+    r000_00 as bindings,
     GspFmcBootParams,
     GspFwWprMeta,
     LibosMemoryRegionInitArgument,
drivers/gpu/nova-core/gsp/cmdq/gmc.rs +0 -3
index c5ae5943c8b8..d41d5e533cf7 100644
@@ -302,10 +302,8 @@ pub(super) fn log(&self, dev: &device::Device) {
 /// Response from a GMC API command.
 pub(crate) struct GmcResponse {
     /// Response status (`NV_STATUS` code). Zero means success.
-    #[expect(dead_code)]
     pub(crate) status: u32,
     /// Response payload copied out of the message queue.
-    #[expect(dead_code)]
     pub(crate) payload: KVec<u8>,
 }
 
@@ -356,7 +354,6 @@ impl Cmdq<'_> {
     /// # Errors
     ///
     /// Returns the same errors as [`Self::send_gmc_and_receive_timeout`].
-    #[expect(dead_code)]
     pub(crate) fn send_gmc_and_receive(
         &self,
         command_id: u32,
drivers/gpu/nova-core/gsp/fw.rs +1 -1
index ce4a7c45caf2..94ecf35ac04b 100644
@@ -2,7 +2,7 @@
 // SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 
 pub(crate) mod commands;
-mod r000_00;
+pub(crate) mod r000_00;
 
 // Alias to avoid repeating the version number with every use.
 use r000_00 as bindings;
drivers/gpu/nova-core/vgpu.rs +2 -0
index 1402e37541b7..d910445dd2b0 100644
@@ -20,6 +20,8 @@
     gsp::commands::FifoEngineList, //
 };
 
+mod commands;
+mod fw;
 mod hal;
 mod vram;
 
drivers/gpu/nova-core/vgpu/commands.rs new file +92 -0

92 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/fw.rs new file +17 -0
new file mode 100644
index 000000000000..23097cb13121
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! vGPU firmware interface.
+//!
+//! Exposes command constants and communication buffer layout types from
+//! raw firmware bindings.
+
+pub(super) mod commands;
+
+use crate::gsp::bindings;
+
+pub(super) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE;
+
+pub(super) const GMCAPI_CMD_QUERY_VGPU_PROPERTIES: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_VGPU_PROPERTIES;
drivers/gpu/nova-core/vgpu/fw/commands.rs new file +95 -0

95 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#22 lore raw

PATCH v4 19/32 gpu: nova-core: vgpu: add instance create/destroy

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

A vGPU instance owns the host resources reserved for a VF. Its assigned type determines the channel range and VRAM and management-heap reservation.

Add a manager-owned instance registry, reject duplicate GFID or DBDF entries and enforce the profile's instance limit. Reserve registry capacity before acquiring resources, then move the channel and VRAM reservations into the instance. Removing an instance or dropping the registry releases these host reservations through their owners.

---
 drivers/gpu/nova-core/gpu.rs           |  12 +-
 drivers/gpu/nova-core/vgpu.rs          |  24 ++-
 drivers/gpu/nova-core/vgpu/instance.rs | 218 +++++++++++++++++++++++++
 drivers/gpu/nova-core/vgpu/vram.rs     |   1 -
 4 files changed, 240 insertions(+), 15 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/instance.rs
drivers/gpu/nova-core/gpu.rs +7 -5
index 98772868979d..04cdffc3de2b 100644
@@ -59,14 +59,16 @@
     },
 };
 
-#[cfg_attr(not(CONFIG_KUNIT = "y"), expect(dead_code))]
 mod channel;
 mod hal;
 mod regs;
 
 use self::channel::TOTAL_CHANNELS;
 
-pub(crate) use self::channel::ChannelIdPool;
+pub(crate) use self::channel::{
+    ChannelIdPool,
+    ChannelIdReservation, //
+};
 
 macro_rules! define_chipset {
     ({ $($variant:ident),* $(,)* }) =>
@@ -400,7 +402,7 @@ unsafe fn new(
 #[pin_data]
 pub(crate) struct Gpu<'gpu> {
     pub(crate) spec: Spec,
-    vgpu: Option<VgpuManager<'gpu>>,
+    vgpu: Option<Pin<KBox<VgpuManager<'gpu>>>>,
     /// GSP event interrupt registration, and the enable of its subtree.
     ///
     /// Must be kept declared *before* `gsp_resources`, so that the handler is unregistered, and
@@ -542,7 +544,7 @@ pub(crate) fn new<'a>(
                 let info = &gsp_resources.boot_result.static_info;
                 match gsp_resources.vgpu_state {
                     VgpuState::Disabled => None,
-                    VgpuState::Enabled { .. } => Some(VgpuManager::new(
+                    VgpuState::Enabled { .. } => Some(KBox::pin_init(VgpuManager::new(
                         // SAFETY: `chid_pool` is initialized above at its final pinned address.
                         // The private manager and its pool borrow cannot escape this `Gpu`.
                         // Completed field drop order drops the manager before the pool; on failure,
@@ -551,7 +553,7 @@ pub(crate) fn new<'a>(
                         &info.fifo_engine_list(),
                         info.vmmu_segment_size,
                         TOTAL_CHANNELS,
-                    )),
+                    ), GFP_KERNEL)?),
                 }
             },
 
drivers/gpu/nova-core/vgpu.rs +15 -9
index d910445dd2b0..fb4f1b5f7754 100644
@@ -4,8 +4,10 @@
 
 use kernel::{
     device,
+    new_mutex,
     pci,
-    prelude::*, //
+    prelude::*,
+    sync::Mutex, //
 };
 
 use crate::{
@@ -23,6 +25,7 @@
 mod commands;
 mod fw;
 mod hal;
+mod instance;
 mod vram;
 
 /// vGPU state detected during GPU construction.
@@ -88,16 +91,17 @@ fn query_state(
     }
 }
 
+use self::instance::VgpuInstances;
+
 /// Runtime resources for an enabled vGPU boot.
+#[pin_data]
 pub(crate) struct VgpuManager<'gpu> {
-    #[expect(dead_code)]
+    #[pin]
+    instances: Mutex<VgpuInstances<'gpu>>,
     chid_pool: &'gpu ChannelIdPool,
     /// VMMU segment size in bytes, or zero if GSP-RM omitted it.
-    #[expect(dead_code)]
     vmmu_segment_size: u64,
-    #[expect(dead_code)]
     total_channels: u32,
-    #[expect(dead_code)]
     fifo_engine_list: FifoEngineList,
 }
 
@@ -108,12 +112,14 @@ pub(crate) fn new(
         fifo_engine_list: &FifoEngineList,
         vmmu_segment_size: u64,
         total_channels: u32,
-    ) -> Self {
-        Self {
+    ) -> impl PinInit<Self> + use<'gpu> {
+        let fifo_engine_list = *fifo_engine_list;
+        pin_init!(Self {
+            instances <- new_mutex!(VgpuInstances::new(), "nova-core::vgpu-instances"),
             chid_pool,
             vmmu_segment_size,
             total_channels,
-            fifo_engine_list: *fifo_engine_list,
-        }
+            fifo_engine_list,
+        })
     }
 }
drivers/gpu/nova-core/vgpu/instance.rs new file +218 -0

218 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/vram.rs +0 -1
index 65e6a945ea6f..faaeea106009 100644
@@ -116,7 +116,6 @@ pub(super) struct VgpuVramSlotAllocator {
     slot_bitmap: Arc<SlotBitmap>,
 }
 
-#[expect(dead_code)]
 impl VgpuVramSlotAllocator {
     pub(super) fn new(mm: &GpuMm<'_>, layout: VgpuVramLayout) -> Result<Self> {
         let layout = layout.validated()?;
Signature · 1 line
2.53.0
author
#23 lore raw

PATCH v4 20/32 gpu: nova-core: vgpu: encode vGPU boot requests

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The boot request describes a vGPU's identity, channel map, VRAM, plugin heap and log buffers to GSP-RM.

Add the NVKV schema and encode it from a named boot description. Pass VRAM region views so each physical address stays paired with its length until encoding, and retain the control offset relative to the plugin heap. Represent channel-map engine and index fields in their 16-bit wire domains.

Describe one guest VRAM segment and the current whole-GPU, non-SMIG resource layout. Keep separate MIG-RM heap fields zero for that supported firmware path.

---
 drivers/gpu/nova-core/gsp/nvkv.rs         |  11 ++
 drivers/gpu/nova-core/gsp/nvkv/encode.rs  |  12 ++
 drivers/gpu/nova-core/vgpu/fw/commands.rs | 168 +++++++++++++++++++++-
 3 files changed, 188 insertions(+), 3 deletions(-)
drivers/gpu/nova-core/gsp/nvkv.rs +11 -0
index 5887a61af02a..0e8e14f98059 100644
@@ -131,6 +131,17 @@ pub(crate) struct Array<T, const N: usize, const KEY_ID: KeyId> {
     vec: ArrayVec<T, N>,
 }
 
+impl<T: Default + Copy, const N: usize, const KEY_ID: KeyId> Array<T, N, KEY_ID> {
+    /// Creates an array filled from `values`.
+    ///
+    /// Fails with `EINVAL` if `values` is longer than `N`.
+    pub(crate) fn new(values: &[T]) -> Result<Self> {
+        let mut vec = ArrayVec::default();
+        vec.extend_from_slice(values)?;
+        Ok(Self { vec })
+    }
+}
+
 bitfield! {
     /// The op word that starts each NVKV operation.
     struct Op(u64) {
drivers/gpu/nova-core/gsp/nvkv/encode.rs +12 -0
index c6891c0441ff..c4578e3ecef9 100644
@@ -7,6 +7,7 @@
 use zerocopy::Immutable;
 
 use super::{
+    Array,
     Index,
     IndexedKey,
     Key,
@@ -130,6 +131,17 @@ fn encode(&self, encoder: &mut Encoder) -> Result {
     }
 }
 
+impl<T, const N: usize, const KEY_ID: KeyId> Encodable for Array<T, N, KEY_ID>
+where
+    T: Default + Copy,
+    for<'a> IndexedKey<&'a [T], KEY_ID>: Encodable,
+{
+    #[inline(always)]
+    fn encode(&self, encoder: &mut Encoder) -> Result {
+        IndexedKey::<&[T], KEY_ID>::new(Index::new::<0>(), self.vec.as_slice()).encode(encoder)
+    }
+}
+
 impl<T: Encodable> Encodable for Option<T> {
     fn encode(&self, encoder: &mut Encoder) -> Result {
         if let Some(value) = self {
drivers/gpu/nova-core/vgpu/fw/commands.rs +165 -3

188 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#24 lore raw

PATCH v4 21/32 gpu: nova-core: vgpu: add GSP plugin communication buffers

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The GSP plugin keeps its boot-ready marker, control data and logs in a communication region within the instance's management heap.

Map that region through BAR1 and expose its firmware-defined subregions. Check fixed layout totals and the control structure size at build time while retaining runtime checks against the allocated heap and mapping bounds.

Own the BarMapping so dropping the communication region unmaps it, and retain its BarUser and MM borrows for that lifetime. Expose explicit unmap to report teardown failures without consuming the owner. Provide the ready-marker access needed to boot a reused instance slot.

---
 drivers/gpu/nova-core/mm.rs                   |   1 -
 drivers/gpu/nova-core/vgpu.rs                 |   1 +
 drivers/gpu/nova-core/vgpu/fw.rs              |  15 ++
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs | 205 ++++++++++++++++++
 4 files changed, 221 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
drivers/gpu/nova-core/mm.rs +0 -1
index 016d99608aeb..d1cad5ad52fc 100644
@@ -67,7 +67,6 @@ macro_rules! impl_pfn_bounded {
 mod regs;
 pub(super) mod tlb;
 pub(super) mod vmm;
-#[expect(dead_code)]
 pub(crate) mod vram;
 
 /// GPU Memory Manager - owns all core MM components.
drivers/gpu/nova-core/vgpu.rs +1 -0
index fb4f1b5f7754..024cb13694f4 100644
@@ -24,6 +24,7 @@
 
 mod commands;
 mod fw;
+mod gsp_plugin_comm;
 mod hal;
 mod instance;
 mod vram;
drivers/gpu/nova-core/vgpu/fw.rs +15 -0
index 23097cb13121..aabe114b55c2 100644
@@ -10,6 +10,21 @@
 
 use crate::gsp::bindings;
 
+pub(super) use bindings::{
+    GSP_PLUGIN_BOOTLOADED,
+    VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE,
+    VGPU_CPU_GSP_CTRL_BUFF_REGION as RawControlRegion,
+    VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_INIT_TASK_LOG_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE, //
+};
+
 pub(super) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE;
 
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs new file +205 -0

205 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#25 lore raw

PATCH v4 22/32 gpu: nova-core: vgpu: add instance boot

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Start an allocated instance's GSP plugin with its channel map, VRAM, plugin heap and log regions. Clear the old ready marker before BOOTLOAD and wait for firmware to publish a new one.

Retain the command queue and memory dependencies needed by the instance, and map its communication buffers before boot.

---
 drivers/gpu/nova-core/gpu.rs                  |  71 +++++---
 drivers/gpu/nova-core/gsp/fw/commands.rs      |   1 -
 drivers/gpu/nova-core/vgpu.rs                 |  30 +++-
 drivers/gpu/nova-core/vgpu/commands.rs        |  21 ++-
 drivers/gpu/nova-core/vgpu/fw.rs              |   3 +
 drivers/gpu/nova-core/vgpu/fw/commands.rs     |   2 -
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs |  16 +-
 drivers/gpu/nova-core/vgpu/instance.rs        | 152 ++++++++++++++++--
 drivers/gpu/nova-core/vgpu/vram.rs            |   1 -
 9 files changed, 244 insertions(+), 53 deletions(-)
drivers/gpu/nova-core/gpu.rs +45 -26
index 04cdffc3de2b..da753bfc2df7 100644
@@ -8,6 +8,7 @@
     fmt,
     gpu::buddy::GpuBuddyParams,
     io::Io,
+    new_mutex,
     num::Bounded,
     pci,
     prelude::*,
@@ -16,6 +17,7 @@
         SizeConstants,
         SZ_4K, //
     },
+    sync::Mutex,
     uapi, //
 };
 
@@ -402,6 +404,7 @@ unsafe fn new(
 #[pin_data]
 pub(crate) struct Gpu<'gpu> {
     pub(crate) spec: Spec,
+    /// Drops before the MM, BAR1 mappings and GSP needed for instance teardown.
     vgpu: Option<Pin<KBox<VgpuManager<'gpu>>>>,
     /// GSP event interrupt registration, and the enable of its subtree.
     ///
@@ -414,7 +417,8 @@ pub(crate) struct Gpu<'gpu> {
     ///
     /// Must be kept declared *before* `gsp_resources`, so that its components are dropped while
     /// the GSP is still operational.
-    mm: GpuMm<'gpu>,
+    #[pin]
+    mm: Mutex<GpuMm<'gpu>>,
     /// BAR1 user interface for CPU access to GPU virtual memory.
     #[pin]
     bar_user: BarUser<'gpu>,
@@ -540,23 +544,6 @@ pub(crate) fn new<'a>(
                 })?,
             }),
 
-            vgpu: {
-                let info = &gsp_resources.boot_result.static_info;
-                match gsp_resources.vgpu_state {
-                    VgpuState::Disabled => None,
-                    VgpuState::Enabled { .. } => Some(KBox::pin_init(VgpuManager::new(
-                        // SAFETY: `chid_pool` is initialized above at its final pinned address.
-                        // The private manager and its pool borrow cannot escape this `Gpu`.
-                        // Completed field drop order drops the manager before the pool; on failure,
-                        // pin-init drops it before the earlier-initialized pool.
-                        unsafe { &*core::ptr::from_ref(chid_pool.as_ref().get_ref()) },
-                        &info.fifo_engine_list(),
-                        info.vmmu_segment_size,
-                        TOTAL_CHANNELS,
-                    ), GFP_KERNEL)?),
-                }
-            },
-
             _: {
                 irq::gsp::quiesce(tree_ref, &gsp_resources.gsp_falcon);
             },
@@ -602,7 +589,7 @@ pub(crate) fn new<'a>(
             },
 
             // Create GPU memory manager owning memory management resources.
-            mm: {
+            mm <- {
                 let info = gsp_resources.static_info();
                 let usable_vram = info.usable_fb_regions.first().ok_or(ENODEV)?;
                 let buddy_params = GpuBuddyParams {
@@ -611,12 +598,15 @@ pub(crate) fn new<'a>(
                     chunk_size: Alignment::new::<SZ_4K>(),
                 };
 
-                GpuMm::new(
-                    bar,
-                    gsp_resources.spec.chipset,
-                    buddy_params,
-                    VramAddress::from_raw(info.total_fb_end),
-                )?
+                new_mutex!(
+                    GpuMm::new(
+                        bar,
+                        gsp_resources.spec.chipset,
+                        buddy_params,
+                        VramAddress::from_raw(info.total_fb_end),
+                    )?,
+                    "nova-core::gpu-mm",
+                )
             },
 
             // Create BAR1 user interface for CPU access to GPU virtual memory.
@@ -632,6 +622,34 @@ pub(crate) fn new<'a>(
                     bar1,
                 )
             },
+
+            vgpu: {
+                match gsp_resources.vgpu_state {
+                    VgpuState::Disabled => None,
+                    VgpuState::Enabled { .. } => {
+                        // SAFETY: These sibling fields are initialized at their final pinned
+                        // addresses. The private manager cannot escape this `Gpu`, and is dropped
+                        // before all its dependencies, both here on failure and on normal removal.
+                        let (cmdq, bar_user, mm, chid_pool) = unsafe {
+                            (
+                                &*core::ptr::from_ref(&gsp_resources.gsp.cmdq),
+                                &*core::ptr::from_ref(bar_user.as_ref().get_ref()),
+                                &*core::ptr::from_ref(mm.as_ref().get_ref()),
+                                &*core::ptr::from_ref(chid_pool.as_ref().get_ref()),
+                            )
+                        };
+                        Some(KBox::pin_init(VgpuManager::new(
+                            dev,
+                            cmdq,
+                            bar_user,
+                            mm,
+                            chid_pool,
+                            gsp_resources.static_info(),
+                            TOTAL_CHANNELS,
+                        ), GFP_KERNEL)?)
+                    }
+                }
+            },
         })
     }
 
@@ -663,10 +681,11 @@ pub(crate) fn run_selftests(self: Pin<&mut Self>, pdev: &pci::Device<device::Bou
         let this = self.project();
         let dev = pdev.as_ref();
         let info = this.gsp_resources.static_info();
+        let mut mm = this.mm.lock();
 
         if let Err(err) = crate::mm::selftest::run(
             dev,
-            this.mm,
+            &mut mm,
             &info.usable_fb_regions,
             this.bar_user.as_ref().get_ref(),
             info.bar1_pde_base,
drivers/gpu/nova-core/gsp/fw/commands.rs +0 -1
index bdc0764bb898..08798f150ad7 100644
@@ -326,7 +326,6 @@ pub(crate) struct FifoEngineList {
 }
 
 impl FifoEngineList {
-    #[expect(dead_code)]
     pub(crate) fn gmc_ids(&self) -> &[u32] {
         // PANIC: The type invariant bounds `count` by the array capacity.
         &self.gmc_ids[..self.count]
drivers/gpu/nova-core/vgpu.rs +26 -4
index 024cb13694f4..84c4d0b3839d 100644
@@ -19,7 +19,17 @@
         ChannelIdPool,
         Chipset, //
     },
-    gsp::commands::FifoEngineList, //
+    gsp::{
+        cmdq::Cmdq,
+        commands::{
+            FifoEngineList,
+            GspStaticInfo, //
+        }, //
+    },
+    mm::{
+        bar_user::BarUser,
+        GpuMm, //
+    }, //
 };
 
 mod commands;
@@ -99,6 +109,10 @@ fn query_state(
 pub(crate) struct VgpuManager<'gpu> {
     #[pin]
     instances: Mutex<VgpuInstances<'gpu>>,
+    dev: &'gpu device::Device<device::Bound>,
+    cmdq: &'gpu Cmdq<'gpu>,
+    bar_user: &'gpu BarUser<'gpu>,
+    mm: &'gpu Mutex<GpuMm<'gpu>>,
     chid_pool: &'gpu ChannelIdPool,
     /// VMMU segment size in bytes, or zero if GSP-RM omitted it.
     vmmu_segment_size: u64,
@@ -109,14 +123,22 @@ pub(crate) struct VgpuManager<'gpu> {
 impl<'gpu> VgpuManager<'gpu> {
     /// Retains runtime parameters from a completed vGPU-enabled GSP boot.
     pub(crate) fn new(
+        dev: &'gpu device::Device<device::Bound>,
+        cmdq: &'gpu Cmdq<'gpu>,
+        bar_user: &'gpu BarUser<'gpu>,
+        mm: &'gpu Mutex<GpuMm<'gpu>>,
         chid_pool: &'gpu ChannelIdPool,
-        fifo_engine_list: &FifoEngineList,
-        vmmu_segment_size: u64,
+        info: &GspStaticInfo,
         total_channels: u32,
     ) -> impl PinInit<Self> + use<'gpu> {
-        let fifo_engine_list = *fifo_engine_list;
+        let fifo_engine_list = info.fifo_engine_list();
+        let vmmu_segment_size = info.vmmu_segment_size;
         pin_init!(Self {
             instances <- new_mutex!(VgpuInstances::new(), "nova-core::vgpu-instances"),
+            dev,
+            cmdq,
+            bar_user,
+            mm,
             chid_pool,
             vmmu_segment_size,
             total_channels,
drivers/gpu/nova-core/vgpu/commands.rs +20 -1
index dc2534175e11..6207211d7f9d 100644
@@ -8,7 +8,9 @@
 
 use kernel::{
     device,
-    prelude::*, //
+    prelude::*,
+    time::Delta,
+    transmute::AsBytes, //
 };
 
 use crate::gsp::{
@@ -26,6 +28,7 @@
         commands::{
             VgpuPropertiesSchema, //
         },
+        GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK,
         GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE,
         GMCAPI_CMD_QUERY_VGPU_PROPERTIES, //
     }, //
@@ -90,3 +93,19 @@ pub(super) fn query_vgpu_properties(
         Ok(properties)
     }
 }
+
+/// Send BOOTLOAD and check its firmware status.
+pub(super) fn send_bootload(
+    dev: &device::Device<device::Bound>,
+    cmdq: &Cmdq<'_>,
+    payload: &[u64],
+) -> Result {
+    let command_id = GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK;
+    let response = cmdq.send_gmc_and_receive_timeout(
+        command_id,
+        AsBytes::as_bytes(payload),
+        0,
+        Delta::from_secs(10),
+    )?;
+    check_status(dev, command_id, response.status)
+}
drivers/gpu/nova-core/vgpu/fw.rs +3 -0
index aabe114b55c2..82c29dfb467a 100644
@@ -30,3 +30,6 @@
 
 pub(super) const GMCAPI_CMD_QUERY_VGPU_PROPERTIES: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_VGPU_PROPERTIES;
+
+pub(super) const GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK;
drivers/gpu/nova-core/vgpu/fw/commands.rs +0 -2
index b8007c3e4a71..3c4a6192d2a6 100644
@@ -60,7 +60,6 @@ pub(crate) struct ChannelMapEntry(u64) {
 impl ChannelMapEntry {
     const KEY: KeyId = 0x1001;
 
-    #[expect(dead_code)]
     pub(crate) fn new(engine_type: u16, index: u16, chid_offset: u32) -> Self {
         Self::zeroed()
             .with_engine_type(engine_type)
@@ -158,7 +157,6 @@ pub(crate) struct BootloadInfo<'a> {
 }
 
 /// Encodes a `VGPU_BOOTLOAD` request using the typed NVKV schema.
-#[expect(dead_code)]
 pub(crate) fn encode_vgpu_bootload(info: BootloadInfo<'_>) -> Result<KVVec<u64>> {
     let request = VgpuBootloadRequest {
         dbdf: info.dbdf.into(),
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs +14 -2
index b945e216a1d6..0b4d03c5f0fc 100644
@@ -39,7 +39,6 @@
     size_of::<RawControlRegion>() == u32_as_usize(fw::VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE)
 );
 /// Physical VRAM regions containing the vGPU plugin logs.
-#[expect(dead_code)]
 pub(super) struct PluginLogRegions {
     pub(super) init: VramRegion,
     pub(super) vgpu: VramRegion,
@@ -86,7 +85,6 @@ pub(super) struct CommBufferRegion<'map, 'gpu> {
     kernel_log: VramRegion,
 }
 
-#[expect(dead_code)]
 impl<'map, 'gpu> CommBufferRegion<'map, 'gpu> {
     /// Map the communication portion of a plugin management heap.
     pub(super) fn new(
@@ -188,6 +186,19 @@ pub(super) fn plugin_logs(&self) -> PluginLogRegions {
         }
     }
 
+    /// Clear a previous boot marker before starting the plugin.
+    pub(super) fn clear_plugin_ready(&self) -> Result {
+        let offset = self.io_offset(
+            &self.control,
+            core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.message_seq_num),
+            size_of::<u32>(),
+        )?;
+        self.map.try_write32(0, offset)?;
+        // Complete the posted clear before firmware can publish its new marker.
+        self.map.try_read32(offset)?;
+        Ok(())
+    }
+
     /// Return whether firmware has published the plugin boot marker.
     pub(super) fn is_plugin_ready(&self) -> Result<bool> {
         let value = self.read_u32(
@@ -199,6 +210,7 @@ pub(super) fn is_plugin_ready(&self) -> Result<bool> {
     }
 
     /// Invalidate the PTEs and release the communication mapping.
+    #[expect(dead_code)]
     pub(super) fn unmap(&mut self) -> Result {
         self.map.unmap()
     }
drivers/gpu/nova-core/vgpu/instance.rs +136 -16

216 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/vram.rs +0 -1
index faaeea106009..bd7d1b4bf039 100644
@@ -102,7 +102,6 @@ fn drop(&mut self) {
 /// All device accesses and mappings of its regions must end before dropping the slot.
 /// Clearing the entry locks a sleeping mutex, so dropping the slot may sleep.
 #[must_use]
-#[expect(dead_code)]
 pub(super) struct VgpuVramSlot {
     pub(super) fbmem: VramRegion,
     pub(super) mgmt_heap: VramRegion,
Signature · 1 line
2.53.0
author
#26 lore raw

PATCH v4 23/32 gpu: nova-core: vgpu: add instance shutdown

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Stop the GSP plugin, wait for SHUTDOWN completion, and release its firmware resources with CLEANUP before unmapping the communication buffers.

Add PendingInstance rollback and manager teardown. Register host resources before firmware work, and retain them until device removal if firmware state is uncertain or teardown fails. Keep the manager's dependencies alive until cleanup finishes.

---
 drivers/gpu/nova-core/gsp/cmdq/gmc.rs         |   1 -
 drivers/gpu/nova-core/vgpu.rs                 |   9 +-
 drivers/gpu/nova-core/vgpu/commands.rs        |  55 +++++++-
 drivers/gpu/nova-core/vgpu/fw.rs              |   9 ++
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs |   1 -
 drivers/gpu/nova-core/vgpu/instance.rs        | 118 +++++++++++++++++-
 6 files changed, 182 insertions(+), 11 deletions(-)
drivers/gpu/nova-core/gsp/cmdq/gmc.rs +0 -1
index d41d5e533cf7..3bceb080f9a9 100644
@@ -469,7 +469,6 @@ pub(crate) fn send_gmc_and_receive_timeout(
     ///
     /// Errors from initializing the request headers and from either callback are propagated
     /// as-is. The current valid element is consumed before a callback error is returned.
-    #[expect(dead_code)]
     pub(crate) fn send_gmc_and_wait_event(
         &self,
         command_id: u32,
drivers/gpu/nova-core/vgpu.rs +8 -1
index 84c4d0b3839d..f7184e7009a5 100644
@@ -105,7 +105,7 @@ fn query_state(
 use self::instance::VgpuInstances;
 
 /// Runtime resources for an enabled vGPU boot.
-#[pin_data]
+#[pin_data(PinnedDrop)]
 pub(crate) struct VgpuManager<'gpu> {
     #[pin]
     instances: Mutex<VgpuInstances<'gpu>>,
@@ -146,3 +146,10 @@ pub(crate) fn new(
         })
     }
 }
+
+#[pinned_drop]
+impl PinnedDrop for VgpuManager<'_> {
+    fn drop(self: Pin<&mut Self>) {
+        self.instances.lock().release_all(&self);
+    }
+}
drivers/gpu/nova-core/vgpu/commands.rs +53 -2

65 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/fw.rs +9 -0
index 82c29dfb467a..1528cc56ce74 100644
@@ -33,3 +33,12 @@
 
 pub(super) const GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK;
+
+pub(super) const GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK;
+
+pub(super) const GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK_COMPLETE: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK_COMPLETE;
+
+pub(super) const GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES;
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs +0 -1
index 0b4d03c5f0fc..a00f4d2affc7 100644
@@ -210,7 +210,6 @@ pub(super) fn is_plugin_ready(&self) -> Result<bool> {
     }
 
     /// Invalidate the PTEs and release the communication mapping.
-    #[expect(dead_code)]
     pub(super) fn unmap(&mut self) -> Result {
         self.map.unmap()
     }
drivers/gpu/nova-core/vgpu/instance.rs +112 -6

177 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#27 lore raw

PATCH v4 24/32 gpu: nova-core: vgpu: initialize GSP plugin RPC buffers

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The first plugin RPC needs region offsets, a protocol version and initialized control and response state in the shared communication buffer.

Add BAR1 accessors to initialize those fields and verify the fixed response structure size at build time. The control sequence field first carries the boot-ready marker, so RPC initialization must follow the plugin's ready indication.

---
 drivers/gpu/nova-core/mm/bar_user.rs          |  10 ++
 drivers/gpu/nova-core/vgpu/fw.rs              |   2 +
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs | 141 +++++++++++++++++-
 3 files changed, 147 insertions(+), 6 deletions(-)
drivers/gpu/nova-core/mm/bar_user.rs +10 -0
index d4364f94b13e..925bbefbddaf 100644
@@ -154,6 +154,11 @@ pub(crate) fn try_read32(&self, offset: usize) -> Result<u32> {
         self.bar_user.bar1.try_read32(off)
     }
 
+    fn try_write8(&self, value: u8, offset: usize) -> Result {
+        let off = self.bar_offset(offset)?;
+        self.bar_user.bar1.try_write8(value, off)
+    }
+
     /// Write a 32-bit value at the given offset.
     pub(crate) fn try_write32(&self, value: u32, offset: usize) -> Result {
         let off = self.bar_offset(offset)?;
@@ -287,6 +292,11 @@ pub(crate) fn try_read32(&self, offset: usize) -> Result<u32> {
             .try_read32(self.access_offset(offset, size_of::<u32>())?)
     }
 
+    pub(crate) fn try_write8(&self, value: u8, offset: usize) -> Result {
+        self.access()?
+            .try_write8(value, self.access_offset(offset, size_of::<u8>())?)
+    }
+
     pub(crate) fn try_write32(&self, value: u32, offset: usize) -> Result {
         self.access()?
             .try_write32(value, self.access_offset(offset, size_of::<u32>())?)
drivers/gpu/nova-core/vgpu/fw.rs +2 -0
index 1528cc56ce74..03225b9833c3 100644
@@ -15,12 +15,14 @@
     VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE,
     VGPU_CPU_GSP_CTRL_BUFF_REGION as RawControlRegion,
     VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_CTRL_BUFF_VERSION,
     VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_INIT_TASK_LOG_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE,
+    VGPU_CPU_GSP_RESPONSE_BUFF_REGION as RawResponseRegion,
     VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE,
     VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE, //
 };
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs +135 -6

207 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#28 lore raw

PATCH v4 25/32 gpu: nova-core: vgpu: add GSP plugin RPC transactions

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

A GSP plugin consumes a shared-buffer request after its VF doorbell is rung and publishes completion in the response region.

Add PluginRpc to own the communication mapping together with the instance's stable BAR0 and validated GFID. Accept typed RPC message IDs, copy the payload before publishing its sequence, advance the local sequence, ring and read back the doorbell, then wait for a matching completion and check its firmware status.

Keep one sequence identity for each published request, including a request whose doorbell or wait later fails, and bound completion polling with the RPC timeout. Delegate explicit unmap to the owned communication region so teardown errors retain its owner; ordinary drop releases the mapping through BarMapping.

---
 drivers/gpu/nova-core/regs.rs                 |  31 +++-
 drivers/gpu/nova-core/vgpu.rs                 |   1 +
 drivers/gpu/nova-core/vgpu/fw.rs              |  13 ++
 drivers/gpu/nova-core/vgpu/fw/commands.rs     |  10 ++
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs |  51 +++++-
 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs  | 155 ++++++++++++++++++
 drivers/gpu/nova-core/vgpu/instance.rs        |  15 +-
 7 files changed, 267 insertions(+), 9 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs
drivers/gpu/nova-core/regs.rs +30 -1
index bf9fee4393ce..aea194eb8069 100644
@@ -7,13 +7,17 @@
         Io,
         Mmio, //
     },
+    prelude::*,
     sizes::SizeConstants,
     time, //
 };
 use pin_init::Zeroable;
 
 use crate::{
-    driver::NovaRegisters,
+    driver::{
+        Bar0,
+        NovaRegisters, //
+    },
     falcon::{
         DmaTrfCmdSize,
         FalconCoreRev,
@@ -39,6 +43,31 @@
     pub(crate) NV_PBUS_SW_SCRATCH(u32)[64] @ 0x00001400 {}
 }
 
+// VIRTUAL_FUNCTION
+
+register! {
+    base: NovaRegisters;
+
+    // PF BAR0 exposes the virtual-function register window at 0x00b8_0000.
+    pub(crate) NV_VIRTUAL_FUNCTION_PRIV_DOORBELL(u32) @ 0x00b8_2200 {
+        31:0 handle;
+    }
+}
+
+impl NV_VIRTUAL_FUNCTION_PRIV_DOORBELL {
+    const DOORBELL_STRIDE: u32 = 32;
+    const DOORBELL_VECTOR: u32 = 17;
+
+    /// Notify the GSP plugin for the given guest function and read back the doorbell.
+    pub(crate) fn ring_gsp_plugin(bar0: Bar0<'_>, gfid: u16) -> Result {
+        // A `u16` GFID produces a handle of at most 0x1f_fff1, which fits in `u32`.
+        let value = u32::from(gfid) * Self::DOORBELL_STRIDE + Self::DOORBELL_VECTOR;
+        bar0.try_write_reg(Self::zeroed().with_handle(value))?;
+        bar0.try_read(NV_VIRTUAL_FUNCTION_PRIV_DOORBELL)?;
+        Ok(())
+    }
+}
+
 // PGC6 register space.
 //
 // `GC6` is a GPU low-power state where VRAM is in self-refresh and the GPU is powered down (except
drivers/gpu/nova-core/vgpu.rs +1 -0
index f7184e7009a5..d6bcdabafb42 100644
@@ -35,6 +35,7 @@
 mod commands;
 mod fw;
 mod gsp_plugin_comm;
+mod gsp_plugin_rpc;
 mod hal;
 mod instance;
 mod vram;
drivers/gpu/nova-core/vgpu/fw.rs +13 -0
index 03225b9833c3..d80f36465296 100644
@@ -10,6 +10,8 @@
 
 use crate::gsp::bindings;
 
+pub(super) use commands::RpcMessage;
+
 pub(super) use bindings::{
     GSP_PLUGIN_BOOTLOADED,
     VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE,
@@ -44,3 +46,14 @@
 
 pub(super) const GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES;
+
+/// State observed in the response buffer for an expected RPC sequence.
+pub(super) enum RpcResponse {
+    Pending {
+        /// Last sequence completed by firmware.
+        sequence: u32,
+    },
+    Complete {
+        status: u32,
+    },
+}
drivers/gpu/nova-core/vgpu/fw/commands.rs +10 -0
index 3c4a6192d2a6..917de4d21861 100644
@@ -27,6 +27,16 @@
     mm::vram::VramRegion, //
 };
 
+use super::bindings;
+
+/// Message types supported by the nova-core plugin RPC channel.
+#[expect(dead_code)]
+#[derive(Clone, Copy)]
+#[repr(u32)]
+pub(crate) enum RpcMessage {
+    VersionNegotiation = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_VERSION_NEGOTIATION,
+}
+
 bitfield! {
     pub(crate) struct Dbdf(u32) {
         2:0 function;
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs +49 -2

69 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs new file +155 -0

155 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/instance.rs +9 -6
index bf02595590e1..8a2d8e3970a3 100644
@@ -20,6 +20,7 @@
 };
 
 use crate::{
+    driver::Bar0,
     gpu::ChannelIdReservation,
     gsp::{
         cmdq::Cmdq,
@@ -41,6 +42,7 @@
         ChannelMapEntry, //
     },
     gsp_plugin_comm::CommBufferRegion,
+    gsp_plugin_rpc::PluginRpc,
     vram::{
         VgpuVramLayout,
         VgpuVramSlot,
@@ -141,7 +143,7 @@ struct VgpuInstance<'gpu> {
     vgpu_type: VgpuType,
     vm_pid: u32,
     num_plugin_channels: u32,
-    comm: CommBufferRegion<'gpu, 'gpu>,
+    plugin_rpc: PluginRpc<'gpu, 'gpu>,
     // Unmap the communication region before returning its slot and channel IDs.
     vram_slot: VgpuVramSlot,
     chids: ChannelIdReservation<'gpu>,
@@ -170,7 +172,7 @@ fn teardown(&mut self, vgpu: &VgpuManager<'gpu>) -> Result {
                 send_cleanup(vgpu.dev, vgpu.cmdq, self.gfid)?;
                 self.needs_teardown = false;
             }
-            self.comm.unmap()
+            self.plugin_rpc.unmap()
         })();
         if let Err(error) = result {
             self.failure = Some(error);
@@ -187,7 +189,7 @@ fn bootload(
     ) -> Result {
         let fb = &self.vram_slot.fbmem;
         let mgmt = &self.vram_slot.mgmt_heap;
-        let logs = self.comm.plugin_logs();
+        let logs = self.plugin_rpc.comm().plugin_logs();
 
         let payload = encode_vgpu_bootload(BootloadInfo {
             dbdf: self.dbdf,
@@ -215,11 +217,11 @@ fn bootload(
             payload.len() * size_of::<u64>(),
         );
 
-        self.comm.clear_plugin_ready()?;
+        self.plugin_rpc.comm().clear_plugin_ready()?;
         self.needs_teardown = true;
         send_bootload(dev, cmdq, &payload)?;
 
-        wait_plugin_ready(dev, &self.comm)?;
+        wait_plugin_ready(dev, self.plugin_rpc.comm())?;
 
         dev_dbg!(dev, "bootload: gfid={} plugin ready\n", self.gfid.get());
         Ok(())
@@ -293,6 +295,7 @@ fn alloc_vram_slot(&mut self, mm: &GpuMm<'_>, layout: VgpuVramLayout) -> Result<
     fn allocate_instance<'a>(
         &'a mut self,
         vgpu: &'a VgpuManager<'gpu>,
+        bar0: Bar0<'gpu>,
         info: InstanceInfo,
     ) -> Result<PendingInstance<'a, 'gpu>> {
         let InstanceInfo {
@@ -351,7 +354,7 @@ fn allocate_instance<'a>(
             vgpu_type,
             vm_pid,
             num_plugin_channels: PLUGIN_CHANNELS_PER_ENGINE,
-            comm,
+            plugin_rpc: PluginRpc::new(comm, bar0, gfid),
             vram_slot,
             chids,
             needs_teardown: false,
Signature · 1 line
2.53.0
author
#29 lore raw

PATCH v4 26/32 gpu: nova-core: vgpu: negotiate the GSP plugin RPC version

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The host must negotiate the RPC protocol with the booted GSP plugin before sending instance configuration.

After observing the plugin's ready marker, initialize the shared RPC buffers and submit the existing typed version-negotiation request through the instance's PluginRpc channel.

---
 drivers/gpu/nova-core/vgpu/commands.rs       | 12 +++++++++++-
 drivers/gpu/nova-core/vgpu/fw.rs             |  4 ++--
 drivers/gpu/nova-core/vgpu/fw/commands.rs    |  1 -
 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs |  2 --
 drivers/gpu/nova-core/vgpu/instance.rs       |  3 +++
 5 files changed, 16 insertions(+), 6 deletions(-)
drivers/gpu/nova-core/vgpu/commands.rs +11 -1
index 00da8fa4f910..a68841fe4019 100644
@@ -28,13 +28,15 @@
         commands::{
             VgpuPropertiesSchema, //
         },
+        RpcMessage, //
         GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK,
         GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES,
         GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE,
         GMCAPI_CMD_QUERY_VGPU_PROPERTIES,
         GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK,
-        GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK_COMPLETE, //
+        GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK_COMPLETE,
     },
+    gsp_plugin_rpc::PluginRpc,
     instance::Gfid, //
 };
 
@@ -160,3 +162,11 @@ pub(super) fn send_cleanup(
     dev_dbg!(dev, "cleanup: gfid={} done\n", gfid.get());
     Ok(())
 }
+
+/// Negotiate the host protocol with a bootloaded GSP plugin.
+pub(super) fn negotiate_plugin_version(
+    dev: &device::Device<device::Bound>,
+    rpc: &mut PluginRpc<'_, '_>,
+) -> Result {
+    rpc.rpc_call(dev, RpcMessage::VersionNegotiation, &[])
+}
drivers/gpu/nova-core/vgpu/fw.rs +2 -2
index d80f36465296..6cec92c9b505 100644
@@ -10,8 +10,6 @@
 
 use crate::gsp::bindings;
 
-pub(super) use commands::RpcMessage;
-
 pub(super) use bindings::{
     GSP_PLUGIN_BOOTLOADED,
     VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE,
@@ -29,6 +27,8 @@
     VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE, //
 };
 
+pub(super) use commands::RpcMessage;
+
 pub(super) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE;
 
drivers/gpu/nova-core/vgpu/fw/commands.rs +0 -1
index 917de4d21861..c3d0acece2eb 100644
@@ -30,7 +30,6 @@
 use super::bindings;
 
 /// Message types supported by the nova-core plugin RPC channel.
-#[expect(dead_code)]
 #[derive(Clone, Copy)]
 #[repr(u32)]
 pub(crate) enum RpcMessage {
drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs +0 -2
index 0ee32f011f63..696127c7169f 100644
@@ -65,7 +65,6 @@ pub(super) fn comm(&self) -> &CommBufferRegion<'map, 'gpu> {
     }
 
     /// Initialize the control and response buffers for the first RPC.
-    #[expect(dead_code)]
     pub(super) fn init_rpc(&mut self) -> Result {
         self.comm.initialize()?;
         self.message_sequence = 0;
@@ -82,7 +81,6 @@ fn next_sequence(&self) -> u32 {
     }
 
     /// Write one RPC message, ring the VF doorbell, and wait for its response.
-    #[expect(dead_code)]
     pub(super) fn rpc_call(
         &mut self,
         dev: &device::Device<device::Bound>,
drivers/gpu/nova-core/vgpu/instance.rs +3 -0
index 8a2d8e3970a3..445d7f513fdc 100644
@@ -31,6 +31,7 @@
 
 use super::{
     commands::{
+        negotiate_plugin_version,
         send_bootload,
         send_cleanup,
         send_shutdown,
@@ -156,6 +157,8 @@ impl<'gpu> VgpuInstance<'gpu> {
     fn activate(&mut self, vgpu: &VgpuManager<'gpu>) -> Result {
         let dev = vgpu.dev;
         self.bootload(dev, vgpu.cmdq, &vgpu.fifo_engine_list)?;
+        self.plugin_rpc.init_rpc()?;
+        negotiate_plugin_version(dev, &mut self.plugin_rpc)?;
 
         Ok(())
     }
Signature · 1 line
2.53.0
author
#30 lore raw

PATCH v4 27/32 gpu: nova-core: vgpu: send GSP plugin configuration parameters

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The GSP plugin needs instance identity, channel allocation and host parameters before it can serve guest requests.

Encode the configuration as an NVKV stream, prefix it with its word count and send SETUP_CONFIG_PARAMS_AND_INIT after protocol negotiation through the instance's PluginRpc channel.

Derive the host's opaque device identity from the VF DBDF within the supported 16-bit PCI segment namespace. This identifies the local device without imposing a migration-persistent identity.

Describe the supported whole-GPU configuration and distinguish the optional VMM capability bitmap from the separately reported migration features. Encode the host page size with the kernel's infallible conversion.

---
 drivers/gpu/nova-core/vgpu/commands.rs       |   9 ++
 drivers/gpu/nova-core/vgpu/fw/commands.rs    | 135 +++++++++++++++++++
 drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs |  17 +++
 drivers/gpu/nova-core/vgpu/instance.rs       |  16 +++
 4 files changed, 177 insertions(+)
drivers/gpu/nova-core/vgpu/commands.rs +9 -0
index a68841fe4019..968942273b9e 100644
@@ -170,3 +170,12 @@ pub(super) fn negotiate_plugin_version(
 ) -> Result {
     rpc.rpc_call(dev, RpcMessage::VersionNegotiation, &[])
 }
+
+/// Send an instance's encoded configuration to the GSP plugin.
+pub(super) fn send_plugin_config(
+    dev: &device::Device<device::Bound>,
+    rpc: &mut PluginRpc<'_, '_>,
+    config: &[u64],
+) -> Result {
+    rpc.rpc_call_nvkv(dev, RpcMessage::SetupConfigParamsAndInit, config)
+}
drivers/gpu/nova-core/vgpu/fw/commands.rs +135 -0

150 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/gsp_plugin_rpc.rs +17 -0
index 696127c7169f..48977cd67792 100644
@@ -19,6 +19,7 @@
 
 use kernel::{
     device,
+    num::casts::usize_as_u64,
     prelude::*,
     time::{
         delay::fsleep,
@@ -26,6 +27,7 @@
         Instant,
         Monotonic, //
     },
+    transmute::AsBytes, //
 };
 
 use crate::{
@@ -105,6 +107,21 @@ pub(super) fn rpc_call(
         self.wait_response(dev, sequence)
     }
 
+    /// Send an NVKV stream prefixed by its word count.
+    pub(super) fn rpc_call_nvkv(
+        &mut self,
+        dev: &device::Device<device::Bound>,
+        message_type: RpcMessage,
+        encoded: &[u64],
+    ) -> Result {
+        let word_count = usize_as_u64(encoded.len());
+        let mut payload = KVec::new();
+        payload.extend_from_slice(&word_count.to_le_bytes(), GFP_KERNEL)?;
+        payload.extend_from_slice(AsBytes::as_bytes(encoded), GFP_KERNEL)?;
+
+        self.rpc_call(dev, message_type, &payload)
+    }
+
     fn wait_response(&self, dev: &device::Device<device::Bound>, expected_sequence: u32) -> Result {
         let start = Instant::<Monotonic>::now();
         let timeout = Delta::from_secs(120);
drivers/gpu/nova-core/vgpu/instance.rs +16 -0
index 445d7f513fdc..247e88bdc82f 100644
@@ -34,10 +34,12 @@
         negotiate_plugin_version,
         send_bootload,
         send_cleanup,
+        send_plugin_config,
         send_shutdown,
         Dbdf, //
     },
     fw::commands::{
+        encode_plugin_config_params,
         encode_vgpu_bootload,
         BootloadInfo,
         ChannelMapEntry, //
@@ -159,6 +161,7 @@ fn activate(&mut self, vgpu: &VgpuManager<'gpu>) -> Result {
         self.bootload(dev, vgpu.cmdq, &vgpu.fifo_engine_list)?;
         self.plugin_rpc.init_rpc()?;
         negotiate_plugin_version(dev, &mut self.plugin_rpc)?;
+        self.configure_plugin(dev)?;
 
         Ok(())
     }
@@ -230,6 +233,19 @@ fn bootload(
         Ok(())
     }
 
+    fn configure_plugin(&mut self, dev: &device::Device<device::Bound>) -> Result {
+        let config = encode_plugin_config_params(
+            [0; 16],
+            self.dbdf,
+            self.vgpu_type.vgpu_type_id,
+            self.vm_pid,
+            u32::try_from(self.chids.len()).map_err(|_| EOVERFLOW)?,
+            self.num_plugin_channels,
+        )?;
+
+        send_plugin_config(dev, &mut self.plugin_rpc, &config)
+    }
+
     /// Stop the plugin when firmware may own instance resources.
     fn shutdown(&mut self, dev: &device::Device<device::Bound>, cmdq: &Cmdq<'_>) -> Result {
         if self.needs_teardown {
Signature · 1 line
2.53.0
author
#31 lore raw

PATCH v4 28/32 gpu: nova-core: vgpu: update the GSP plugin BME state

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

The vGPU manager reports the enabled bus-mastering state to the GSP plugin after its initial configuration. The guest can then use the vGPU from now on.

Add the BME state request and NVKV encoder, and send the enabled state after configuration succeeds.

---
 drivers/gpu/nova-core/vgpu/commands.rs    | 11 +++++++++++
 drivers/gpu/nova-core/vgpu/fw/commands.rs | 22 ++++++++++++++++++++++
 drivers/gpu/nova-core/vgpu/instance.rs    |  2 ++
 3 files changed, 35 insertions(+)
drivers/gpu/nova-core/vgpu/commands.rs +11 -0
index 968942273b9e..7f26d2dabca4 100644
@@ -26,6 +26,7 @@
 use super::{
     fw::{
         commands::{
+            encode_plugin_set_bme,
             VgpuPropertiesSchema, //
         },
         RpcMessage, //
@@ -179,3 +180,13 @@ pub(super) fn send_plugin_config(
 ) -> Result {
     rpc.rpc_call_nvkv(dev, RpcMessage::SetupConfigParamsAndInit, config)
 }
+
+/// Update the bus-mastering state reported to the GSP plugin.
+pub(super) fn set_plugin_bme(
+    dev: &device::Device<device::Bound>,
+    rpc: &mut PluginRpc<'_, '_>,
+    enable: bool,
+) -> Result {
+    let bme = encode_plugin_set_bme(enable)?;
+    rpc.rpc_call_nvkv(dev, RpcMessage::UpdateBmeState, &bme)
+}
drivers/gpu/nova-core/vgpu/fw/commands.rs +22 -0
index 3f94435e7a59..9356b4fb643a 100644
@@ -36,6 +36,7 @@
 pub(crate) enum RpcMessage {
     VersionNegotiation = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_VERSION_NEGOTIATION,
     SetupConfigParamsAndInit = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_SETUP_CONFIG_PARAMS_AND_INIT,
+    UpdateBmeState = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_UPDATE_BME_STATE,
 }
 
 bitfield! {
@@ -397,3 +398,24 @@ pub(crate) fn encode_plugin_config_params(
     request.encode(&mut encoder)?;
     Ok(encoder.finish())
 }
+
+nvkv_encode! {
+    struct PluginSetBmeRequest {
+        bme_enable: Key<bool, { Self::BME_ENABLE_KEY }, u32>,
+    }
+}
+
+impl PluginSetBmeRequest {
+    const BME_ENABLE_KEY: KeyId = 0x0100;
+}
+
+/// Encodes a plugin BME state update using the typed NVKV schema.
+pub(crate) fn encode_plugin_set_bme(enable: bool) -> Result<KVVec<u64>> {
+    let request = PluginSetBmeRequest {
+        bme_enable: enable.into(),
+    };
+
+    let mut encoder = Encoder::new();
+    request.encode(&mut encoder)?;
+    Ok(encoder.finish())
+}
drivers/gpu/nova-core/vgpu/instance.rs +2 -0
index 247e88bdc82f..23c2b791aa25 100644
@@ -36,6 +36,7 @@
         send_cleanup,
         send_plugin_config,
         send_shutdown,
+        set_plugin_bme,
         Dbdf, //
     },
     fw::commands::{
@@ -162,6 +163,7 @@ fn activate(&mut self, vgpu: &VgpuManager<'gpu>) -> Result {
         self.plugin_rpc.init_rpc()?;
         negotiate_plugin_version(dev, &mut self.plugin_rpc)?;
         self.configure_plugin(dev)?;
+        set_plugin_bme(dev, &mut self.plugin_rpc, true)?;
 
         Ok(())
     }
Signature · 1 line
2.53.0
author
#32 lore raw

PATCH v4 29/32 gpu: nova-core: vgpu: add CeUtils commands

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Add Cmdq operations to allocate and free a CeUtils channel and submit guest VRAM scrub requests. Decode the allocation and scrub replies, and validate the returned semaphore address and aperture.

Distinguish an explicit allocation rejection from failures that may leave firmware owning the channel, so callers can decide whether to retain its host resources.

---
 .../gpu/nova-core/gsp/fw/r000_00/bindings.rs  |   1 +
 drivers/gpu/nova-core/vgpu/commands.rs        | 145 ++++++++++++++++--
 drivers/gpu/nova-core/vgpu/fw.rs              |  11 ++
 drivers/gpu/nova-core/vgpu/fw/commands.rs     |  52 ++++++-
 4 files changed, 198 insertions(+), 11 deletions(-)
drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs +1 -0
index 3cc718bb2ab8..333256ea8a04 100644
@@ -851,6 +851,7 @@ fn default() -> Self {
         }
     }
 }
+pub const NV_ADDR_FBMEM: u32 = 2;
 pub const GSP_PLUGIN_BOOTLOADED: u32 = 1315261039;
 pub const VGPU_CPU_GSP_CTRL_BUFF_VERSION: u32 = 2;
 pub const VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE: u32 = 4096;
drivers/gpu/nova-core/vgpu/commands.rs +136 -9

173 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/fw.rs +11 -0
index 6cec92c9b505..795ce4855586 100644
@@ -27,6 +27,8 @@
     VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE, //
 };
 
+pub(super) use bindings::NV_ADDR_FBMEM;
+
 pub(super) use commands::RpcMessage;
 
 pub(super) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
@@ -47,6 +49,15 @@
 pub(super) const GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES: u32 =
     bindings::GMCAPI_COMMANDS_GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES;
 
+pub(super) const GMCAPI_CMD_VGPU_MGR_ALLOC_GSP_CEUTILS: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_ALLOC_GSP_CEUTILS;
+
+pub(super) const GMCAPI_CMD_VGPU_MGR_FREE_GSP_CEUTILS: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_FREE_GSP_CEUTILS;
+
+pub(super) const GMCAPI_CMD_VGPU_MGR_SCRUB_GUEST_FB: u32 =
+    bindings::GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_SCRUB_GUEST_FB;
+
 /// State observed in the response buffer for an expected RPC sequence.
 pub(super) enum RpcResponse {
     Pending {
drivers/gpu/nova-core/vgpu/fw/commands.rs +50 -2

66 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#33 lore raw

PATCH v4 30/32 gpu: nova-core: vgpu: scrub guest VRAM with CeUtils

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Scrub guest VRAM before plugin boot and after plugin shutdown, before its slot can be reused. Reserve the last channel ID in each instance range for CeUtils and give the remaining channels to the plugin.

Submit scrubs in chunks and wait for completion through a temporary BAR1 mapping of the CeUtils semaphore. Release the mapping on both success and failure, preserving a polling error if unmapping also fails.

Keep allocation, scrubbing and release within the instance lifecycle. Retain host resources until device removal when firmware ownership is uncertain or a scrub fails, and do not retry a recorded teardown failure.

---
 drivers/gpu/nova-core/vgpu.rs          |   1 +
 drivers/gpu/nova-core/vgpu/commands.rs |   4 -
 drivers/gpu/nova-core/vgpu/instance.rs |  51 +++++++-
 drivers/gpu/nova-core/vgpu/scrubber.rs | 173 +++++++++++++++++++++++++
 4 files changed, 223 insertions(+), 6 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/scrubber.rs
drivers/gpu/nova-core/vgpu.rs +1 -0
index d6bcdabafb42..f78a4621984a 100644
@@ -38,6 +38,7 @@
 mod gsp_plugin_rpc;
 mod hal;
 mod instance;
+mod scrubber;
 mod vram;
 
 /// vGPU state detected during GPU construction.
drivers/gpu/nova-core/vgpu/commands.rs +0 -4
index 44ab9175313f..bb446d742e19 100644
@@ -208,7 +208,6 @@ pub(super) fn set_plugin_bme(
 }
 
 /// Whether a failed allocation may still have transferred CHID ownership to firmware.
-#[expect(dead_code)]
 pub(super) enum CeUtilsAllocError {
     /// A matching firmware response explicitly rejected the allocation.
     NotOwned(Error),
@@ -217,7 +216,6 @@ pub(super) enum CeUtilsAllocError {
 }
 
 /// Allocate a CeUtils channel and validate its semaphore description.
-#[expect(dead_code)]
 pub(super) fn alloc_ceutils(
     dev: &device::Device<device::Bound>,
     cmdq: &Cmdq<'_>,
@@ -266,7 +264,6 @@ pub(super) fn alloc_ceutils(
 }
 
 /// Release a CeUtils allocation, including one whose allocation reply was lost.
-#[expect(dead_code)]
 pub(super) fn free_ceutils(
     dev: &device::Device<device::Bound>,
     cmdq: &Cmdq<'_>,
@@ -283,7 +280,6 @@ pub(super) fn free_ceutils(
 }
 
 /// Submit an asynchronous guest FB scrub and return its work identifier.
-#[expect(dead_code)]
 pub(super) fn submit_ceutils_scrub(
     dev: &device::Device<device::Bound>,
     cmdq: &Cmdq<'_>,
drivers/gpu/nova-core/vgpu/instance.rs +49 -2

111 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/scrubber.rs new file +173 -0

173 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#34 lore raw

PATCH v4 31/32 gpu: nova-core: vgpu: export plugin log buffers via debugfs

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Expose each instance's init, vgpu and kernel plugin logs through debugfs for nvlog_decoder.

Read the management-heap log buffers through BAR1 and reuse the existing GSP log header when a firmware build ID is available. Use the INIT, VGPU and KRNL task names expected by the decoder, and derive the instance directory name from the typed DBDF fields.
Retain the GPU identity and firmware build ID in the manager for per-instance log headers.

Retain the mapping while the files are accessible and revoke the debugfs scope before explicitly unmapping it. Place the scope before the RPC mapping in the instance's field order so device removal also drains readers before the mapping is dropped. Preserve partial-read accounting when a user copy fails.

---
 drivers/gpu/nova-core/gpu.rs                  |   9 +-
 drivers/gpu/nova-core/gsp.rs                  |  23 +++
 drivers/gpu/nova-core/mm/bar_user.rs          |   5 +-
 drivers/gpu/nova-core/vgpu.rs                 |  11 +-
 drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs | 116 +++++++++++-
 drivers/gpu/nova-core/vgpu/instance.rs        |  60 +++++-
 drivers/gpu/nova-core/vgpu/log.rs             | 171 ++++++++++++++++++
 7 files changed, 382 insertions(+), 13 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/log.rs
drivers/gpu/nova-core/gpu.rs +6 -3
index da753bfc2df7..02145f16a2dd 100644
@@ -630,9 +630,10 @@ pub(crate) fn new<'a>(
                         // SAFETY: These sibling fields are initialized at their final pinned
                         // addresses. The private manager cannot escape this `Gpu`, and is dropped
                         // before all its dependencies, both here on failure and on normal removal.
-                        let (cmdq, bar_user, mm, chid_pool) = unsafe {
+                        // The GSP build ID is not modified after initialization.
+                        let (gsp, bar_user, mm, chid_pool) = unsafe {
                             (
-                                &*core::ptr::from_ref(&gsp_resources.gsp.cmdq),
+                                &*core::ptr::from_ref(&gsp_resources.gsp),
                                 &*core::ptr::from_ref(bar_user.as_ref().get_ref()),
                                 &*core::ptr::from_ref(mm.as_ref().get_ref()),
                                 &*core::ptr::from_ref(chid_pool.as_ref().get_ref()),
@@ -640,7 +641,9 @@ pub(crate) fn new<'a>(
                         };
                         Some(KBox::pin_init(VgpuManager::new(
                             dev,
-                            cmdq,
+                            &gsp.cmdq,
+                            gsp_resources.spec,
+                            gsp.build_id(),
                             bar_user,
                             mm,
                             chid_pool,
drivers/gpu/nova-core/gsp.rs +23 -0
index 7e10a1deeafe..e590fc9fbe3d 100644
@@ -180,6 +180,21 @@ fn new(spec: Spec, build_id: &BuildId, task_prefix: &str) -> Self {
     }
 }
 
+/// Size of the header prepended to debugfs log buffer dumps.
+pub(crate) const LOG_BUFFER_HEADER_SIZE: usize = size_of::<LogBufferHeader>();
+
+/// Builds a log header using the GPU implementation reported by the hardware.
+pub(crate) fn build_log_buffer_header(
+    spec: Spec,
+    build_id: &BuildId,
+    task_prefix: &str,
+) -> [u8; LOG_BUFFER_HEADER_SIZE] {
+    let header = LogBufferHeader::new(spec, build_id, task_prefix);
+    let mut bytes = [0; LOG_BUFFER_HEADER_SIZE];
+    bytes.copy_from_slice(header.as_bytes());
+    bytes
+}
+
 /// The logging buffers are byte queues that contain encoded printf-like
 /// messages from GSP-RM.  They need to be decoded by a special application
 /// that can parse the buffers.
@@ -368,6 +383,8 @@ fn register_debugfs<'data>(&'data self, dir: &debugfs::ScopedDir<'data, '_>) {
 pub(crate) struct Gsp<'gsp> {
     /// The GSP firmware's TLV.
     gsp_tlv: firmware::Firmware,
+    /// Build identifier of the firmware whose log buffers are exposed.
+    build_id: Option<BuildId>,
     /// Libos arguments.
     pub(crate) libos: Coherent<'gsp, [LibosMemoryRegionInitArgument]>,
     /// Log buffers, optionally exposed via debugfs.
@@ -383,6 +400,11 @@ pub(crate) struct Gsp<'gsp> {
 }
 
 impl<'gsp> Gsp<'gsp> {
+    /// Returns the GSP firmware build identifier, when available.
+    pub(crate) fn build_id(&self) -> Option<&BuildId> {
+        self.build_id.as_ref()
+    }
+
     // Creates an in-place initializer for a `Gsp` manager for `pdev`.
     pub(crate) fn new(
         pdev: &'gsp pci::Device<device::Bound>,
@@ -405,6 +427,7 @@ pub(crate) fn new(
 
             Ok(try_pin_init!(Self {
                 gsp_tlv,
+                build_id,
                 cmdq <- Cmdq::new(dev, bar),
                 rm_state_monitor: Coherent::zeroed(dev, GFP_KERNEL)?,
                 rmargs: Coherent::init(
drivers/gpu/nova-core/mm/bar_user.rs +4 -1
index 925bbefbddaf..ee718cf7668f 100644
@@ -201,7 +201,6 @@ pub(crate) struct BarMapping<'map, 'gpu> {
     unmap_error: Option<Error>,
 }
 
-#[expect(dead_code)]
 impl<'map, 'gpu> BarMapping<'map, 'gpu> {
     /// Maps the containing pages while restricting CPU access to the requested byte range.
     pub(crate) fn new(
@@ -247,6 +246,10 @@ pub(crate) fn new(
         })
     }
 
+    pub(crate) fn bar1(&self) -> Bar1<'gpu> {
+        self.access.bar_user.bar1
+    }
+
     pub(crate) fn region(&self) -> &VramRegion {
         &self.region
     }
drivers/gpu/nova-core/vgpu.rs +10 -1
index f78a4621984a..2dbf95222d4d 100644
@@ -11,13 +11,15 @@
 };
 
 use crate::{
+    firmware::gsp::BuildId,
     fsp::{
         Fsp,
         VgpuMode, //
     },
     gpu::{
         ChannelIdPool,
-        Chipset, //
+        Chipset,
+        Spec, //
     },
     gsp::{
         cmdq::Cmdq,
@@ -38,6 +40,7 @@
 mod gsp_plugin_rpc;
 mod hal;
 mod instance;
+mod log;
 mod scrubber;
 mod vram;
 
@@ -113,6 +116,8 @@ pub(crate) struct VgpuManager<'gpu> {
     instances: Mutex<VgpuInstances<'gpu>>,
     dev: &'gpu device::Device<device::Bound>,
     cmdq: &'gpu Cmdq<'gpu>,
+    spec: Spec,
+    build_id: Option<&'gpu BuildId>,
     bar_user: &'gpu BarUser<'gpu>,
     mm: &'gpu Mutex<GpuMm<'gpu>>,
     chid_pool: &'gpu ChannelIdPool,
@@ -127,6 +132,8 @@ impl<'gpu> VgpuManager<'gpu> {
     pub(crate) fn new(
         dev: &'gpu device::Device<device::Bound>,
         cmdq: &'gpu Cmdq<'gpu>,
+        spec: Spec,
+        build_id: Option<&'gpu BuildId>,
         bar_user: &'gpu BarUser<'gpu>,
         mm: &'gpu Mutex<GpuMm<'gpu>>,
         chid_pool: &'gpu ChannelIdPool,
@@ -139,6 +146,8 @@ pub(crate) fn new(
             instances <- new_mutex!(VgpuInstances::new(), "nova-core::vgpu-instances"),
             dev,
             cmdq,
+            spec,
+            build_id,
             bar_user,
             mm,
             chid_pool,
drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs +110 -6

140 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/instance.rs +58 -2

113 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/log.rs new file +171 -0

171 lines not on this page · raw message

Signature · 1 line
2.53.0
author
#35 lore raw

PATCH v4 32/32 gpu: nova-core: vgpu: introduce SR-IOV PF APIs

In reply to Alok Kumar

From: Zhi Wang <[email protected]>

Provide PF operations for querying vGPU availability and creating, closing and resetting VF instances.

Validate VF identifiers against the PF's VF count and route lifecycle operations through the manager. Retain the GPU context needed to create plugin RPC channels and return guest PCI properties after activation.

---
 drivers/gpu/nova-core/gpu.rs              |  24 +++-
 drivers/gpu/nova-core/nova_core.rs        |   6 +
 drivers/gpu/nova-core/vgpu.rs             |   2 +
 drivers/gpu/nova-core/vgpu/commands.rs    |  10 +-
 drivers/gpu/nova-core/vgpu/fw/commands.rs |   1 +
 drivers/gpu/nova-core/vgpu/instance.rs    |  93 +++++++++++--
 drivers/gpu/nova-core/vgpu/vgpu_api.rs    | 158 ++++++++++++++++++++++
 7 files changed, 280 insertions(+), 14 deletions(-)
 create mode 100644 drivers/gpu/nova-core/vgpu/vgpu_api.rs
drivers/gpu/nova-core/gpu.rs +23 -1
index 02145f16a2dd..c097c83c5c47 100644
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 
-use core::ops::Range;
+use core::{
+    num::NonZero,
+    ops::Range, //
+};
 
 use kernel::{
     device,
@@ -472,6 +475,25 @@ fn drop(self: Pin<&mut Self>) {
 }
 
 impl<'gpu> Gpu<'gpu> {
+    pub(crate) fn cmdq(&self) -> &Cmdq<'gpu> {
+        &self.gsp_resources.gsp.cmdq
+    }
+
+    pub(crate) fn vgpu_manager(&self) -> Option<&VgpuManager<'gpu>> {
+        self.vgpu.as_ref().map(|vgpu| vgpu.as_ref().get_ref())
+    }
+
+    pub(crate) fn vgpu_total_vfs(&self) -> Option<NonZero<u16>> {
+        match self.gsp_resources.vgpu_state {
+            VgpuState::Disabled => None,
+            VgpuState::Enabled { total_vfs } => Some(total_vfs),
+        }
+    }
+
+    pub(crate) fn bar0(&self) -> Bar0<'gpu> {
+        self.gsp_resources.bar
+    }
+
     pub(crate) fn new<'a>(
         pdev: &'gpu pci::Device<device::Core<'a>>,
         bar: Bar0<'gpu>,
drivers/gpu/nova-core/nova_core.rs +6 -0
index a8f6dc22057b..f7753d63c432 100644
@@ -30,6 +30,12 @@
 mod vbios;
 mod vgpu;
 
+#[cfg(CONFIG_PCI_IOV)]
+pub use vgpu::vgpu_api::{
+    NovaCoreVfApi,
+    VgpuTypeInfo, //
+};
+
 pub(crate) const MODULE_NAME: &core::ffi::CStr = <LocalModule as kernel::ModuleMetadata>::NAME;
 
 // TODO: Move this into per-module data once that exists.
drivers/gpu/nova-core/vgpu.rs +2 -0
index 2dbf95222d4d..47a27236a086 100644
@@ -42,6 +42,8 @@
 mod instance;
 mod log;
 mod scrubber;
+#[cfg_attr(not(CONFIG_PCI_IOV), expect(dead_code, unreachable_pub))]
+pub(crate) mod vgpu_api;
 mod vram;
 
 /// vGPU state detected during GPU construction.
drivers/gpu/nova-core/vgpu/commands.rs +8 -2
index bb446d742e19..3ac03eac77e5 100644
@@ -78,7 +78,6 @@ fn check_status(dev: &device::Device<device::Bound>, command_id: u32, status: u3
 }
 
 /// Query the vGPU type assigned to a VF by its DBDF.
-#[expect(dead_code)]
 pub(super) fn query_assigned_vf_type(
     dev: &device::Device<device::Bound>,
     cmdq: &Cmdq<'_>,
@@ -95,7 +94,6 @@ pub(super) fn query_assigned_vf_type(
 }
 
 /// Query and decode the firmware properties of one vGPU type.
-#[expect(dead_code)]
 pub(super) fn query_vgpu_properties(
     dev: &device::Device<device::Bound>,
     cmdq: &Cmdq<'_>,
@@ -207,6 +205,14 @@ pub(super) fn set_plugin_bme(
     rpc.rpc_call_nvkv(dev, RpcMessage::UpdateBmeState, &bme)
 }
 
+/// Reset an active GSP plugin.
+pub(super) fn reset_plugin(
+    dev: &device::Device<device::Bound>,
+    rpc: &mut PluginRpc<'_, '_>,
+) -> Result {
+    rpc.rpc_call(dev, RpcMessage::Reset, &[])
+}
+
 /// Whether a failed allocation may still have transferred CHID ownership to firmware.
 pub(super) enum CeUtilsAllocError {
     /// A matching firmware response explicitly rejected the allocation.
drivers/gpu/nova-core/vgpu/fw/commands.rs +1 -0
index b040c8c36572..670ff2b02e7e 100644
@@ -36,6 +36,7 @@
 pub(crate) enum RpcMessage {
     VersionNegotiation = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_VERSION_NEGOTIATION,
     SetupConfigParamsAndInit = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_SETUP_CONFIG_PARAMS_AND_INIT,
+    Reset = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_RESET,
     UpdateBmeState = bindings::MESSAGE_NV_VGPU_CPU_RPC_MSG_UPDATE_BME_STATE,
 }
 
drivers/gpu/nova-core/vgpu/instance.rs +82 -11

167 lines not on this page · raw message

drivers/gpu/nova-core/vgpu/vgpu_api.rs new file +158 -0

158 lines not on this page · raw message

Signature · 1 line
2.53.0