[PATCH v3 28/31] gpu: nova-core: vgpu: add CeUtils commands

From: Zhi Wang

Date: Mon Sep 28 2026 - 06:39:12 EST


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.

Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
.../gpu/nova-core/gsp/fw/r000_00/bindings.rs | 1 +
drivers/gpu/nova-core/vgpu/commands.rs | 143 +++++++++++++++++-
drivers/gpu/nova-core/vgpu/fw.rs | 11 ++
drivers/gpu/nova-core/vgpu/fw/commands.rs | 52 ++++++-
4 files changed, 197 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs b/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
index 3cc718bb2ab8..333256ea8a04 100644
--- a/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
+++ b/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
@@ -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;
diff --git a/drivers/gpu/nova-core/vgpu/commands.rs b/drivers/gpu/nova-core/vgpu/commands.rs
index bfb033e5eb37..704d13259cf3 100644
--- a/drivers/gpu/nova-core/vgpu/commands.rs
+++ b/drivers/gpu/nova-core/vgpu/commands.rs
@@ -8,33 +8,49 @@

use kernel::{
device,
+ num::casts::{
+ usize_as_u64,
+ usize_into_u32, //
+ },
prelude::*,
time::Delta,
transmute::AsBytes, //
};

-use crate::gsp::{
- cmdq::Cmdq,
- nvkv::{
- nvkv_words,
- Decoder,
- UnknownKeyPolicy, //
- }, //
+use crate::{
+ gsp::{
+ cmdq::Cmdq,
+ nvkv::{
+ nvkv_words,
+ Decoder,
+ UnknownKeyPolicy, //
+ }, //
+ },
+ mm::PAGE_SIZE, //
};

use super::{
fw::{
commands::{
encode_plugin_set_bme,
+ AllocCeutilsRequest,
+ AllocCeutilsResponse,
+ FreeCeutilsRequest,
+ ScrubGuestFbRequest,
+ ScrubGuestFbResponse,
VgpuPropertiesSchema, //
},
- RpcMessage, //
+ 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_VGPU_MGR_ALLOC_GSP_CEUTILS,
+ GMCAPI_CMD_VGPU_MGR_FREE_GSP_CEUTILS,
+ GMCAPI_CMD_VGPU_MGR_SCRUB_GUEST_FB,
+ NV_ADDR_FBMEM, //
},
gsp_plugin_rpc::PluginRpc,
instance::Gfid, //
@@ -189,3 +205,114 @@ pub(super) fn set_plugin_bme(
let bme = encode_plugin_set_bme(enable)?;
rpc.rpc_call_nvkv(dev, RpcMessage::UpdateBmeState, &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),
+ /// The request may have completed despite a transport or response-validation error.
+ MayOwn(Error),
+}
+
+/// Allocate a CeUtils channel and validate its semaphore description.
+#[expect(dead_code)]
+pub(super) fn alloc_ceutils(
+ dev: &device::Device<device::Bound>,
+ cmdq: &Cmdq<'_>,
+ gfid: Gfid,
+ chid: u32,
+) -> core::result::Result<u64, CeUtilsAllocError> {
+ let request = AllocCeutilsRequest {
+ gfid: u32::from(gfid.get()).to_le(),
+ fixed_chid: chid.to_le(),
+ force_ceid: u32::MAX.to_le(),
+ swizz_id: 0,
+ };
+
+ dev_dbg!(dev, "alloc CeUtils: gfid={} chid={}\n", gfid.get(), chid,);
+
+ let command_id = GMCAPI_CMD_VGPU_MGR_ALLOC_GSP_CEUTILS;
+ let response = cmdq
+ .send_gmc_and_receive(
+ command_id,
+ IntoBytes::as_bytes(&request),
+ usize_into_u32::<{ size_of::<AllocCeutilsResponse>() }>(),
+ )
+ .map_err(CeUtilsAllocError::MayOwn)?;
+ check_status(dev, command_id, response.status).map_err(CeUtilsAllocError::NotOwned)?;
+ let (response, _) = AllocCeutilsResponse::read_from_prefix(&response.payload)
+ .map_err(|_| CeUtilsAllocError::MayOwn(EMSGSIZE))?;
+
+ let semaphore_address = u64::from_le(response.semaphore_address);
+ let semaphore_aperture = u32::from_le(response.semaphore_aperture);
+ let page_size = usize_as_u64(PAGE_SIZE);
+
+ if semaphore_address == 0
+ || !semaphore_address.is_multiple_of(page_size)
+ || semaphore_aperture != NV_ADDR_FBMEM
+ {
+ return Err(CeUtilsAllocError::MayOwn(EINVAL));
+ }
+
+ dev_dbg!(
+ dev,
+ "alloc CeUtils: gfid={} semaphore={:#x}\n",
+ gfid.get(),
+ semaphore_address,
+ );
+ Ok(semaphore_address)
+}
+
+/// 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<'_>,
+ gfid: Gfid,
+) -> Result {
+ let request = FreeCeutilsRequest {
+ gfid: u32::from(gfid.get()).to_le(),
+ };
+
+ dev_dbg!(dev, "free CeUtils: gfid={}\n", gfid.get());
+ let command_id = GMCAPI_CMD_VGPU_MGR_FREE_GSP_CEUTILS;
+ let response = cmdq.send_gmc_and_receive(command_id, IntoBytes::as_bytes(&request), 0)?;
+ check_status(dev, command_id, response.status)
+}
+
+/// 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<'_>,
+ gfid: Gfid,
+ fb_offset: u64,
+ fb_size: u64,
+) -> Result<u32> {
+ let request = ScrubGuestFbRequest {
+ gfid: u32::from(gfid.get()).to_le(),
+ reserved: 0,
+ fb_offset: fb_offset.to_le(),
+ fb_size: fb_size.to_le(),
+ };
+
+ dev_dbg!(
+ dev,
+ "submit scrub: gfid={} offset={:#x} size={:#x}\n",
+ gfid.get(),
+ fb_offset,
+ fb_size,
+ );
+
+ let command_id = GMCAPI_CMD_VGPU_MGR_SCRUB_GUEST_FB;
+ let response = cmdq.send_gmc_and_receive(
+ command_id,
+ IntoBytes::as_bytes(&request),
+ usize_into_u32::<{ size_of::<ScrubGuestFbResponse>() }>(),
+ )?;
+ check_status(dev, command_id, response.status)?;
+ let (response, _) =
+ ScrubGuestFbResponse::read_from_prefix(&response.payload).map_err(|_| EMSGSIZE)?;
+ u32::try_from(u64::from_le(response.work_id)).map_err(|_| EOVERFLOW)
+}
diff --git a/drivers/gpu/nova-core/vgpu/fw.rs b/drivers/gpu/nova-core/vgpu/fw.rs
index 6cec92c9b505..795ce4855586 100644
--- a/drivers/gpu/nova-core/vgpu/fw.rs
+++ b/drivers/gpu/nova-core/vgpu/fw.rs
@@ -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 {
diff --git a/drivers/gpu/nova-core/vgpu/fw/commands.rs b/drivers/gpu/nova-core/vgpu/fw/commands.rs
index cc2298c3679c..ad2b29340986 100644
--- a/drivers/gpu/nova-core/vgpu/fw/commands.rs
+++ b/drivers/gpu/nova-core/vgpu/fw/commands.rs
@@ -21,10 +21,10 @@
Encodable,
EncodedStream,
Encoder,
- Index, //
+ Index,
Key,
KeyId,
- Required,
+ Required, //
},
mm::vram::VramRegion, //
};
@@ -420,3 +420,51 @@ pub(crate) fn encode_plugin_set_bme(enable: bool) -> Result<EncodedStream> {
request.encode(&mut encoder)?;
Ok(encoder.finish())
}
+
+#[repr(C)]
+#[derive(IntoBytes, zerocopy_derive::Immutable)]
+pub(crate) struct AllocCeutilsRequest {
+ pub(crate) gfid: u32,
+ pub(crate) fixed_chid: u32,
+ pub(crate) force_ceid: u32,
+ pub(crate) swizz_id: u32,
+}
+
+static_assert!(size_of::<AllocCeutilsRequest>() == 16);
+
+#[repr(C)]
+#[derive(FromBytes)]
+pub(crate) struct AllocCeutilsResponse {
+ pub(crate) semaphore_address: u64,
+ pub(crate) semaphore_aperture: u32,
+ _reserved: u32,
+}
+
+static_assert!(size_of::<AllocCeutilsResponse>() == 16);
+
+#[repr(C)]
+#[derive(IntoBytes, zerocopy_derive::Immutable)]
+pub(crate) struct FreeCeutilsRequest {
+ pub(crate) gfid: u32,
+}
+
+static_assert!(size_of::<FreeCeutilsRequest>() == 4);
+
+#[repr(C)]
+#[derive(IntoBytes, zerocopy_derive::Immutable)]
+pub(crate) struct ScrubGuestFbRequest {
+ pub(crate) gfid: u32,
+ pub(crate) reserved: u32,
+ pub(crate) fb_offset: u64,
+ pub(crate) fb_size: u64,
+}
+
+static_assert!(size_of::<ScrubGuestFbRequest>() == 24);
+
+#[repr(C)]
+#[derive(FromBytes)]
+pub(crate) struct ScrubGuestFbResponse {
+ pub(crate) work_id: u64,
+}
+
+static_assert!(size_of::<ScrubGuestFbResponse>() == 8);