[PATCH v3 23/31] gpu: nova-core: vgpu: initialize GSP plugin RPC buffers

From: Zhi Wang

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


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.

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

diff --git a/drivers/gpu/nova-core/mm/bar_user.rs b/drivers/gpu/nova-core/mm/bar_user.rs
index 964aee8bfdb1..bcbef1571fb9 100644
--- a/drivers/gpu/nova-core/mm/bar_user.rs
+++ b/drivers/gpu/nova-core/mm/bar_user.rs
@@ -150,6 +150,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)?;
@@ -283,6 +288,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>())?)
diff --git a/drivers/gpu/nova-core/vgpu/fw.rs b/drivers/gpu/nova-core/vgpu/fw.rs
index 1528cc56ce74..03225b9833c3 100644
--- a/drivers/gpu/nova-core/vgpu/fw.rs
+++ b/drivers/gpu/nova-core/vgpu/fw.rs
@@ -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, //
};
diff --git a/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs b/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
index a00f4d2affc7..31da31de4fd2 100644
--- a/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
+++ b/drivers/gpu/nova-core/vgpu/gsp_plugin_comm.rs
@@ -20,7 +20,8 @@

use super::fw::{
self,
- RawControlRegion, //
+ RawControlRegion,
+ RawResponseRegion, //
};

static_assert!(
@@ -38,6 +39,10 @@
static_assert!(
size_of::<RawControlRegion>() == u32_as_usize(fw::VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE)
);
+static_assert!(
+ size_of::<RawResponseRegion>() == u32_as_usize(fw::VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE)
+);
+
/// Physical VRAM regions containing the vGPU plugin logs.
pub(super) struct PluginLogRegions {
pub(super) init: VramRegion,
@@ -80,9 +85,14 @@ fn take_region(region: &VramRegion, cursor: &mut u64, size: u32) -> Result<VramR
pub(super) struct CommBufferRegion<'map, 'gpu> {
map: BarMapping<'map, 'gpu>,
control: VramRegion,
+ response: VramRegion,
+ message: VramRegion,
+ migration: VramRegion,
+ error: VramRegion,
init_log: VramRegion,
vgpu_log: VramRegion,
kernel_log: VramRegion,
+ guest_trace: VramRegion,
}

impl<'map, 'gpu> CommBufferRegion<'map, 'gpu> {
@@ -97,22 +107,22 @@ pub(super) fn new(
let mut cursor = 0;

let control = take_region(&region, &mut cursor, fw::VGPU_CPU_GSP_CTRL_BUFF_REGION_SIZE)?;
- take_region(
+ let response = take_region(
&region,
&mut cursor,
fw::VGPU_CPU_GSP_RESPONSE_BUFF_REGION_SIZE,
)?;
- take_region(
+ let message = take_region(
&region,
&mut cursor,
fw::VGPU_CPU_GSP_MESSAGE_BUFF_REGION_SIZE,
)?;
- take_region(
+ let migration = take_region(
&region,
&mut cursor,
fw::VGPU_CPU_GSP_MIGRATION_BUFF_REGION_SIZE,
)?;
- take_region(
+ let error = take_region(
&region,
&mut cursor,
fw::VGPU_CPU_GSP_ERROR_BUFF_REGION_SIZE,
@@ -132,7 +142,7 @@ pub(super) fn new(
&mut cursor,
fw::VGPU_CPU_GSP_KERNEL_TASK_LOG_BUFF_REGION_SIZE,
)?;
- take_region(
+ let guest_trace = take_region(
&region,
&mut cursor,
fw::VGPU_CPU_GSP_GUEST_RPC_TRACE_BUFF_REGION_SIZE,
@@ -143,9 +153,14 @@ pub(super) fn new(
Ok(Self {
map,
control,
+ response,
+ message,
+ migration,
+ error,
init_log,
vgpu_log,
kernel_log,
+ guest_trace,
})
}

@@ -177,6 +192,21 @@ fn read_u32(&self, region: &VramRegion, field: usize) -> Result<u32> {
.try_read32(self.io_offset(region, field, size_of::<u32>())?)
}

+ fn write_u8(&self, region: &VramRegion, field: usize, value: u8) -> Result {
+ self.map
+ .try_write8(value, self.io_offset(region, field, size_of::<u8>())?)
+ }
+
+ fn write_u32(&self, region: &VramRegion, field: usize, value: u32) -> Result {
+ self.map
+ .try_write32(value, self.io_offset(region, field, size_of::<u32>())?)
+ }
+
+ fn write_u64(&self, region: &VramRegion, field: usize, value: u64) -> Result {
+ self.map
+ .try_write64(value, self.io_offset(region, field, size_of::<u64>())?)
+ }
+
/// Return the physical regions occupied by the three plugin logs.
pub(super) fn plugin_logs(&self) -> PluginLogRegions {
PluginLogRegions {
@@ -209,6 +239,105 @@ pub(super) fn is_plugin_ready(&self) -> Result<bool> {
Ok(value == fw::GSP_PLUGIN_BOOTLOADED)
}

+ /// Initialize the shared control and response buffers for plugin RPC.
+ #[expect(dead_code)]
+ pub(super) fn initialize(&self) -> Result {
+ self.write_u64(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.response_buff_offset),
+ u64::try_from(self.region_offset(&self.response)?)?,
+ )?;
+ self.write_u64(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.message_buff_offset),
+ u64::try_from(self.region_offset(&self.message)?)?,
+ )?;
+ self.write_u64(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.migration_buff_offset),
+ u64::try_from(self.region_offset(&self.migration)?)?,
+ )?;
+ self.write_u64(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.error_buff_offset),
+ u64::try_from(self.region_offset(&self.error)?)?,
+ )?;
+ self.write_u64(
+ &self.control,
+ core::mem::offset_of!(
+ RawControlRegion,
+ __bindgen_anon_1.guest_rpc_trace_buff_offset
+ ),
+ u64::try_from(self.region_offset(&self.guest_trace)?)?,
+ )?;
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(
+ RawControlRegion,
+ __bindgen_anon_1.migration_buf_cpu_access_offset
+ ),
+ 0,
+ )?;
+ self.write_u8(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.is_migration_in_progress),
+ 0,
+ )?;
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.error_buff_cpu_get_idx),
+ 0,
+ )?;
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(
+ RawControlRegion,
+ __bindgen_anon_1.guest_rpc_trace_buff_cpu_get_idx
+ ),
+ 0,
+ )?;
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.attached_vgpu_count),
+ 1,
+ )?;
+ self.write_u8(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.is_gr_init_done),
+ 0,
+ )?;
+
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.message_type),
+ 0,
+ )?;
+ // Replace the boot-ready marker with the initial RPC sequence.
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.message_seq_num),
+ 0,
+ )?;
+ self.write_u32(
+ &self.response,
+ core::mem::offset_of!(
+ RawResponseRegion,
+ __bindgen_anon_1.message_seq_num_processed
+ ),
+ 0,
+ )?;
+ self.write_u32(
+ &self.response,
+ core::mem::offset_of!(RawResponseRegion, __bindgen_anon_1.result_code),
+ 0,
+ )?;
+ self.write_u32(
+ &self.control,
+ core::mem::offset_of!(RawControlRegion, __bindgen_anon_1.version),
+ fw::VGPU_CPU_GSP_CTRL_BUFF_VERSION,
+ )
+ }
+
/// Invalidate the PTEs and release the communication mapping.
pub(super) fn unmap(&mut self) -> Result {
self.map.unmap()