[PATCH v2 22/32] gpu: nova-core: vgpu: encode vGPU boot requests
From: Zhi Wang
Date: Mon Sep 14 2026 - 04:15:57 EST
The vGPU boot command describes a vGPU's channel assignment, framebuffer,
plugin heap and log buffers to GSP. During vGPU instance creation,
once all resources have been allocated, this command registers them
with GSP-RM and boots the GSP plugin.
Add the NVKV request schema and encoder, including the channel map
and guest framebuffer segment arrays.
Co-developed-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
Signed-off-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
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 | 163 +++++++++++++++++++++-
3 files changed, 185 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/nova-core/gsp/nvkv.rs b/drivers/gpu/nova-core/gsp/nvkv.rs
index 9f2119b98b26..8d8dd4435354 100644
--- a/drivers/gpu/nova-core/gsp/nvkv.rs
+++ b/drivers/gpu/nova-core/gsp/nvkv.rs
@@ -168,6 +168,17 @@ pub(crate) struct Array<T: Default + Copy, 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) {
diff --git a/drivers/gpu/nova-core/gsp/nvkv/encode.rs b/drivers/gpu/nova-core/gsp/nvkv/encode.rs
index 0047be65e8a9..1f4df24d8c9c 100644
--- a/drivers/gpu/nova-core/gsp/nvkv/encode.rs
+++ b/drivers/gpu/nova-core/gsp/nvkv/encode.rs
@@ -6,6 +6,7 @@
use kernel::prelude::*;
use super::{
+ Array,
EncodedStream,
Index,
Key,
@@ -145,6 +146,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> {
#[inline(always)]
fn encode(&self, encoder: &mut Encoder) -> Result {
diff --git a/drivers/gpu/nova-core/vgpu/fw/commands.rs b/drivers/gpu/nova-core/vgpu/fw/commands.rs
index 51226bbca757..3af84c584dd6 100644
--- a/drivers/gpu/nova-core/vgpu/fw/commands.rs
+++ b/drivers/gpu/nova-core/vgpu/fw/commands.rs
@@ -8,12 +8,18 @@
use kernel::{
alloc::ArrayVec,
- bitfield, //
+ bitfield,
+ prelude::*, //
};
use crate::gsp::nvkv::{
nvkv_decode,
+ nvkv_encode,
Array,
+ Encodable,
+ EncodedStream,
+ Encoder,
+ Index,
Key,
KeyId,
Required, //
@@ -28,6 +34,161 @@ pub(in crate::vgpu) struct Dbdf(u32) {
}
}
+#[derive(Clone, Copy)]
+struct SwizzId(u32);
+
+impl SwizzId {
+ const WHOLE_GPU: Self = Self(0xFFFF_FFFF);
+}
+
+impl From<SwizzId> for u32 {
+ fn from(value: SwizzId) -> Self {
+ value.0
+ }
+}
+
+bitfield! {
+ pub(in crate::vgpu) struct ChannelMapEntry(u64) {
+ 15:0 engine_type;
+ 31:16 index;
+ 63:32 chid_offset;
+ }
+}
+
+impl ChannelMapEntry {
+ const KEY: KeyId = 0x1001;
+
+ #[expect(dead_code)]
+ pub(in crate::vgpu) fn new(engine_type: usize, index: u32, chid_offset: u32) -> Result<Self> {
+ Self::zeroed()
+ .try_with_engine_type(u64::try_from(engine_type).map_err(|_| EOVERFLOW)?)
+ .and_then(|entry| entry.try_with_index(u64::from(index)))
+ .and_then(|entry| entry.try_with_chid_offset(u64::from(chid_offset)))
+ }
+}
+
+impl Encodable for KVVec<ChannelMapEntry> {
+ fn encode(&self, encoder: &mut Encoder) -> Result {
+ // SAFETY: `ChannelMapEntry` is a `bitfield!` over `u64`, i.e.
+ // `#[repr(transparent)]` around a `u64`, so the entries are
+ // layout-compatible with `u64` and can be viewed as a `u64` slice.
+ let slice = unsafe { core::slice::from_raw_parts(self.as_ptr().cast::<u64>(), self.len()) };
+ encoder.encode_array64(ChannelMapEntry::KEY, Index::new::<0>(), slice)
+ }
+}
+
+bitfield! {
+ struct VgpuBootloadOptions(u64) {
+ }
+}
+
+nvkv_encode! {
+ struct VgpuBootloadRequest {
+ dbdf: Key<Dbdf, { Self::DBDF_KEY }, u32>,
+ gfid: Key<u32, { Self::GFID_KEY }>,
+ vgpu_type: Key<u32, { Self::VGPU_TYPE_KEY }>,
+ vm_pid: Key<u32, { Self::VM_PID_KEY }>,
+ swizz_id: Key<SwizzId, { Self::SWIZZ_ID_KEY }, u32>,
+ num_channels: Key<u32, { Self::NUM_CHANNELS_KEY }>,
+ num_plugin_channels: Key<u32, { Self::NUM_PLUGIN_CHANNELS_KEY }>,
+ guest_fb_segment_count: Key<u32, { Self::GUEST_FB_SEGMENT_COUNT_KEY }>,
+ options: Key<VgpuBootloadOptions, { Self::OPTIONS_KEY }, u64>,
+ channel_mapping: KVVec<ChannelMapEntry>,
+ guest_fb_segment_phys_addr: Array<u64, 8, { Self::GUEST_FB_SEGMENT_PHYS_ADDR_KEY }>,
+ guest_fb_segment_length: Array<u64, 8, { Self::GUEST_FB_SEGMENT_LENGTH_KEY }>,
+ plugin_heap_phys_addr: Key<u64, { Self::PLUGIN_HEAP_PHYS_ADDR_KEY }>,
+ plugin_heap_length: Key<u64, { Self::PLUGIN_HEAP_LENGTH_KEY }>,
+ ctrl_buff_offset: Key<u64, { Self::CTRL_BUFF_OFFSET_KEY }>,
+ init_task_log_offset: Key<u64, { Self::INIT_TASK_LOG_OFFSET_KEY }>,
+ init_task_log_size: Key<u64, { Self::INIT_TASK_LOG_SIZE_KEY }>,
+ vgpu_task_log_offset: Key<u64, { Self::VGPU_TASK_LOG_OFFSET_KEY }>,
+ vgpu_task_log_size: Key<u64, { Self::VGPU_TASK_LOG_SIZE_KEY }>,
+ kernel_log_offset: Key<u64, { Self::KERNEL_LOG_OFFSET_KEY }>,
+ kernel_log_size: Key<u64, { Self::KERNEL_LOG_SIZE_KEY }>,
+ mig_rm_heap_phys_addr: Key<u64, { Self::MIG_RM_HEAP_PHYS_ADDR_KEY }>,
+ mig_rm_heap_length: Key<u64, { Self::MIG_RM_HEAP_LENGTH_KEY }>,
+ }
+}
+
+impl VgpuBootloadRequest {
+ const DBDF_KEY: KeyId = 0x0001;
+ const GFID_KEY: KeyId = 0x0002;
+ const VGPU_TYPE_KEY: KeyId = 0x0003;
+ const VM_PID_KEY: KeyId = 0x0004;
+ const SWIZZ_ID_KEY: KeyId = 0x0005;
+ const NUM_CHANNELS_KEY: KeyId = 0x0006;
+ const NUM_PLUGIN_CHANNELS_KEY: KeyId = 0x0007;
+ const GUEST_FB_SEGMENT_COUNT_KEY: KeyId = 0x0008;
+ const OPTIONS_KEY: KeyId = 0x1000;
+ const GUEST_FB_SEGMENT_PHYS_ADDR_KEY: KeyId = 0x1002;
+ const GUEST_FB_SEGMENT_LENGTH_KEY: KeyId = 0x1003;
+ const PLUGIN_HEAP_PHYS_ADDR_KEY: KeyId = 0x1004;
+ const PLUGIN_HEAP_LENGTH_KEY: KeyId = 0x1005;
+ const CTRL_BUFF_OFFSET_KEY: KeyId = 0x1006;
+ const INIT_TASK_LOG_OFFSET_KEY: KeyId = 0x1007;
+ const INIT_TASK_LOG_SIZE_KEY: KeyId = 0x1008;
+ const VGPU_TASK_LOG_OFFSET_KEY: KeyId = 0x1009;
+ const VGPU_TASK_LOG_SIZE_KEY: KeyId = 0x100A;
+ const KERNEL_LOG_OFFSET_KEY: KeyId = 0x100B;
+ const KERNEL_LOG_SIZE_KEY: KeyId = 0x100C;
+ const MIG_RM_HEAP_PHYS_ADDR_KEY: KeyId = 0x100D;
+ const MIG_RM_HEAP_LENGTH_KEY: KeyId = 0x100E;
+}
+
+/// Encodes a `VGPU_BOOTLOAD` request using the typed NVKV schema.
+#[expect(dead_code)]
+#[expect(clippy::too_many_arguments)]
+pub(in crate::vgpu) fn encode_vgpu_bootload(
+ dbdf: Dbdf,
+ gfid: u32,
+ vgpu_type: u32,
+ vm_pid: u32,
+ num_channels: u32,
+ num_plugin_channels: u32,
+ channel_mapping: KVVec<ChannelMapEntry>,
+ guest_fb_address: u64,
+ guest_fb_length: u64,
+ plugin_heap_address: u64,
+ plugin_heap_length: u64,
+ ctrl_buffer_offset: u64,
+ init_log_address: u64,
+ init_log_size: u64,
+ vgpu_log_address: u64,
+ vgpu_log_size: u64,
+ kernel_log_address: u64,
+ kernel_log_size: u64,
+) -> Result<EncodedStream> {
+ let request = VgpuBootloadRequest {
+ dbdf: dbdf.into(),
+ gfid: gfid.into(),
+ vgpu_type: vgpu_type.into(),
+ vm_pid: vm_pid.into(),
+ swizz_id: SwizzId::WHOLE_GPU.into(),
+ num_channels: num_channels.into(),
+ num_plugin_channels: num_plugin_channels.into(),
+ guest_fb_segment_count: 1.into(),
+ options: VgpuBootloadOptions::zeroed().into(),
+ channel_mapping,
+ guest_fb_segment_phys_addr: Array::new(&[guest_fb_address])?,
+ guest_fb_segment_length: Array::new(&[guest_fb_length])?,
+ plugin_heap_phys_addr: plugin_heap_address.into(),
+ plugin_heap_length: plugin_heap_length.into(),
+ ctrl_buff_offset: ctrl_buffer_offset.into(),
+ init_task_log_offset: init_log_address.into(),
+ init_task_log_size: init_log_size.into(),
+ vgpu_task_log_offset: vgpu_log_address.into(),
+ vgpu_task_log_size: vgpu_log_size.into(),
+ kernel_log_offset: kernel_log_address.into(),
+ kernel_log_size: kernel_log_size.into(),
+ mig_rm_heap_phys_addr: 0.into(),
+ mig_rm_heap_length: 0.into(),
+ };
+
+ let mut encoder = Encoder::new();
+ request.encode(&mut encoder)?;
+ Ok(encoder.finish())
+}
+
nvkv_decode! {
pub(in crate::vgpu) struct VgpuPropertiesSchema => VgpuProperties {
// TODO: `name`/`class` required?