[PATCH v3 20/31] gpu: nova-core: vgpu: add GSP plugin communication buffers

From: Zhi Wang

Date: Mon Sep 28 2026 - 06:32:24 EST


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.

Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
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

diff --git a/drivers/gpu/nova-core/mm.rs b/drivers/gpu/nova-core/mm.rs
index a1e0bbffc460..72e96a1dde5a 100644
--- a/drivers/gpu/nova-core/mm.rs
+++ b/drivers/gpu/nova-core/mm.rs
@@ -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.
diff --git a/drivers/gpu/nova-core/vgpu.rs b/drivers/gpu/nova-core/vgpu.rs
index fb4f1b5f7754..024cb13694f4 100644
--- a/drivers/gpu/nova-core/vgpu.rs
+++ b/drivers/gpu/nova-core/vgpu.rs
@@ -24,6 +24,7 @@

mod commands;
mod fw;
+mod gsp_plugin_comm;
mod hal;
mod instance;
mod vram;
diff --git a/drivers/gpu/nova-core/vgpu/fw.rs b/drivers/gpu/nova-core/vgpu/fw.rs
index 23097cb13121..aabe114b55c2 100644
--- a/drivers/gpu/nova-core/vgpu/fw.rs
+++ b/drivers/gpu/nova-core/vgpu/fw.rs
@@ -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;

diff --git a/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs b/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
new file mode 100644
index 000000000000..b945e216a1d6
--- /dev/null
+++ b/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! GSP plugin communication buffer mappings and access.
+
+use kernel::{
+ num::casts::u32_as_usize,
+ prelude::*,
+ sync::Mutex, //
+};
+
+use crate::mm::{
+ bar_user::{
+ BarMapping,
+ BarUser, //
+ },
+ vram::VramRegion,
+ GpuMm, //
+};
+
+use super::fw::{
+ self,
+ RawControlRegion, //
+};
+
+static_assert!(
+ fw::VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_INIT_TASK_LOG_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE
+ + fw::VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE
+ == fw::VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE
+);
+static_assert!(
+ 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,
+ pub(super) kernel: VramRegion,
+}
+
+fn take_region(region: &VramRegion, cursor: &mut u64, size: u32) -> Result<VramRegion> {
+ let end = cursor.checked_add(u64::from(size)).ok_or(EOVERFLOW)?;
+ let subregion = region.subregion(*cursor..end)?;
+ *cursor = end;
+ Ok(subregion)
+}
+
+/// BAR1 mapping of the plugin communication region in its management heap.
+///
+/// r000 layout, with byte offsets from the management heap (not to scale):
+///
+/// ```text
+/// 0x000000 +----------------------------------------+
+/// | Control (boot-ready marker) 4 KiB |
+/// 0x001000 +----------------------------------------+
+/// | Response 4 KiB |
+/// 0x002000 +----------------------------------------+
+/// | Message 4 KiB |
+/// 0x003000 +----------------------------------------+
+/// | Migration 2 MiB |
+/// 0x203000 +----------------------------------------+
+/// | Error 4 KiB |
+/// 0x204000 +----------------------------------------+
+/// | Init task log 128 KiB |
+/// 0x224000 +----------------------------------------+
+/// | vGPU task log 256 KiB |
+/// 0x264000 +----------------------------------------+
+/// | Kernel task log 64 KiB |
+/// 0x274000 +----------------------------------------+
+/// | Guest RPC trace 64 KiB |
+/// 0x284000 +----------------------------------------+
+/// ```
+pub(super) struct CommBufferRegion<'map, 'gpu> {
+ map: BarMapping<'map, 'gpu>,
+ control: VramRegion,
+ init_log: VramRegion,
+ vgpu_log: VramRegion,
+ 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(
+ bar_user: &'map BarUser<'gpu>,
+ mm: &'map Mutex<GpuMm<'gpu>>,
+ management_heap: &VramRegion,
+ ) -> Result<Self> {
+ let total_size = u64::from(fw::VGPU_CPU_GSP_COMMUNICATION_BUFF_TOTAL_SIZE);
+ let region = management_heap.subregion(0..total_size)?;
+ let mut cursor = 0;
+
+ let control = take_region(&region, &mut cursor, fw::VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE)?;
+ take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE,
+ )?;
+ take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE,
+ )?;
+ take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE,
+ )?;
+ take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE,
+ )?;
+ let init_log = take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_INIT_TASK_LOG_BUFF_REGION_SIZE,
+ )?;
+ let vgpu_log = take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_VGPU_TASK_LOG_BUFF_REGION_SIZE,
+ )?;
+ let kernel_log = take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE,
+ )?;
+ take_region(
+ &region,
+ &mut cursor,
+ fw::VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE,
+ )?;
+
+ let map = BarMapping::new(bar_user, mm, region, true)?;
+
+ Ok(Self {
+ map,
+ control,
+ init_log,
+ vgpu_log,
+ kernel_log,
+ })
+ }
+
+ fn region_offset(&self, region: &VramRegion) -> Result<usize> {
+ let offset = region
+ .address()
+ .checked_sub(self.map.region().address())
+ .ok_or(EINVAL)?;
+ if offset.checked_add(region.size()).ok_or(EOVERFLOW)? > self.map.region().size() {
+ return Err(EINVAL);
+ }
+
+ usize::try_from(offset).map_err(|_| EOVERFLOW)
+ }
+
+ fn io_offset(&self, region: &VramRegion, field: usize, width: usize) -> Result<usize> {
+ let field_end = field.checked_add(width).ok_or(EOVERFLOW)?;
+ if u64::try_from(field_end).map_err(|_| EOVERFLOW)? > region.size() {
+ return Err(EINVAL);
+ }
+
+ self.region_offset(region)?
+ .checked_add(field)
+ .ok_or(EOVERFLOW)
+ }
+
+ fn read_u32(&self, region: &VramRegion, field: usize) -> Result<u32> {
+ self.map
+ .try_read32(self.io_offset(region, field, size_of::<u32>())?)
+ }
+
+ /// Return the physical regions occupied by the three plugin logs.
+ pub(super) fn plugin_logs(&self) -> PluginLogRegions {
+ PluginLogRegions {
+ init: self.init_log.clone(),
+ vgpu: self.vgpu_log.clone(),
+ kernel: self.kernel_log.clone(),
+ }
+ }
+
+ /// Return whether firmware has published the plugin boot marker.
+ pub(super) fn is_plugin_ready(&self) -> Result<bool> {
+ let value = self.read_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.message_seq_num),
+ )?;
+
+ Ok(value == fw::GSP_PLUGIN_BOOTLOADED)
+ }
+
+ /// Invalidate the PTEs and release the communication mapping.
+ pub(super) fn unmap(&mut self) -> Result {
+ self.map.unmap()
+ }
+}