* [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:51 ` [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module John Hubbard
` (25 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
"r000" means "latest firmware", without getting bogged down into naming
actual versions, because those will vary. The story is: nova uses
r570 at this point, but will transition soon to some later version, and
that version will have a stable ABI, which in turn means that the
firmware file names will stop including version numbers in the file name
itself. Version information will still be recorded inside each firmware
image, though.
The *_SI_* items are for key-value access to "System Info" items that
nova will received from an early GSP-RM RPC call.
Add generated firmware bindings from the r000 Open RM headers. The
module is registered but not yet wired as the active binding set.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/fw.rs | 1 +
drivers/gpu/nova-core/gsp/fw/r000_00.rs | 31 +
.../gpu/nova-core/gsp/fw/r000_00/bindings.rs | 1288 +++++++++++++++++
3 files changed, 1320 insertions(+)
create mode 100644 drivers/gpu/nova-core/gsp/fw/r000_00.rs
create mode 100644 drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 0b01c81ec092..9b29308678a7 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
pub(crate) mod commands;
+mod r000_00;
mod r570_144;
// Alias to avoid repeating the version number with every use.
diff --git a/drivers/gpu/nova-core/gsp/fw/r000_00.rs b/drivers/gpu/nova-core/gsp/fw/r000_00.rs
new file mode 100644
index 000000000000..095e5dddf6cf
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/fw/r000_00.rs
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Firmware bindings.
+//!
+//! Imports the generated bindings by `bindgen`.
+//!
+//! This module may not be directly used. Please abstract or re-export the needed symbols in the
+//! parent module instead.
+
+#![allow(
+ dead_code,
+ clippy::all,
+ clippy::undocumented_unsafe_blocks,
+ clippy::ptr_as_ptr,
+ clippy::ref_as_ptr,
+ missing_docs,
+ non_camel_case_types,
+ non_upper_case_globals,
+ non_snake_case,
+ improper_ctypes,
+ unreachable_pub,
+ unsafe_op_in_unsafe_fn
+)]
+use kernel::ffi;
+use pin_init::MaybeZeroable;
+
+include!("r000_00/bindings.rs");
+
+// SAFETY: This type has a size of zero, so its inclusion into another type should not affect their
+// ability to implement `Zeroable`.
+unsafe impl<T> kernel::prelude::Zeroable for __IncompleteArrayField<T> {}
diff --git a/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs b/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
new file mode 100644
index 000000000000..e6bab20baa65
--- /dev/null
+++ b/drivers/gpu/nova-core/gsp/fw/r000_00/bindings.rs
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::core::marker::PhantomData<T>, [T; 0]);
+impl<T> __IncompleteArrayField<T> {
+ #[inline]
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::core::marker::PhantomData, [])
+ }
+ #[inline]
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
+ }
+ #[inline]
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
+ }
+ #[inline]
+ pub unsafe fn as_slice(&self, len: usize) -> &[T] {
+ ::core::slice::from_raw_parts(self.as_ptr(), len)
+ }
+ #[inline]
+ pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
+ ::core::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
+ }
+}
+impl<T> ::core::fmt::Debug for __IncompleteArrayField<T> {
+ fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
+ fmt.write_str("__IncompleteArrayField")
+ }
+}
+#[repr(C)]
+pub struct __BindgenUnionField<T>(::core::marker::PhantomData<T>);
+impl<T> __BindgenUnionField<T> {
+ #[inline]
+ pub const fn new() -> Self {
+ __BindgenUnionField(::core::marker::PhantomData)
+ }
+ #[inline]
+ pub unsafe fn as_ref(&self) -> &T {
+ ::core::mem::transmute(self)
+ }
+ #[inline]
+ pub unsafe fn as_mut(&mut self) -> &mut T {
+ ::core::mem::transmute(self)
+ }
+}
+impl<T> ::core::default::Default for __BindgenUnionField<T> {
+ #[inline]
+ fn default() -> Self {
+ Self::new()
+ }
+}
+impl<T> ::core::clone::Clone for __BindgenUnionField<T> {
+ #[inline]
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+impl<T> ::core::marker::Copy for __BindgenUnionField<T> {}
+impl<T> ::core::fmt::Debug for __BindgenUnionField<T> {
+ fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
+ fmt.write_str("__BindgenUnionField")
+ }
+}
+impl<T> ::core::hash::Hash for __BindgenUnionField<T> {
+ fn hash<H: ::core::hash::Hasher>(&self, _state: &mut H) {}
+}
+impl<T> ::core::cmp::PartialEq for __BindgenUnionField<T> {
+ fn eq(&self, _other: &__BindgenUnionField<T>) -> bool {
+ true
+ }
+}
+impl<T> ::core::cmp::Eq for __BindgenUnionField<T> {}
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0: u32 = 0;
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3: u32 = 3;
+pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_7: u32 = 7;
+pub const NV_VGPU_MSG_SIGNATURE_VALID: u32 = 1129337430;
+pub const NVGMC_SI_PCI_DEVICE_ID: u32 = 1;
+pub const NVGMC_SI_PCI_SUB_DEVICE_ID: u32 = 2;
+pub const NVGMC_SI_PCI_REVISION_ID: u32 = 3;
+pub const NVGMC_SI_PCI_CONFIG_MIRROR_BASE: u32 = 16;
+pub const NVGMC_SI_PCI_CONFIG_MIRROR_SIZE: u32 = 17;
+pub const NVGMC_SI_PCIE_ATOMICS_CPL_DEV_CAP_MASK: u32 = 32;
+pub const NVGMC_SI_CHIPSET: u32 = 48;
+pub const NVGMC_SI_CHIPSET_L1SS_ENABLE: u32 = 49;
+pub const NVGMC_SI_HYPERVISOR_TYPE: u32 = 64;
+pub const NVGMC_SI_VIRTUAL_CONFIG_BITS: u32 = 65;
+pub const NVGMC_SI_PCIE_POWER_CONTROL_VALUE: u32 = 80;
+pub const NVGMC_SI_PF0_DEVICE_CONTROL2_REG: u32 = 81;
+pub const NVGMC_SI_PCIE_LINK_CAP: u32 = 82;
+pub const NVGMC_SI_GRID_BUILD_CSP: u32 = 96;
+pub const NVGMC_SI_HOST_ARCH: u32 = 112;
+pub const NVGMC_SI_HOST_ARCH_NONE: u32 = 0;
+pub const NVGMC_SI_HOST_ARCH_X86_64: u32 = 1;
+pub const NVGMC_SI_HOST_ARCH_PPC64LE: u32 = 2;
+pub const NVGMC_SI_HOST_ARCH_ARM: u32 = 3;
+pub const NVGMC_SI_HOST_ARCH_AARCH64: u32 = 4;
+pub const NVGMC_SI_HOST_ARCH_RISCV64: u32 = 5;
+pub const NVGMC_SI_HOST_OS_TYPE: u32 = 113;
+pub const NVGMC_SI_HOST_OS_TYPE_OTHER: u32 = 0;
+pub const NVGMC_SI_HOST_OS_TYPE_UNIX: u32 = 1;
+pub const NVGMC_SI_HOST_OS_TYPE_WINDOWS: u32 = 2;
+pub const NVGMC_SI_HOST_PAGE_SIZE: u32 = 120;
+pub const NVGMC_SI_VF_TOTAL_VFS: u32 = 128;
+pub const NVGMC_SI_VF_FIRST_VF_OFFSET: u32 = 129;
+pub const NVGMC_SI_FHB_BUSINFO_REVISION_ID: u32 = 144;
+pub const NVGMC_SI_CHIPSET_BUSINFO_REVISION_ID: u32 = 145;
+pub const NVGMC_SI_ACPI_DOD_STATUS: u32 = 512;
+pub const NVGMC_SI_ACPI_DOD_ACPI_IDS: u32 = 528;
+pub const NVGMC_SI_ACPI_JT_STATUS: u32 = 544;
+pub const NVGMC_SI_ACPI_JT_CAPS: u32 = 545;
+pub const NVGMC_SI_ACPI_JT_REV_ID: u32 = 546;
+pub const NVGMC_SI_ACPI_JT_B_SBIOS_CAPS: u32 = 547;
+pub const NVGMC_SI_ACPI_MUX_TABLE_LEN: u32 = 560;
+pub const NVGMC_SI_ACPI_MUX_MODE_ACPI_ID: u32 = 576;
+pub const NVGMC_SI_ACPI_MUX_MODE_MODE: u32 = 577;
+pub const NVGMC_SI_ACPI_MUX_MODE_STATUS: u32 = 578;
+pub const NVGMC_SI_ACPI_MUX_PART_ACPI_ID: u32 = 592;
+pub const NVGMC_SI_ACPI_MUX_PART_MODE: u32 = 593;
+pub const NVGMC_SI_ACPI_MUX_PART_STATUS: u32 = 594;
+pub const NVGMC_SI_ACPI_MUX_STATE_ACPI_ID: u32 = 608;
+pub const NVGMC_SI_ACPI_MUX_STATE_MODE: u32 = 609;
+pub const NVGMC_SI_ACPI_MUX_STATE_STATUS: u32 = 610;
+pub const NVGMC_SI_ACPI_CAPS_STATUS: u32 = 624;
+pub const NVGMC_SI_ACPI_CAPS_OPTIMUS_CAPS: u32 = 625;
+pub const NVGMC_SI_PCIE_FLAGS: u32 = 4096;
+pub const NVGMC_SI_PLATFORM_FLAGS: u32 = 4097;
+pub const NVGMC_SI_FEATURE_FLAGS: u32 = 4098;
+pub const NVGMC_SI_VF_FLAGS: u32 = 4099;
+pub const NVGMC_SI_GPU_PHYS_ADDR: u32 = 4112;
+pub const NVGMC_SI_GPU_PHYS_FB_ADDR: u32 = 4113;
+pub const NVGMC_SI_GPU_PHYS_INST_ADDR: u32 = 4114;
+pub const NVGMC_SI_GPU_PHYS_IO_ADDR: u32 = 4115;
+pub const NVGMC_SI_NV_DOMAIN_BUS_DEVICE_FUNC: u32 = 4128;
+pub const NVGMC_SI_SIM_ACCESS_BUF_PHYS_ADDR: u32 = 4129;
+pub const NVGMC_SI_NOTIFY_OP_SHARED_SURF_PHYS_ADDR: u32 = 4130;
+pub const NVGMC_SI_PCIE_ATOMICS_OP_MASK: u32 = 4144;
+pub const NVGMC_SI_CONSOLE_MEM_SIZE: u32 = 4145;
+pub const NVGMC_SI_MAX_USER_VA: u32 = 4146;
+pub const NVGMC_SI_SYS_TIMER_OFFSET_NS: u32 = 4160;
+pub const NVGMC_SI_VF_FIRST_BAR0_ADDRESS: u32 = 4176;
+pub const NVGMC_SI_VF_FIRST_BAR1_ADDRESS: u32 = 4177;
+pub const NVGMC_SI_VF_FIRST_BAR2_ADDRESS: u32 = 4178;
+pub const NVGMC_SI_FHB_BUSINFO: u32 = 4240;
+pub const NVGMC_SI_CHIPSET_BUSINFO: u32 = 4241;
+pub const NVGMC_SI_KERNEL_DRIVER_NAME: u32 = 4352;
+pub const NVGMC_SI_KERNEL_DRIVER_VERSION_STRING: u32 = 4353;
+pub const NVGMC_SI_KERNEL_DRIVER_CHANGELIST: u32 = 4354;
+pub const NVGMC_SI_UNSTABLE_INTERFACE_SUPPORT: u32 = 4368;
+pub const NVGMC_SI_REGKEY_NAME: u32 = 12400;
+pub const NVGMC_SI_REGKEY_VALUE_U32: u32 = 12401;
+pub const NVGMC_SI_REGKEY_VALUE_STRING: u32 = 12402;
+pub const NVGMC_SI_REGKEY_VALUE_BINARY: u32 = 12403;
+pub const NVGMC_SI_REGKEY_NAME_MAX_LEN: u32 = 256;
+pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS2: u32 = 0;
+pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS3_BAREMETAL: u32 = 23068672;
+pub const GSP_FW_HEAP_PARAM_BASE_RM_SIZE_TU10X: u32 = 8388608;
+pub const GSP_FW_HEAP_PARAM_BASE_RM_SIZE_GH100: u32 = 18874368;
+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_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;
+pub const GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MAX_MB: u32 = 280;
+pub const GSP_FW_WPR_META_REVISION: u32 = 1;
+pub const GSP_FW_WPR_META_MAGIC: i64 = -2577556379034558285;
+pub const REGISTRY_TABLE_ENTRY_TYPE_DWORD: u32 = 1;
+pub const NVGMC_SI_CL_FLAGS: u32 = 4147;
+pub type __u8 = ffi::c_uchar;
+pub type __u16 = ffi::c_ushort;
+pub type __u32 = ffi::c_uint;
+pub type __u64 = ffi::c_ulonglong;
+pub type u8_ = __u8;
+pub type u16_ = __u16;
+pub type u32_ = __u32;
+pub type u64_ = __u64;
+pub const NV_VGPU_MSG_FUNCTION_NOP: _bindgen_ty_2 = 0;
+pub const NV_VGPU_MSG_FUNCTION_SET_GUEST_SYSTEM_INFO: _bindgen_ty_2 = 1;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_ROOT: _bindgen_ty_2 = 2;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_DEVICE: _bindgen_ty_2 = 3;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_MEMORY: _bindgen_ty_2 = 4;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_CTX_DMA: _bindgen_ty_2 = 5;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_CHANNEL_DMA: _bindgen_ty_2 = 6;
+pub const NV_VGPU_MSG_FUNCTION_MAP_MEMORY: _bindgen_ty_2 = 7;
+pub const NV_VGPU_MSG_FUNCTION_BIND_CTX_DMA: _bindgen_ty_2 = 8;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_OBJECT: _bindgen_ty_2 = 9;
+pub const NV_VGPU_MSG_FUNCTION_FREE: _bindgen_ty_2 = 10;
+pub const NV_VGPU_MSG_FUNCTION_LOG: _bindgen_ty_2 = 11;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_VIDMEM: _bindgen_ty_2 = 12;
+pub const NV_VGPU_MSG_FUNCTION_UNMAP_MEMORY: _bindgen_ty_2 = 13;
+pub const NV_VGPU_MSG_FUNCTION_MAP_MEMORY_DMA: _bindgen_ty_2 = 14;
+pub const NV_VGPU_MSG_FUNCTION_UNMAP_MEMORY_DMA: _bindgen_ty_2 = 15;
+pub const NV_VGPU_MSG_FUNCTION_GET_EDID: _bindgen_ty_2 = 16;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_DISP_CHANNEL: _bindgen_ty_2 = 17;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_DISP_OBJECT: _bindgen_ty_2 = 18;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_SUBDEVICE: _bindgen_ty_2 = 19;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_DYNAMIC_MEMORY: _bindgen_ty_2 = 20;
+pub const NV_VGPU_MSG_FUNCTION_DUP_OBJECT: _bindgen_ty_2 = 21;
+pub const NV_VGPU_MSG_FUNCTION_IDLE_CHANNELS: _bindgen_ty_2 = 22;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_EVENT: _bindgen_ty_2 = 23;
+pub const NV_VGPU_MSG_FUNCTION_SEND_EVENT: _bindgen_ty_2 = 24;
+pub const NV_VGPU_MSG_FUNCTION_REMAPPER_CONTROL: _bindgen_ty_2 = 25;
+pub const NV_VGPU_MSG_FUNCTION_DMA_CONTROL: _bindgen_ty_2 = 26;
+pub const NV_VGPU_MSG_FUNCTION_DMA_FILL_PTE_MEM: _bindgen_ty_2 = 27;
+pub const NV_VGPU_MSG_FUNCTION_MANAGE_HW_RESOURCE: _bindgen_ty_2 = 28;
+pub const NV_VGPU_MSG_FUNCTION_BIND_ARBITRARY_CTX_DMA: _bindgen_ty_2 = 29;
+pub const NV_VGPU_MSG_FUNCTION_CREATE_FB_SEGMENT: _bindgen_ty_2 = 30;
+pub const NV_VGPU_MSG_FUNCTION_DESTROY_FB_SEGMENT: _bindgen_ty_2 = 31;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_SHARE_DEVICE: _bindgen_ty_2 = 32;
+pub const NV_VGPU_MSG_FUNCTION_DEFERRED_API_CONTROL: _bindgen_ty_2 = 33;
+pub const NV_VGPU_MSG_FUNCTION_REMOVE_DEFERRED_API: _bindgen_ty_2 = 34;
+pub const NV_VGPU_MSG_FUNCTION_SIM_ESCAPE_READ: _bindgen_ty_2 = 35;
+pub const NV_VGPU_MSG_FUNCTION_SIM_ESCAPE_WRITE: _bindgen_ty_2 = 36;
+pub const NV_VGPU_MSG_FUNCTION_SIM_MANAGE_DISPLAY_CONTEXT_DMA: _bindgen_ty_2 = 37;
+pub const NV_VGPU_MSG_FUNCTION_FREE_VIDMEM_VIRT: _bindgen_ty_2 = 38;
+pub const NV_VGPU_MSG_FUNCTION_PERF_GET_PSTATE_INFO: _bindgen_ty_2 = 39;
+pub const NV_VGPU_MSG_FUNCTION_PERF_GET_PERFMON_SAMPLE: _bindgen_ty_2 = 40;
+pub const NV_VGPU_MSG_FUNCTION_PERF_GET_VIRTUAL_PSTATE_INFO: _bindgen_ty_2 = 41;
+pub const NV_VGPU_MSG_FUNCTION_PERF_GET_LEVEL_INFO: _bindgen_ty_2 = 42;
+pub const NV_VGPU_MSG_FUNCTION_MAP_SEMA_MEMORY: _bindgen_ty_2 = 43;
+pub const NV_VGPU_MSG_FUNCTION_UNMAP_SEMA_MEMORY: _bindgen_ty_2 = 44;
+pub const NV_VGPU_MSG_FUNCTION_SET_SURFACE_PROPERTIES: _bindgen_ty_2 = 45;
+pub const NV_VGPU_MSG_FUNCTION_CLEANUP_SURFACE: _bindgen_ty_2 = 46;
+pub const NV_VGPU_MSG_FUNCTION_UNLOADING_GUEST_DRIVER: _bindgen_ty_2 = 47;
+pub const NV_VGPU_MSG_FUNCTION_TDR_SET_TIMEOUT_STATE: _bindgen_ty_2 = 48;
+pub const NV_VGPU_MSG_FUNCTION_SWITCH_TO_VGA: _bindgen_ty_2 = 49;
+pub const NV_VGPU_MSG_FUNCTION_GPU_EXEC_REG_OPS: _bindgen_ty_2 = 50;
+pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_INFO: _bindgen_ty_2 = 51;
+pub const NV_VGPU_MSG_FUNCTION_ALLOC_VIRTMEM: _bindgen_ty_2 = 52;
+pub const NV_VGPU_MSG_FUNCTION_UPDATE_PDE_2: _bindgen_ty_2 = 53;
+pub const NV_VGPU_MSG_FUNCTION_SET_PAGE_DIRECTORY: _bindgen_ty_2 = 54;
+pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_PSTATE_INFO: _bindgen_ty_2 = 55;
+pub const NV_VGPU_MSG_FUNCTION_TRANSLATE_GUEST_GPU_PTES: _bindgen_ty_2 = 56;
+pub const NV_VGPU_MSG_FUNCTION_RESERVED_57: _bindgen_ty_2 = 57;
+pub const NV_VGPU_MSG_FUNCTION_RESET_CURRENT_GR_CONTEXT: _bindgen_ty_2 = 58;
+pub const NV_VGPU_MSG_FUNCTION_SET_SEMA_MEM_VALIDATION_STATE: _bindgen_ty_2 = 59;
+pub const NV_VGPU_MSG_FUNCTION_GET_ENGINE_UTILIZATION: _bindgen_ty_2 = 60;
+pub const NV_VGPU_MSG_FUNCTION_UPDATE_GPU_PDES: _bindgen_ty_2 = 61;
+pub const NV_VGPU_MSG_FUNCTION_GET_ENCODER_CAPACITY: _bindgen_ty_2 = 62;
+pub const NV_VGPU_MSG_FUNCTION_VGPU_PF_REG_READ32: _bindgen_ty_2 = 63;
+pub const NV_VGPU_MSG_FUNCTION_SET_GUEST_SYSTEM_INFO_EXT: _bindgen_ty_2 = 64;
+pub const NV_VGPU_MSG_FUNCTION_GET_GSP_STATIC_INFO: _bindgen_ty_2 = 65;
+pub const NV_VGPU_MSG_FUNCTION_RMFS_INIT: _bindgen_ty_2 = 66;
+pub const NV_VGPU_MSG_FUNCTION_RMFS_CLOSE_QUEUE: _bindgen_ty_2 = 67;
+pub const NV_VGPU_MSG_FUNCTION_RMFS_CLEANUP: _bindgen_ty_2 = 68;
+pub const NV_VGPU_MSG_FUNCTION_RMFS_TEST: _bindgen_ty_2 = 69;
+pub const NV_VGPU_MSG_FUNCTION_UPDATE_BAR_PDE: _bindgen_ty_2 = 70;
+pub const NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD: _bindgen_ty_2 = 71;
+pub const NV_VGPU_MSG_FUNCTION_GSP_SET_SYSTEM_INFO: _bindgen_ty_2 = 72;
+pub const NV_VGPU_MSG_FUNCTION_SET_REGISTRY: _bindgen_ty_2 = 73;
+pub const NV_VGPU_MSG_FUNCTION_GSP_INIT_POST_OBJGPU: _bindgen_ty_2 = 74;
+pub const NV_VGPU_MSG_FUNCTION_SUBDEV_EVENT_SET_NOTIFICATION: _bindgen_ty_2 = 75;
+pub const NV_VGPU_MSG_FUNCTION_GSP_RM_CONTROL: _bindgen_ty_2 = 76;
+pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_INFO2: _bindgen_ty_2 = 77;
+pub const NV_VGPU_MSG_FUNCTION_DUMP_PROTOBUF_COMPONENT: _bindgen_ty_2 = 78;
+pub const NV_VGPU_MSG_FUNCTION_UNSET_PAGE_DIRECTORY: _bindgen_ty_2 = 79;
+pub const NV_VGPU_MSG_FUNCTION_GET_CONSOLIDATED_STATIC_INFO: _bindgen_ty_2 = 80;
+pub const NV_VGPU_MSG_FUNCTION_GMMU_REGISTER_FAULT_BUFFER: _bindgen_ty_2 = 81;
+pub const NV_VGPU_MSG_FUNCTION_GMMU_UNREGISTER_FAULT_BUFFER: _bindgen_ty_2 = 82;
+pub const NV_VGPU_MSG_FUNCTION_GMMU_REGISTER_CLIENT_SHADOW_FAULT_BUFFER: _bindgen_ty_2 = 83;
+pub const NV_VGPU_MSG_FUNCTION_GMMU_UNREGISTER_CLIENT_SHADOW_FAULT_BUFFER: _bindgen_ty_2 = 84;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_VGPU_FB_USAGE: _bindgen_ty_2 = 85;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_NVFBC_SW_SESSION_UPDATE_INFO: _bindgen_ty_2 = 86;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_NVENC_SW_SESSION_UPDATE_INFO: _bindgen_ty_2 = 87;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESET_CHANNEL: _bindgen_ty_2 = 88;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESET_ISOLATED_CHANNEL: _bindgen_ty_2 = 89;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_HANDLE_VF_PRI_FAULT: _bindgen_ty_2 = 90;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CLK_GET_EXTENDED_INFO: _bindgen_ty_2 = 91;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_BOOST: _bindgen_ty_2 = 92;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_VPSTATES_GET_CONTROL: _bindgen_ty_2 = 93;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_ZBC_CLEAR_TABLE: _bindgen_ty_2 = 94;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_COLOR_CLEAR: _bindgen_ty_2 = 95;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_DEPTH_CLEAR: _bindgen_ty_2 = 96;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_SCHEDULE: _bindgen_ty_2 = 97;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_TIMESLICE: _bindgen_ty_2 = 98;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PREEMPT: _bindgen_ty_2 = 99;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_DISABLE_CHANNELS: _bindgen_ty_2 = 100;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_TSG_INTERLEAVE_LEVEL: _bindgen_ty_2 = 101;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_CHANNEL_INTERLEAVE_LEVEL: _bindgen_ty_2 = 102;
+pub const NV_VGPU_MSG_FUNCTION_GSP_RM_ALLOC: _bindgen_ty_2 = 103;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS_V2: _bindgen_ty_2 = 104;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_AES_ENCRYPT: _bindgen_ty_2 = 105;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_SESSION_KEY: _bindgen_ty_2 = 106;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_SESSION_KEY_STATUS: _bindgen_ty_2 = 107;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_CLEAR_ALL_SM_ERROR_STATES: _bindgen_ty_2 = 108;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_READ_ALL_SM_ERROR_STATES: _bindgen_ty_2 = 109;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_EXCEPTION_MASK: _bindgen_ty_2 = 110;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_PROMOTE_CTX: _bindgen_ty_2 = 111;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_CTXSW_PREEMPTION_BIND: _bindgen_ty_2 = 112;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_SET_CTXSW_PREEMPTION_MODE: _bindgen_ty_2 = 113;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_CTXSW_ZCULL_BIND: _bindgen_ty_2 = 114;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_INITIALIZE_CTX: _bindgen_ty_2 = 115;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_VASPACE_COPY_SERVER_RESERVED_PDES: _bindgen_ty_2 = 116;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_CLEAR_FAULTED_BIT: _bindgen_ty_2 = 117;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_LATEST_ECC_ADDRESSES: _bindgen_ty_2 = 118;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_MC_SERVICE_INTERRUPTS: _bindgen_ty_2 = 119;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DMA_SET_DEFAULT_VASPACE: _bindgen_ty_2 = 120;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_CE_PCE_MASK: _bindgen_ty_2 = 121;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_ZBC_CLEAR_TABLE_ENTRY: _bindgen_ty_2 = 122;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_NVLINK_PEER_ID_MASK: _bindgen_ty_2 = 123;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_NVLINK_STATUS: _bindgen_ty_2 = 124;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS: _bindgen_ty_2 = 125;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS_MATRIX: _bindgen_ty_2 = 126;
+pub const NV_VGPU_MSG_FUNCTION_RESERVED_0: _bindgen_ty_2 = 127;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_PM_AREA_SMPC: _bindgen_ty_2 = 128;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_HWPM_LEGACY: _bindgen_ty_2 = 129;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_B0CC_EXEC_REG_OPS: _bindgen_ty_2 = 130;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_BIND_PM_RESOURCES: _bindgen_ty_2 = 131;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SUSPEND_CONTEXT: _bindgen_ty_2 = 132;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_RESUME_CONTEXT: _bindgen_ty_2 = 133;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_EXEC_REG_OPS: _bindgen_ty_2 = 134;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_MMU_DEBUG: _bindgen_ty_2 = 135;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_READ_SINGLE_SM_ERROR_STATE: _bindgen_ty_2 = 136;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_CLEAR_SINGLE_SM_ERROR_STATE: _bindgen_ty_2 = 137;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_ERRBAR_DEBUG: _bindgen_ty_2 = 138;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_NEXT_STOP_TRIGGER_TYPE: _bindgen_ty_2 = 139;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_ALLOC_PMA_STREAM: _bindgen_ty_2 = 140;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PMA_STREAM_UPDATE_GET_PUT: _bindgen_ty_2 = 141;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FB_GET_INFO_V2: _bindgen_ty_2 = 142;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_SET_CHANNEL_PROPERTIES: _bindgen_ty_2 = 143;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_GET_CTX_BUFFER_INFO: _bindgen_ty_2 = 144;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_KGR_GET_CTX_BUFFER_PTES: _bindgen_ty_2 = 145;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_EVICT_CTX: _bindgen_ty_2 = 146;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FB_GET_FS_INFO: _bindgen_ty_2 = 147;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GRMGR_GET_GR_FS_INFO: _bindgen_ty_2 = 148;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_STOP_CHANNEL: _bindgen_ty_2 = 149;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_PC_SAMPLING_MODE: _bindgen_ty_2 = 150;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_RATED_TDP_GET_STATUS: _bindgen_ty_2 = 151;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_RATED_TDP_SET_CONTROL: _bindgen_ty_2 = 152;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FREE_PMA_STREAM: _bindgen_ty_2 = 153;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_TIMER_SET_GR_TICK_FREQ: _bindgen_ty_2 = 154;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_SETUP_VF_ZOMBIE_SUBCTX_PDB: _bindgen_ty_2 = 155;
+pub const NV_VGPU_MSG_FUNCTION_GET_CONSOLIDATED_GR_STATIC_INFO: _bindgen_ty_2 = 156;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_SINGLE_SM_SINGLE_STEP: _bindgen_ty_2 = 157;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_GET_TPC_PARTITION_MODE: _bindgen_ty_2 = 158;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_SET_TPC_PARTITION_MODE: _bindgen_ty_2 = 159;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_ALLOCATE: _bindgen_ty_2 = 160;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_DESTROY: _bindgen_ty_2 = 161;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_MAP: _bindgen_ty_2 = 162;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_UNMAP: _bindgen_ty_2 = 163;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_PUSH_STREAM: _bindgen_ty_2 = 164;
+pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_SET_HANDLES: _bindgen_ty_2 = 165;
+pub const NV_VGPU_MSG_FUNCTION_UVM_METHOD_STREAM_GUEST_PAGES_OPERATION: _bindgen_ty_2 = 166;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_QUIESCE_PMA_CHANNEL: _bindgen_ty_2 = 167;
+pub const NV_VGPU_MSG_FUNCTION_DCE_RM_INIT: _bindgen_ty_2 = 168;
+pub const NV_VGPU_MSG_FUNCTION_REGISTER_VIRTUAL_EVENT_BUFFER: _bindgen_ty_2 = 169;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_EVENT_BUFFER_UPDATE_GET: _bindgen_ty_2 = 170;
+pub const NV_VGPU_MSG_FUNCTION_GET_PLCABLE_ADDRESS_KIND: _bindgen_ty_2 = 171;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_LIMITS_SET_STATUS_V2: _bindgen_ty_2 = 172;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_SRIOV_PROMOTE_PMA_STREAM: _bindgen_ty_2 = 173;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_MMU_DEBUG_MODE: _bindgen_ty_2 = 174;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_PROMOTE_FAULT_METHOD_BUFFERS: _bindgen_ty_2 = 175;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FLCN_GET_CTX_BUFFER_SIZE: _bindgen_ty_2 = 176;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FLCN_GET_CTX_BUFFER_INFO: _bindgen_ty_2 = 177;
+pub const NV_VGPU_MSG_FUNCTION_DISABLE_CHANNELS: _bindgen_ty_2 = 178;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FABRIC_MEMORY_DESCRIBE: _bindgen_ty_2 = 179;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FABRIC_MEM_STATS: _bindgen_ty_2 = 180;
+pub const NV_VGPU_MSG_FUNCTION_SAVE_HIBERNATION_DATA: _bindgen_ty_2 = 181;
+pub const NV_VGPU_MSG_FUNCTION_RESTORE_HIBERNATION_DATA: _bindgen_ty_2 = 182;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_MEMSYS_SET_ZBC_REFERENCED: _bindgen_ty_2 = 183;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_CREATE: _bindgen_ty_2 = 184;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_DELETE: _bindgen_ty_2 = 185;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_GET_WORK_SUBMIT_TOKEN: _bindgen_ty_2 = 186;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_SET_WORK_SUBMIT_TOKEN_NOTIF_INDEX: _bindgen_ty_2 = 187;
+pub const NV_VGPU_MSG_FUNCTION_PMA_SCRUBBER_SHARED_BUFFER_GUEST_PAGES_OPERATION: _bindgen_ty_2 =
+ 188;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_MASTER_GET_VIRTUAL_FUNCTION_ERROR_CONT_INTR_MASK:
+ _bindgen_ty_2 = 189;
+pub const NV_VGPU_MSG_FUNCTION_RESERVED_190: _bindgen_ty_2 = 190;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_P2P_CAPS: _bindgen_ty_2 = 191;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_BUS_SET_P2P_MAPPING: _bindgen_ty_2 = 192;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_BUS_UNSET_P2P_MAPPING: _bindgen_ty_2 = 193;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FLA_SETUP_INSTANCE_MEM_BLOCK: _bindgen_ty_2 = 194;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_MIGRATABLE_OPS: _bindgen_ty_2 = 195;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_TOTAL_HS_CREDITS: _bindgen_ty_2 = 196;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_HS_CREDITS: _bindgen_ty_2 = 197;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_HS_CREDITS: _bindgen_ty_2 = 198;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_PM_AREA_PC_SAMPLER: _bindgen_ty_2 = 199;
+pub const NV_VGPU_MSG_FUNCTION_INVALIDATE_TLB: _bindgen_ty_2 = 200;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_QUERY_ECC_STATUS: _bindgen_ty_2 = 201;
+pub const NV_VGPU_MSG_FUNCTION_ECC_NOTIFIER_WRITE_ACK: _bindgen_ty_2 = 202;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_GET_MODE_MMU_DEBUG: _bindgen_ty_2 = 203;
+pub const NV_VGPU_MSG_FUNCTION_RM_API_CONTROL: _bindgen_ty_2 = 204;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_INTERNAL_GPU_START_FABRIC_PROBE: _bindgen_ty_2 = 205;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_NVLINK_GET_INBAND_RECEIVED_DATA: _bindgen_ty_2 = 206;
+pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_DATA: _bindgen_ty_2 = 207;
+pub const NV_VGPU_MSG_FUNCTION_RESERVED_208: _bindgen_ty_2 = 208;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_GET_INFO_V2: _bindgen_ty_2 = 209;
+pub const NV_VGPU_MSG_FUNCTION_GET_BRAND_CAPS: _bindgen_ty_2 = 210;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_NVLINK_INBAND_SEND_DATA: _bindgen_ty_2 = 211;
+pub const NV_VGPU_MSG_FUNCTION_UPDATE_GPM_GUEST_BUFFER_INFO: _bindgen_ty_2 = 212;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_INTERNAL_CONTROL_GSP_TRACE: _bindgen_ty_2 = 213;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_STENCIL_CLEAR: _bindgen_ty_2 = 214;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_VGPU_HEAP_STATS: _bindgen_ty_2 = 215;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_LIBOS_HEAP_STATS: _bindgen_ty_2 = 216;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_MMU_GCC_DEBUG: _bindgen_ty_2 = 217;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_GET_MODE_MMU_GCC_DEBUG: _bindgen_ty_2 = 218;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_HES: _bindgen_ty_2 = 219;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RELEASE_HES: _bindgen_ty_2 = 220;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_CCU_PROF: _bindgen_ty_2 = 221;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_RELEASE_CCU_PROF: _bindgen_ty_2 = 222;
+pub const NV_VGPU_MSG_FUNCTION_SETUP_HIBERNATION_BUFFER: _bindgen_ty_2 = 223;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_GET_CHIPLET_HS_CREDIT_POOL: _bindgen_ty_2 = 224;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_GET_HS_CREDITS_MAPPING: _bindgen_ty_2 = 225;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_EXPORT: _bindgen_ty_2 = 226;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_INTERNAL_GPU_CHECK_CTS_ID_VALID: _bindgen_ty_2 = 227;
+pub const NV_VGPU_MSG_FUNCTION_INIT_GSP_TRACE_CRASH_BUFFER: _bindgen_ty_2 = 228;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_SET_MIGRATION_BLOCK: _bindgen_ty_2 = 229;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_VGPU_SWRUNLIST_SUBMIT: _bindgen_ty_2 = 230;
+pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_VGPU_SWRUNLIST_CHANNEL_UPDATE: _bindgen_ty_2 = 231;
+pub const NV_VGPU_MSG_FUNCTION_NUM_FUNCTIONS: _bindgen_ty_2 = 232;
+pub type _bindgen_ty_2 = ffi::c_uint;
+pub const NV_VGPU_MSG_EVENT_FIRST_EVENT: _bindgen_ty_3 = 4096;
+pub const NV_VGPU_MSG_EVENT_GSP_INIT_DONE: _bindgen_ty_3 = 4097;
+pub const NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER: _bindgen_ty_3 = 4098;
+pub const NV_VGPU_MSG_EVENT_POST_EVENT: _bindgen_ty_3 = 4099;
+pub const NV_VGPU_MSG_EVENT_RC_TRIGGERED: _bindgen_ty_3 = 4100;
+pub const NV_VGPU_MSG_EVENT_MMU_FAULT_QUEUED: _bindgen_ty_3 = 4101;
+pub const NV_VGPU_MSG_EVENT_OS_ERROR_LOG: _bindgen_ty_3 = 4102;
+pub const NV_VGPU_MSG_EVENT_RG_LINE_INTR: _bindgen_ty_3 = 4103;
+pub const NV_VGPU_MSG_EVENT_GPUACCT_PERFMON_UTIL_SAMPLES: _bindgen_ty_3 = 4104;
+pub const NV_VGPU_MSG_EVENT_SIM_READ: _bindgen_ty_3 = 4105;
+pub const NV_VGPU_MSG_EVENT_SIM_WRITE: _bindgen_ty_3 = 4106;
+pub const NV_VGPU_MSG_EVENT_SEMAPHORE_SCHEDULE_CALLBACK: _bindgen_ty_3 = 4107;
+pub const NV_VGPU_MSG_EVENT_UCODE_LIBOS_PRINT: _bindgen_ty_3 = 4108;
+pub const NV_VGPU_MSG_EVENT_VGPU_GSP_PLUGIN_TRIGGERED: _bindgen_ty_3 = 4109;
+pub const NV_VGPU_MSG_EVENT_PERF_GPU_BOOST_SYNC_LIMITS_CALLBACK: _bindgen_ty_3 = 4110;
+pub const NV_VGPU_MSG_EVENT_PERF_BRIDGELESS_INFO_UPDATE: _bindgen_ty_3 = 4111;
+pub const NV_VGPU_MSG_EVENT_VGPU_CONFIG: _bindgen_ty_3 = 4112;
+pub const NV_VGPU_MSG_EVENT_DISPLAY_MODESET: _bindgen_ty_3 = 4113;
+pub const NV_VGPU_MSG_EVENT_EXTDEV_INTR_SERVICE: _bindgen_ty_3 = 4114;
+pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_256: _bindgen_ty_3 = 4115;
+pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_512: _bindgen_ty_3 = 4116;
+pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_1024: _bindgen_ty_3 = 4117;
+pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_2048: _bindgen_ty_3 = 4118;
+pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_4096: _bindgen_ty_3 = 4119;
+pub const NV_VGPU_MSG_EVENT_TIMED_SEMAPHORE_RELEASE: _bindgen_ty_3 = 4120;
+pub const NV_VGPU_MSG_EVENT_NVLINK_IS_GPU_DEGRADED: _bindgen_ty_3 = 4121;
+pub const NV_VGPU_MSG_EVENT_PFM_REQ_HNDLR_STATE_SYNC_CALLBACK: _bindgen_ty_3 = 4122;
+pub const NV_VGPU_MSG_EVENT_NVLINK_FAULT_UP: _bindgen_ty_3 = 4123;
+pub const NV_VGPU_MSG_EVENT_GSP_LOCKDOWN_NOTICE: _bindgen_ty_3 = 4124;
+pub const NV_VGPU_MSG_EVENT_MIG_CI_CONFIG_UPDATE: _bindgen_ty_3 = 4125;
+pub const NV_VGPU_MSG_EVENT_UPDATE_GSP_TRACE: _bindgen_ty_3 = 4126;
+pub const NV_VGPU_MSG_EVENT_NVLINK_FATAL_ERROR_RECOVERY: _bindgen_ty_3 = 4127;
+pub const NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD: _bindgen_ty_3 = 4128;
+pub const NV_VGPU_MSG_EVENT_FECS_ERROR: _bindgen_ty_3 = 4129;
+pub const NV_VGPU_MSG_EVENT_RECOVERY_ACTION: _bindgen_ty_3 = 4130;
+pub const NV_VGPU_MSG_EVENT_TRIGGER_BUGCHECK: _bindgen_ty_3 = 4131;
+pub const NV_VGPU_MSG_EVENT_BIND_BAR2: _bindgen_ty_3 = 4132;
+pub const NV_VGPU_MSG_EVENT_FORCED_DRIVER_SHUTDOWN: _bindgen_ty_3 = 4133;
+pub const NV_VGPU_MSG_EVENT_GSP_LOAD_EXEC_GENERIC_BOOTLOADER: _bindgen_ty_3 = 4134;
+pub const NV_VGPU_MSG_EVENT_GSP_LOAD_EXEC_HS_BINARY: _bindgen_ty_3 = 4135;
+pub const NV_VGPU_MSG_EVENT_UPDATE_GRID_DISPLAYLESS_PARAMS: _bindgen_ty_3 = 4136;
+pub const NV_VGPU_MSG_EVENT_PMU_HALTED: _bindgen_ty_3 = 4137;
+pub const NV_VGPU_MSG_EVENT_ECC_POSSIBLE_ERR_FAST_PATH: _bindgen_ty_3 = 4138;
+pub const NV_VGPU_MSG_EVENT_NVLINK_TRAFFIC_QUIESCE_NOTIFY: _bindgen_ty_3 = 4139;
+pub const NV_VGPU_MSG_EVENT_GPU_GFM_STATE_CHANGE: _bindgen_ty_3 = 4140;
+pub const NV_VGPU_MSG_EVENT_NVLINK_ABM_FABRIC_HEALTH_MASK_UPDATE: _bindgen_ty_3 = 4141;
+pub const NV_VGPU_MSG_EVENT_NUM_EVENTS: _bindgen_ty_3 = 4142;
+pub type _bindgen_ty_3 = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct NV0080_CTRL_GPU_GET_SRIOV_CAPS_PARAMS {
+ pub totalVFs: u32_,
+ pub firstVfOffset: u32_,
+ pub vfFeatureMask: u32_,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub FirstVFBar0Address: u64_,
+ pub FirstVFBar1Address: u64_,
+ pub FirstVFBar2Address: u64_,
+ pub bar0Size: u64_,
+ pub bar1Size: u64_,
+ pub bar2Size: u64_,
+ pub b64bitBar0: u8_,
+ pub b64bitBar1: u8_,
+ pub b64bitBar2: u8_,
+ pub bSriovEnabled: u8_,
+ pub bSriovHeavyEnabled: u8_,
+ pub bEmulateVFBar0TlbInvalidationRegister: u8_,
+ pub bClientRmAllocatedCtxBuffer: u8_,
+ pub bNonPowerOf2ChannelCountSupported: u8_,
+ pub bVfResizableBAR1Supported: u8_,
+ pub __bindgen_padding_1: [u8; 7usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct NV2080_CTRL_BIOS_GET_SKU_INFO_PARAMS {
+ pub BoardID: u32_,
+ pub chipSKU: [ffi::c_char; 9usize],
+ pub chipSKUMod: [ffi::c_char; 5usize],
+ pub __bindgen_padding_0: [u8; 2usize],
+ pub skuConfigVersion: u32_,
+ pub project: [ffi::c_char; 5usize],
+ pub projectSKU: [ffi::c_char; 5usize],
+ pub CDP: [ffi::c_char; 6usize],
+ pub projectSKUMod: [ffi::c_char; 2usize],
+ pub __bindgen_padding_1: [u8; 2usize],
+ pub businessCycle: u32_,
+}
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_NONE: NV2080_FB_REGION_TAG = 0;
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_GSP_CARVEOUT: NV2080_FB_REGION_TAG = 1;
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_CPU_RM_RESERVED: NV2080_FB_REGION_TAG = 2;
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_CPU_RM_RESERVED_HEAP: NV2080_FB_REGION_TAG = 3;
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_GSP_RM_RESERVED: NV2080_FB_REGION_TAG = 4;
+pub const NV2080_FB_REGION_TAG_NV2080_FB_REGION_TAG_GSP_RM_RESERVED_HEAP: NV2080_FB_REGION_TAG = 5;
+pub type NV2080_FB_REGION_TAG = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO {
+ pub base: u64_,
+ pub limit: u64_,
+ pub reserved: u64_,
+ pub performance: u32_,
+ pub supportCompressed: u8_,
+ pub supportISO: u8_,
+ pub bProtected: u8_,
+ pub blackList: [u8_; 18usize],
+ pub __bindgen_padding_0: [u8; 3usize],
+ pub regionTag: NV2080_FB_REGION_TAG,
+}
+impl Default for NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_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(Debug, Copy, Clone, MaybeZeroable)]
+pub struct NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS {
+ pub numFBRegions: u32_,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub fbRegion: [NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO; 16usize],
+}
+impl Default for NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS {
+ 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(Debug, Copy, Clone, MaybeZeroable)]
+pub struct NV2080_CTRL_GPU_GET_GID_INFO_PARAMS {
+ pub index: u32_,
+ pub flags: u32_,
+ pub length: u32_,
+ pub data: [u8_; 256usize],
+}
+impl Default for NV2080_CTRL_GPU_GET_GID_INFO_PARAMS {
+ 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(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct DOD_METHOD_DATA {
+ pub status: u32_,
+ pub acpiIdListLen: u32_,
+ pub acpiIdList: [u32_; 16usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct JT_METHOD_DATA {
+ pub status: u32_,
+ pub jtCaps: u32_,
+ pub jtRevId: u16_,
+ pub bSBIOSCaps: u8_,
+ pub __bindgen_padding_0: u8,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct MUX_METHOD_DATA_ELEMENT {
+ pub acpiId: u32_,
+ pub mode: u32_,
+ pub status: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct MUX_METHOD_DATA {
+ pub tableLen: u32_,
+ pub acpiIdMuxModeTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
+ pub acpiIdMuxPartTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
+ pub acpiIdMuxStateTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct CAPS_METHOD_DATA {
+ pub status: u32_,
+ pub optimusCaps: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct ACPI_METHOD_DATA {
+ pub bValid: u8_,
+ pub __bindgen_padding_0: [u8; 3usize],
+ pub dodMethodData: DOD_METHOD_DATA,
+ pub jtMethodData: JT_METHOD_DATA,
+ pub muxMethodData: MUX_METHOD_DATA,
+ pub capsMethodData: CAPS_METHOD_DATA,
+}
+pub type GspStaticConfigInfo = GspStaticConfigInfo_t;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct BUSINFO {
+ pub deviceID: u16_,
+ pub vendorID: u16_,
+ pub subdeviceID: u16_,
+ pub subvendorID: u16_,
+ pub revisionID: u8_,
+ pub __bindgen_padding_0: u8,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_VF_INFO {
+ pub totalVFs: u32_,
+ pub firstVFOffset: u32_,
+ pub FirstVFBar0Address: u64_,
+ pub FirstVFBar1Address: u64_,
+ pub FirstVFBar2Address: u64_,
+ pub b64bitBar0: u8_,
+ pub b64bitBar1: u8_,
+ pub b64bitBar2: u8_,
+ pub __bindgen_padding_0: [u8; 5usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_PCIE_CONFIG_REG {
+ pub linkCap: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct EcidManufacturingInfo {
+ pub info: [u64_; 2usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct FW_WPR_LAYOUT_OFFSET {
+ pub nonWprHeapOffset: u64_,
+ pub frtsOffset: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GspStaticConfigInfo_t {
+ pub gidInfo: NV2080_CTRL_GPU_GET_GID_INFO_PARAMS,
+ pub SKUInfo: NV2080_CTRL_BIOS_GET_SKU_INFO_PARAMS,
+ pub __bindgen_padding_0: [u8; 4usize],
+ pub fbRegionInfoParams: NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS,
+ pub bPdiValid: u8_,
+ pub __bindgen_padding_1: [u8; 7usize],
+ pub pdi: u64_,
+ pub sriovCaps: NV0080_CTRL_GPU_GET_SRIOV_CAPS_PARAMS,
+ pub sriovMaxGfid: u32_,
+ pub engineCaps: [u32_; 3usize],
+ pub poisonFuseEnabled: u8_,
+ pub __bindgen_padding_2: [u8; 7usize],
+ pub fb_length: u64_,
+ pub gpuNameString: [u8_; 64usize],
+ pub gpuShortNameString: [u8_; 64usize],
+ pub bGpuInternalSku: u8_,
+ pub bIsQuadroGeneric: u8_,
+ pub bIsQuadroAd: u8_,
+ pub bIsNvidiaNvs: u8_,
+ pub bIsVgx: u8_,
+ pub bGeforceSmb: u8_,
+ pub bIsTitan: u8_,
+ pub bIsTesla: u8_,
+ pub bIsMobile: u8_,
+ pub bIsCmpSku: u8_,
+ pub bIsGc6Rtd3Allowed: u8_,
+ pub bIsGc8Rtd3Allowed: u8_,
+ pub bIsGcOffRtd3Allowed: u8_,
+ pub bIsGcoffLegacyAllowed: u8_,
+ pub bIsMigSupported: u8_,
+ pub __bindgen_padding_3: u8,
+ pub RTD3GC6TotalBoardPower: u16_,
+ pub RTD3GC6PerstDelay: u16_,
+ pub __bindgen_padding_4: [u8; 4usize],
+ pub bar1PdeBase: u64_,
+ pub bar2PdeBase: u64_,
+ pub bVbiosValid: u8_,
+ pub __bindgen_padding_5: [u8; 3usize],
+ pub vbiosSubVendor: u32_,
+ pub vbiosSubDevice: u32_,
+ pub vbiosRevision: u32_,
+ pub vbiosOemRevision: u32_,
+ pub bPageRetirementSupported: u8_,
+ pub bSplitVasBetweenServerClientRm: u8_,
+ pub bClRootportNeedsNosnoopWAR: u8_,
+ pub __bindgen_padding_6: u8,
+ pub hInternalClient: u32_,
+ pub hInternalDevice: u32_,
+ pub hInternalSubdevice: u32_,
+ pub bSelfHostedMode: u8_,
+ pub bAtsSupported: u8_,
+ pub bSysL2CacheCoherentMode: u8_,
+ pub bIsGpuUefi: u8_,
+ pub bIsEfiInit: u8_,
+ pub __bindgen_padding_7: [u8; 7usize],
+ pub ecidInfo: EcidManufacturingInfo,
+ pub fwWprLayoutOffset: FW_WPR_LAYOUT_OFFSET,
+ pub bBusResetRequired: u8_,
+ pub chipSubRev: u8_,
+ pub __bindgen_padding_8: [u8; 2usize],
+ pub emulationRev1: u32_,
+}
+impl Default for GspStaticConfigInfo_t {
+ 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(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GspSystemInfo {
+ pub gpuPhysAddr: u64_,
+ pub gpuPhysFbAddr: u64_,
+ pub gpuPhysInstAddr: u64_,
+ pub gpuPhysIoAddr: u64_,
+ pub nvDomainBusDeviceFunc: u64_,
+ pub simAccessBufPhysAddr: u64_,
+ pub notifyOpSharedSurfacePhysAddr: u64_,
+ pub pcieAtomicsOpMask: u64_,
+ pub consoleMemSize: u64_,
+ pub maxUserVa: u64_,
+ pub pciConfigMirrorBase: u32_,
+ pub pciConfigMirrorSize: u32_,
+ pub PCIDeviceID: u32_,
+ pub PCISubDeviceID: u32_,
+ pub PCIRevisionID: u32_,
+ pub pcieAtomicsCplDeviceCapMask: u32_,
+ pub oorArch: u8_,
+ pub bUnstableRpcs: u8_,
+ pub bUnstableEvents: u8_,
+ pub __bindgen_padding_0: [u8; 5usize],
+ pub clPdbProperties: u64_,
+ pub Chipset: u32_,
+ pub bGpuBehindBridge: u8_,
+ pub bFlrSupported: u8_,
+ pub b64bBar0Supported: u8_,
+ pub bMnocAvailable: u8_,
+ pub chipsetL1ssEnable: u32_,
+ pub bUpstreamL0sUnsupported: u8_,
+ pub bUpstreamL1Unsupported: u8_,
+ pub bUpstreamL1PorSupported: u8_,
+ pub bUpstreamL1PorMobileOnly: u8_,
+ pub bSystemHasMux: u8_,
+ pub upstreamAddressValid: u8_,
+ pub FHBBusInfo: BUSINFO,
+ pub chipsetIDInfo: BUSINFO,
+ pub __bindgen_padding_1: [u8; 2usize],
+ pub acpiMethodData: ACPI_METHOD_DATA,
+ pub hypervisorType: u32_,
+ pub virtualConfigBits: u16_,
+ pub bIsPassthru: u8_,
+ pub __bindgen_padding_2: [u8; 5usize],
+ pub sysTimerOffsetNs: u64_,
+ pub gspVFInfo: GSP_VF_INFO,
+ pub bIsPrimary: u8_,
+ pub bIsUnixHdmiFrlComplianceEnabled: u8_,
+ pub isGridBuild: u8_,
+ pub __bindgen_padding_3: u8,
+ pub pcieConfigReg: GSP_PCIE_CONFIG_REG,
+ pub gridBuildCsp: u32_,
+ pub bPreserveVideoMemoryAllocations: u8_,
+ pub bTdrEventSupported: u8_,
+ pub bFeatureStretchVblankCapable: u8_,
+ pub bEnableDynamicGranularityPageArrays: u8_,
+ pub bClockBoostSupported: u8_,
+ pub __bindgen_padding_4: [u8; 7usize],
+ pub hostPageSize: u64_,
+ pub bIsCmcBasedHws: u8_,
+ pub bGspNocatEnabled: u8_,
+ pub bS0ixSupport: u8_,
+ pub bWindowChannelAlwaysMapped: u8_,
+ pub pciePowerControlValue: u32_,
+ pub bPciePowerControlPresent: u8_,
+ pub __bindgen_padding_5: [u8; 3usize],
+ pub pf0DeviceControl2Reg: u32_,
+ pub bIsCxlDevice: u8_,
+ pub bReserveZeroFbAddressAsRegion: u8_,
+ pub __bindgen_padding_6: [u8; 6usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct MESSAGE_QUEUE_INIT_ARGUMENTS {
+ pub flags: u64_,
+ pub sharedMemPhysAddr: u64_,
+ pub cmdQueueOffset: u64_,
+ pub statQueueOffset: u64_,
+ pub queueElementHdrSize: u32_,
+ pub queueElementSizeMin: u32_,
+ pub queueElementSizeMax: u32_,
+ pub queueHeaderAlign: u32_,
+ pub queueElementAlign: u32_,
+ pub pageTableEntryCount: u32_,
+ pub reserved: [u8_; 8usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_SR_INIT_ARGUMENTS {
+ pub oldLevel: u32_,
+ pub flags: u32_,
+ pub reserved: [u8_; 8usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED {
+ pub magic: u32_,
+ pub size: u16_,
+ pub reserved: [u8_; 2usize],
+ pub flags: u64_,
+ pub messageQueueInitArguments: MESSAGE_QUEUE_INIT_ARGUMENTS,
+ pub srInitArguments: GSP_SR_INIT_ARGUMENTS,
+ pub reserved2: [u8_; 4usize],
+ pub gpuInstance: u32_,
+ pub profilerArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_1,
+ pub sysmemHeapArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_2,
+ pub rmStateMonitorBufferArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_3,
+ pub bindataArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_4,
+ pub vbiosOverrideArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_5,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_1 {
+ pub pa: u64_,
+ pub size: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_2 {
+ pub pa: u64_,
+ pub size: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_3 {
+ pub pa: u64_,
+ pub size: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_4 {
+ pub radix3: u64_,
+ pub size: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_5 {
+ pub pa: u64_,
+ pub size: u64_,
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union rpc_message_rpc_union_field_v03_00 {
+ pub spare: u32_,
+ pub cpuRmGfid: u32_,
+}
+impl Default for rpc_message_rpc_union_field_v03_00 {
+ 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 type rpc_message_rpc_union_field_v = rpc_message_rpc_union_field_v03_00;
+#[repr(C)]
+#[derive(MaybeZeroable)]
+pub struct rpc_message_header_v03_00 {
+ pub header_version: u32_,
+ pub signature: u32_,
+ pub length: u32_,
+ pub function: u32_,
+ pub rpc_result: u32_,
+ pub rpc_result_private: u32_,
+ pub sequence: u32_,
+ pub u: rpc_message_rpc_union_field_v,
+ pub rpc_message_data: __IncompleteArrayField<u8_>,
+}
+impl Default for rpc_message_header_v03_00 {
+ 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 type rpc_message_header_v = rpc_message_header_v03_00;
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub struct GspFwWprMeta {
+ pub magic: u64_,
+ pub revision: u64_,
+ pub sysmemAddrOfRadix3Elf: u64_,
+ pub sizeOfRadix3Elf: u64_,
+ pub sysmemAddrOfBootloader: u64_,
+ pub sizeOfBootloader: u64_,
+ pub bootloaderCodeOffset: u64_,
+ pub bootloaderDataOffset: u64_,
+ pub bootloaderManifestOffset: u64_,
+ pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_1,
+ pub gspFwRsvdStart: u64_,
+ pub nonWprHeapOffset: u64_,
+ pub nonWprHeapSize: u64_,
+ pub gspFwWprStart: u64_,
+ pub gspFwHeapOffset: u64_,
+ pub gspFwHeapSize: u64_,
+ pub gspFwOffset: u64_,
+ pub bootBinOffset: u64_,
+ pub frtsOffset: u64_,
+ pub frtsSize: u64_,
+ pub gspFwWprEnd: u64_,
+ pub fbSize: u64_,
+ pub vgaWorkspaceOffset: u64_,
+ pub vgaWorkspaceSize: u64_,
+ pub bootCount: u64_,
+ pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_2,
+ pub gspFwHeapVfPartitionCount: u8_,
+ pub flags: u8_,
+ pub pagingConfig: u16_,
+ pub pmuReservedSize: u32_,
+ pub verified: u64_,
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union GspFwWprMeta__bindgen_ty_1 {
+ pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_1__bindgen_ty_1,
+ pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_1__bindgen_ty_2,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GspFwWprMeta__bindgen_ty_1__bindgen_ty_1 {
+ pub sysmemAddrOfSignature: u64_,
+ pub sizeOfSignature: u64_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GspFwWprMeta__bindgen_ty_1__bindgen_ty_2 {
+ pub gspFwHeapFreeListWprOffset: u32_,
+ pub unused0: u32_,
+ pub unused1: u64_,
+}
+impl Default for GspFwWprMeta__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()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union GspFwWprMeta__bindgen_ty_2 {
+ pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_2__bindgen_ty_1,
+ pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_2__bindgen_ty_2,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GspFwWprMeta__bindgen_ty_2__bindgen_ty_1 {
+ pub partitionRpcAddr: u64_,
+ pub partitionRpcRequestOffset: u16_,
+ pub partitionRpcReplyOffset: u16_,
+ pub elfCodeOffset: u32_,
+ pub elfDataOffset: u32_,
+ pub elfCodeSize: u32_,
+ pub elfDataSize: u32_,
+ pub lsUcodeVersion: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GspFwWprMeta__bindgen_ty_2__bindgen_ty_2 {
+ pub partitionRpcPadding: [u32_; 4usize],
+ pub sysmemAddrOfCrashReportQueue: u64_,
+ pub sizeOfCrashReportQueue: u32_,
+ pub lsUcodeVersionPadding: [u32_; 1usize],
+}
+impl Default for GspFwWprMeta__bindgen_ty_2 {
+ 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 GspFwWprMeta {
+ 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 type LibosAddress = u64_;
+pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_NONE: LibosMemoryRegionKind = 0;
+pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_CONTIGUOUS: LibosMemoryRegionKind = 1;
+pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_RADIX3: LibosMemoryRegionKind = 2;
+pub type LibosMemoryRegionKind = ffi::c_uint;
+pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_NONE: LibosMemoryRegionLoc = 0;
+pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_SYSMEM: LibosMemoryRegionLoc = 1;
+pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_FB: LibosMemoryRegionLoc = 2;
+pub type LibosMemoryRegionLoc = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct LibosMemoryRegionInitArgument {
+ pub id8: LibosAddress,
+ pub pa: LibosAddress,
+ pub size: LibosAddress,
+ pub kind: u8_,
+ pub loc: u8_,
+ pub __bindgen_padding_0: [u8; 6usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct PACKED_REGISTRY_ENTRY {
+ pub nameOffset: u32_,
+ pub type_: u8_,
+ pub __bindgen_padding_0: [u8; 3usize],
+ pub data: u32_,
+ pub length: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, MaybeZeroable)]
+pub struct PACKED_REGISTRY_TABLE {
+ pub size: u32_,
+ pub numEntries: u32_,
+ pub entries: __IncompleteArrayField<PACKED_REGISTRY_ENTRY>,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct msgqTxHeader {
+ pub versionMajor: u16_,
+ pub versionMinor: u16_,
+ pub size: u32_,
+ pub msgSize: u32_,
+ pub msgCount: u32_,
+ pub entryOff: u32_,
+ pub reserved: [u32_; 3usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_MSG_QUEUE_ENCRYPTION_TAG {
+ pub authTagBuffer: [u8_; 16usize],
+}
+#[repr(C)]
+#[derive(MaybeZeroable)]
+pub struct GSP_MSG_QUEUE_ELEMENT {
+ pub mctpMagic: u32_,
+ pub mctpPayloadSize: u32_,
+ pub mctpHeader: u32_,
+ pub nvdmHeader: u32_,
+ pub __bindgen_anon_1: GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(MaybeZeroable)]
+pub struct GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1 {
+ pub withEncryption: __BindgenUnionField<GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1__bindgen_ty_1>,
+ pub noEncryption: __BindgenUnionField<GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1__bindgen_ty_2>,
+ pub bindgen_union_field: [u32; 6usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, MaybeZeroable)]
+pub struct GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1__bindgen_ty_1 {
+ pub encryptionTag: GSP_MSG_QUEUE_ENCRYPTION_TAG,
+ pub nvdmPayloadSize: u32_,
+ pub reserved: u32_,
+ pub payload: __IncompleteArrayField<u8_>,
+}
+#[repr(C)]
+#[derive(Debug, Default, MaybeZeroable)]
+pub struct GSP_MSG_QUEUE_ELEMENT__bindgen_ty_1__bindgen_ty_2 {
+ pub nvdmPayloadSize: u32_,
+ pub reserved: u32_,
+ pub payload: __IncompleteArrayField<u8_>,
+}
+impl Default for GSP_MSG_QUEUE_ELEMENT__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 GSP_MSG_QUEUE_ELEMENT {
+ 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(MaybeZeroable)]
+pub struct GMCAPI_HEADER {
+ pub command: u32_,
+ pub size: u32_,
+ pub sequence: u64_,
+ pub __bindgen_anon_1: GMCAPI_HEADER__bindgen_ty_1,
+ pub reserved: [u32_; 5usize],
+ pub data: __IncompleteArrayField<u8_>,
+}
+#[repr(C)]
+#[derive(Copy, Clone, MaybeZeroable)]
+pub union GMCAPI_HEADER__bindgen_ty_1 {
+ pub request: GMCAPI_HEADER__bindgen_ty_1__bindgen_ty_1,
+ pub response: GMCAPI_HEADER__bindgen_ty_1__bindgen_ty_2,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GMCAPI_HEADER__bindgen_ty_1__bindgen_ty_1 {
+ pub max_response_size: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GMCAPI_HEADER__bindgen_ty_1__bindgen_ty_2 {
+ pub status: u32_,
+}
+impl Default for GMCAPI_HEADER__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 GMCAPI_HEADER {
+ 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 GMCAPI_COMMANDS_GMCAPI_CMD_RESERVED_LAST: GMCAPI_COMMANDS = 65535;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_GSP_INIT: GMCAPI_COMMANDS = 65537;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER: GMCAPI_COMMANDS = 65538;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_HS_BINARY: GMCAPI_COMMANDS = 65539;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_GSP_SUSPEND: GMCAPI_COMMANDS = 65540;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_GSP_RESUME_DONE: GMCAPI_COMMANDS = 65541;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_ADD_VGPU_TYPE: GMCAPI_COMMANDS = 131073;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES: GMCAPI_COMMANDS = 131074;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES: GMCAPI_COMMANDS = 131075;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_ASSIGN_VGPU_TYPE: GMCAPI_COMMANDS = 131076;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_DEASSIGN_VGPU_TYPE: GMCAPI_COMMANDS = 131077;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_VGPU_PROPERTIES: GMCAPI_COMMANDS = 131078;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: GMCAPI_COMMANDS = 131079;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK: GMCAPI_COMMANDS = 131104;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK: GMCAPI_COMMANDS = 131105;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_SHUTDOWN_GSP_VGPU_PLUGIN_TASK_COMPLETE: GMCAPI_COMMANDS =
+ 131106;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_CLEANUP_GSP_VGPU_PLUGIN_RESOURCES: GMCAPI_COMMANDS = 131107;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_PLUGIN_TRIGGERED_EVENT: GMCAPI_COMMANDS = 131108;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_SCRUB_GUEST_FB: GMCAPI_COMMANDS = 131109;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_ALLOC_GSP_CEUTILS: GMCAPI_COMMANDS = 131110;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_VGPU_MGR_FREE_GSP_CEUTILS: GMCAPI_COMMANDS = 131111;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_SCHED_CONTROL: GMCAPI_COMMANDS = 196609;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_MMU_FAULT_QUEUED: GMCAPI_COMMANDS = 262145;
+pub const GMCAPI_COMMANDS_GMCAPI_CMD_INVALID: GMCAPI_COMMANDS = 4294967295;
+pub type GMCAPI_COMMANDS = ffi::c_uint;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB: GSP_DMA_TARGET = 0;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM: GSP_DMA_TARGET = 1;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM: GSP_DMA_TARGET = 2;
+pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COUNT: GSP_DMA_TARGET = 3;
+pub type GSP_DMA_TARGET = ffi::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_INIT_PARAMS {
+ pub regkeys: u32_,
+ pub reserved: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_ACR_BOOT_GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub gspRmDescSize: u32_,
+ pub gspRmDescOffset: u64_,
+ pub wprCarveoutOffset: u64_,
+ pub wprCarveoutSize: u32_,
+ pub bIsGspRmBoot: u8_,
+ pub bInstInSysMode: u8_,
+ pub bIcuEnabled: u8_,
+ pub bScrubCbcSr: u8_,
+}
+impl Default for GSP_ACR_BOOT_GSP_RM_PARAMS {
+ 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(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_RM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub reserved: u32_,
+ pub bootArgsOffset: u64_,
+}
+impl Default for GSP_RM_PARAMS {
+ 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(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_SPDM_PARAMS {
+ pub target: GSP_DMA_TARGET,
+ pub payloadBufferSize: u32_,
+ pub payloadBufferOffset: u64_,
+}
+impl Default for GSP_SPDM_PARAMS {
+ 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(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct GSP_RM_MEM_PARAMS {
+ pub flushSysmemAddrValLo: u32_,
+ pub flushSysmemAddrValHi: u32_,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone, MaybeZeroable)]
+pub struct GSP_FMC_BOOT_PARAMS {
+ pub magic: u32_,
+ pub size: u16_,
+ pub reserved0: u16_,
+ pub reserved1: [u64_; 3usize],
+ pub initParams: GSP_FMC_INIT_PARAMS,
+ pub reserved2: [u64_; 3usize],
+ pub bootGspRmParams: GSP_ACR_BOOT_GSP_RM_PARAMS,
+ pub reserved3: [u64_; 4usize],
+ pub gspRmParams: GSP_RM_PARAMS,
+ pub reserved4: [u64_; 2usize],
+ pub gspSpdmParams: GSP_SPDM_PARAMS,
+ pub reserved5: [u64_; 2usize],
+ pub gspRmMemParams: GSP_RM_MEM_PARAMS,
+ pub reserved6: [u64_; 7usize],
+}
+impl Default for GSP_FMC_BOOT_PARAMS {
+ 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(Debug, Default, Copy, Clone, MaybeZeroable)]
+pub struct rpc_unloading_guest_driver_v1F_07 {
+ pub bInPMTransition: u8_,
+ pub bGc6Entering: u8_,
+ pub __bindgen_padding_0: [u8; 2usize],
+ pub newLevel: u32_,
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
2026-08-19 3:51 ` [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
` (24 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
Move the 3-level radix page table construction out of GspFirmware
into a standalone Radix3 type in firmware/radix3.rs. The ucodes
(bindata) firmware that GSP-RM loads at runtime also needs a radix3
page table, so the logic needs to be reusable.
Refactor GspFirmware to use the new Radix3 type and expose firmware
size through a size() method instead of a public field.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/fb.rs | 2 +-
drivers/gpu/nova-core/firmware.rs | 1 +
drivers/gpu/nova-core/firmware/gsp.rs | 116 ++++----------------
drivers/gpu/nova-core/firmware/radix3.rs | 133 +++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 4 +-
5 files changed, 159 insertions(+), 97 deletions(-)
create mode 100644 drivers/gpu/nova-core/firmware/radix3.rs
diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index 1576399389b1..f208d6deb5e3 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -236,7 +236,7 @@ pub(crate) fn new(
let elf = {
const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
- let elf_size = u64::from_safe_cast(gsp_fw.size);
+ let elf_size = u64::from_safe_cast(gsp_fw.size());
let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN);
FbRange(elf_addr..elf_addr + elf_size)
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index b49613a90bf0..34657004568c 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -26,6 +26,7 @@
pub(crate) mod fsp;
pub(crate) mod fwsec;
pub(crate) mod gsp;
+pub(crate) mod radix3;
pub(crate) mod riscv;
pub(crate) mod tlv;
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
index e8f9491e84cc..48c73a676ecb 100644
--- a/drivers/gpu/nova-core/firmware/gsp.rs
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -4,21 +4,16 @@
device,
dma::{
Coherent,
- CoherentBox,
- DataDirection,
DmaAddress, //
},
firmware,
prelude::*,
- scatterlist::{
- Owned,
- SGTable, //
- },
str::CString,
};
use crate::{
firmware::{
+ radix3::Radix3,
riscv::RiscvFirmware, //
tlv::{
request_tlv, //
@@ -26,38 +21,21 @@
},
},
gpu::Chipset,
- gsp::GSP_PAGE_SIZE,
num::FromSafeCast,
};
/// GSP firmware with 3-level radix page tables for the GSP bootloader.
///
-/// The bootloader expects firmware to be mapped starting at address 0 in GSP's virtual address
-/// space:
-///
-/// ```text
-/// Level 0: 1 page, 1 entry -> points to first level 1 page
-/// Level 1: Multiple pages/entries -> each entry points to a level 2 page
-/// Level 2: Multiple pages/entries -> each entry points to a firmware page
-/// ```
-///
-/// Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
/// Also known as "Radix3" firmware.
#[pin_data]
pub(crate) struct GspFirmware {
- /// The GSP firmware inside a [`VVec`], device-mapped via a SG table.
- #[pin]
- fw: SGTable<Owned<VVec<u8>>>,
- /// Level 2 page table whose entries contain DMA addresses of firmware pages.
- #[pin]
- level2: SGTable<Owned<VVec<u8>>>,
- /// Level 1 page table whose entries contain DMA addresses of level 2 pages.
+ /// The GSP firmware image mapped via a 3-level radix page table.
#[pin]
- level1: SGTable<Owned<VVec<u8>>>,
- /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
- level0: Coherent<[u64]>,
- /// Size in bytes of the firmware contained in [`Self::fw`].
- pub(crate) size: usize,
+ radix3: Radix3,
+ /// Firmware file path requested from userspace.
+ pub(crate) fw_path: CString,
+ /// Firmware version from the TLV metadata.
+ pub(crate) fw_version: CString,
/// Device-mapped GSP signatures matching the GPU's [`Chipset`].
pub(crate) signatures: Coherent<[u8]>,
/// GSP bootloader, verifies the GSP firmware before loading and running it.
@@ -74,7 +52,12 @@ pub(crate) fn new<'a>(
pin_init::pin_init_scope(move || {
let firmware = request_tlv(dev, chipset, "gsp")?;
let tlv = Tlv::new(firmware.data())?;
- dev_dbg!(dev, "loaded gsp firmware v{}\n", tlv.get_string(b"VERS")?);
+ let fw_version = CString::try_from_fmt(fmt!("{}", tlv.get_string(b"VERS")?))?;
+ dev_dbg!(
+ dev,
+ "loaded gsp firmware v{}\n",
+ fw_version.to_str().unwrap_or("unknown")
+ );
let size = usize::from_safe_cast(tlv.get_u32(b"SIZE")?);
let mut fw_vvec = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?;
@@ -87,49 +70,9 @@ pub(crate) fn new<'a>(
let signatures = Coherent::from_slice(dev, tlv.get_bytes(b"SIGN")?, GFP_KERNEL)?;
Ok(try_pin_init!(Self {
- fw <- SGTable::new(dev, fw_vvec, DataDirection::ToDevice, GFP_KERNEL),
- level2 <- {
- // Allocate the level 2 page table, map the firmware onto it, and map it into
- // the device address space.
- VVec::<u8>::with_capacity(
- fw.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level2| map_into_lvl(&fw, level2))
- .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
- },
- level1 <- {
- // Allocate the level 1 page table, map the level 2 page table onto it, and map
- // it into the device address space.
- VVec::<u8>::with_capacity(
- level2.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level1| map_into_lvl(&level2, level1))
- .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
- },
- level0: {
- // Allocate the level 0 page table as a device-visible DMA object, and map the
- // level 1 page table onto it.
-
- // Fill level 1 page entry.
- let level1_entry = level1.iter().next().ok_or(EINVAL)?;
- let level1_entry_addr = level1_entry.dma_address();
-
- // Create level 0 page table data and fill its first entry with the level 1
- // table.
- let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
- dev,
- GSP_PAGE_SIZE / size_of::<u64>(),
- GFP_KERNEL
- )?;
- level0[0] = level1_entry_addr.to_le();
-
- level0.into()
- },
- size,
+ radix3 <- Radix3::new(dev, fw_vvec),
+ fw_path: filename,
+ fw_version,
signatures,
bootloader: {
let bl = request_tlv(dev, chipset, "gsp_bootloader")?;
@@ -140,28 +83,13 @@ pub(crate) fn new<'a>(
})
}
- /// Returns the DMA address of the radix3 level 0 page table.
- pub(crate) fn radix3_dma_address(&self) -> DmaAddress {
- self.level0.dma_address()
+ /// Returns the size of the GSP firmware image, in bytes.
+ pub(crate) fn size(&self) -> usize {
+ self.radix3.size()
}
-}
-/// Build a page table from a scatter-gather list.
-///
-/// Takes each DMA-mapped region from `sg_table` and writes page table entries
-/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
-/// 4 consecutive page table entries.
-fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
- for sg_entry in sg_table.iter() {
- // Number of pages we need to map.
- let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);
-
- for i in 0..num_pages {
- let entry = sg_entry.dma_address()
- + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));
- dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
- }
+ /// Returns the DMA address of the radix3 level 0 page table.
+ pub(crate) fn radix3_dma_address(&self) -> DmaAddress {
+ self.radix3.dma_address()
}
-
- Ok(dst)
}
diff --git a/drivers/gpu/nova-core/firmware/radix3.rs b/drivers/gpu/nova-core/firmware/radix3.rs
new file mode 100644
index 000000000000..b60611c7bea0
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/radix3.rs
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! 3-level radix page table for GSP firmware data.
+//!
+//! The GSP bootloader expects data to be mapped via a 3-level page table:
+//!
+//! ```text
+//! Level 0: 1 page, 1 entry -> points to first level 1 page
+//! Level 1: Multiple pages/entries -> each entry points to a level 2 page
+//! Level 2: Multiple pages/entries -> each entry points to a data page
+//! ```
+//!
+//! Each page is 4KB, each entry is 8 bytes (64-bit DMA address).
+
+use core::mem::size_of;
+
+use kernel::{
+ device,
+ dma::{
+ Coherent,
+ CoherentBox,
+ DataDirection,
+ DmaAddress, //
+ },
+ prelude::*,
+ scatterlist::{
+ Owned,
+ SGTable, //
+ },
+};
+
+use crate::{
+ gsp::GSP_PAGE_SIZE,
+ num::FromSafeCast, //
+};
+
+/// 3-level radix page table mapping arbitrary data for the GSP.
+#[pin_data]
+pub(crate) struct Radix3 {
+ /// The data mapped via a SG table.
+ #[pin]
+ data: SGTable<Owned<VVec<u8>>>,
+ /// Level 2 page table whose entries contain DMA addresses of data pages.
+ #[pin]
+ level2: SGTable<Owned<VVec<u8>>>,
+ /// Level 1 page table whose entries contain DMA addresses of level 2 pages.
+ #[pin]
+ level1: SGTable<Owned<VVec<u8>>>,
+ /// Level 0 page table (single 4KB page) with one entry: DMA address of first level 1 page.
+ level0: Coherent<[u64]>,
+ /// Size in bytes of the data contained in [`Self::data`].
+ size: usize,
+}
+
+impl Radix3 {
+ /// Builds a 3-level radix page table that maps `data` into `dev`'s DMA address space.
+ ///
+ /// Takes ownership of `data`. May sleep.
+ pub(crate) fn new<'a>(
+ dev: &'a device::Device<device::Bound>,
+ data: VVec<u8>,
+ ) -> impl PinInit<Self, Error> + 'a {
+ let size = data.len();
+
+ pin_init::pin_init_scope(move || {
+ Ok(try_pin_init!(Self {
+ data <- SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL),
+ level2 <- {
+ VVec::<u8>::with_capacity(
+ data.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|level2| map_into_lvl(&data, level2))
+ .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ level1 <- {
+ VVec::<u8>::with_capacity(
+ level2.iter().count() * core::mem::size_of::<u64>(),
+ GFP_KERNEL,
+ )
+ .map_err(|_| ENOMEM)
+ .and_then(|level1| map_into_lvl(&level2, level1))
+ .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
+ },
+ level0: {
+ let level1_entry = level1.iter().next().ok_or(EINVAL)?;
+ let level1_entry_addr = level1_entry.dma_address();
+
+ let mut level0 = CoherentBox::<[u64]>::zeroed_slice(
+ dev,
+ GSP_PAGE_SIZE / size_of::<u64>(),
+ GFP_KERNEL,
+ )?;
+ level0[0] = level1_entry_addr.to_le();
+
+ level0.into()
+ },
+ size,
+ }))
+ })
+ }
+
+ /// Returns the DMA address of the radix3 level 0 page table.
+ pub(crate) fn dma_address(&self) -> DmaAddress {
+ self.level0.dma_address()
+ }
+
+ /// Returns the size of the mapped data, in bytes.
+ pub(crate) fn size(&self) -> usize {
+ self.size
+ }
+}
+
+/// Build a page table from a scatter-gather list.
+///
+/// Takes each DMA-mapped region from `sg_table` and writes page table entries
+/// for all 4KB pages within that region. For example, a 16KB SG entry becomes
+/// 4 consecutive page table entries.
+fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Result<VVec<u8>> {
+ for sg_entry in sg_table.iter() {
+ let num_pages = usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE);
+
+ for i in 0..num_pages {
+ let entry = sg_entry.dma_address()
+ + (u64::from_safe_cast(i) * u64::from_safe_cast(GSP_PAGE_SIZE));
+ dst.extend_from_slice(&entry.to_le_bytes(), GFP_KERNEL)?;
+ }
+ }
+
+ Ok(dst)
+}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 9b29308678a7..d3678f16750a 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -188,7 +188,7 @@ pub(crate) fn from_ranges<'a>(
magic: bindings::GSP_FW_WPR_META_MAGIC as u64,
revision: u64::from(bindings::GSP_FW_WPR_META_REVISION),
sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(),
- sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size),
+ sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()),
sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(),
sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()),
bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset),
@@ -243,7 +243,7 @@ pub(crate) fn from_sizes<'a>(
magic: bindings::GSP_FW_WPR_META_MAGIC as u64,
revision: u64::from(bindings::GSP_FW_WPR_META_REVISION),
sysmemAddrOfRadix3Elf: gsp_firmware.radix3_dma_address(),
- sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size),
+ sizeOfRadix3Elf: u64::from_safe_cast(gsp_firmware.size()),
sysmemAddrOfBootloader: gsp_firmware.bootloader.ucode.dma_address(),
sizeOfBootloader: u64::from_safe_cast(gsp_firmware.bootloader.ucode.size()),
bootloaderCodeOffset: u64::from(gsp_firmware.bootloader.code_offset),
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
2026-08-19 3:51 ` [PATCH 01/27] gpu: nova-core: firmware: add r000 bindings John Hubbard
2026-08-19 3:51 ` [PATCH 02/27] gpu: nova-core: extract radix3 page table into its own module John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
` (23 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
FSP requires MCTP transport header version 1. Nova-core leaves version
bits 3:0 at zero, so every CoT and PRC request carries an invalid
transport header.
Define the version and destination endpoint ID fields. Set the version
to 1 for single-packet requests, and leave the unused destination
endpoint ID at zero.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/mctp.rs | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index 90c642c91a72..03546213da28 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -42,13 +42,23 @@ pub(crate) struct MctpHeader(u32) {
29:28 seq;
/// Source endpoint ID.
23:16 seid;
+ /// Destination endpoint ID.
+ 15:8 deid;
+ /// MCTP header version.
+ 3:0 version;
}
}
impl MctpHeader {
- /// Builds a single-packet MCTP header (`SOM=1`, `EOM=1`, `SEQ=0`, `SEID=0`).
+ /// MCTP specification version 1.0.
+ const VERSION: u32 = 1;
+
+ /// Builds a single-packet MCTP header (`SOM=1`, `EOM=1`, `SEQ=0`, `SEID=0`, `VERSION=1`).
pub(crate) fn single_packet() -> Self {
- Self::zeroed().with_som(true).with_eom(true)
+ Self::zeroed()
+ .with_const_version::<{ Self::VERSION }>()
+ .with_som(true)
+ .with_eom(true)
}
/// Returns whether this is a complete single-packet message (`SOM=1` and `EOM=1`).
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (2 preceding siblings ...)
2026-08-19 3:51 ` [PATCH 03/27] gpu: nova-core: set MCTP transport header version to 1 John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:51 ` [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary John Hubbard
` (22 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 LOAD_EXEC events provide addresses for images that the driver
must copy into GSP Falcon IMEM and DMEM. The existing Falcon DMA path
creates its source buffer from a FalconFirmware object, so it cannot use
those addresses. The protocol also reports GSP RISC-V suspension through
MAILBOX0 bit 31 rather than CPUCTL.halted.
Add Falcon operations for DMA from event-provided addresses and for the
RISC-V suspend indication.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 136 +++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/regs.rs | 2 +-
2 files changed, 136 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 65cb12d26e2b..20a288050c37 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -133,12 +133,25 @@ pub(crate) enum FalconMem {
/// Secure Instruction Memory.
ImemSecure,
/// Non-Secure Instruction Memory.
- #[expect(unused)]
+ #[expect(dead_code)]
ImemNonSecure,
/// Data Memory.
Dmem,
}
+/// Source offset of a raw falcon DMA transfer, added to the DMA base address.
+#[expect(dead_code)]
+#[derive(Copy, Clone)]
+pub(crate) enum FalconDmaSrcOffset {
+ /// Byte offset from the DMA base address.
+ Offset(u32),
+ /// DMEM virtual address. The DMA engine also tags each loaded block with this value, so the
+ /// falcon reaches the data through its DMEM VA. The caller must bias the DMA base address so
+ /// that base plus this value addresses the first byte of the image. Only valid with
+ /// [`FalconMem::Dmem`].
+ DmemVa(u32),
+}
+
bounded_enum! {
/// Defines the Framebuffer Interface (FBIF) aperture type.
/// This determines the memory type for external memory access during a DMA transfer, which is
@@ -592,6 +605,109 @@ fn dma_wr(
Ok(())
}
+ /// Perform a raw DMA transfer from a physical address to falcon IMEM or DMEM.
+ ///
+ /// Used by the GSP boot event handlers where the firmware provides the source
+ /// address directly (in GPU physical address space, routed through the FBIF
+ /// aperture). The caller must configure the FBIF TRANSCFG register
+ /// corresponding to `ctx_dma` before calling this method.
+ ///
+ /// A [`FalconDmaSrcOffset::DmemVa`] source offset also sets `DMATRFCMD.SET_DMTAG`, and
+ /// requires `target_mem` to be [`FalconMem::Dmem`].
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if `ctx_dma` is not a valid context DMA slot, if `src_addr` is not aligned to
+ /// 256 bytes, or if a [`FalconDmaSrcOffset::DmemVa`] source offset is paired with an IMEM
+ /// target.
+ /// - `ERANGE` if `src_addr` does not fit the `DMATRFBASE` register pair.
+ #[expect(dead_code)]
+ pub(crate) fn raw_dma_transfer(
+ &self,
+ ctx_dma: u8,
+ src_addr: u64,
+ target_mem: FalconMem,
+ src: FalconDmaSrcOffset,
+ dst_offset: u32,
+ len: u32,
+ ) -> Result {
+ const DMA_LEN: u32 = num::usize_into_u32::<{ MEM_BLOCK_ALIGNMENT }>();
+ const NUM_CTXDMA_SLOTS: u8 = 8;
+
+ if ctx_dma >= NUM_CTXDMA_SLOTS {
+ dev_err!(self.dev, "raw DMA: ctx_dma {} out of range\n", ctx_dma);
+ return Err(EINVAL);
+ }
+
+ if src_addr % u64::from(DMA_LEN) > 0 {
+ dev_err!(
+ self.dev,
+ "raw DMA: source address {:#x} not 256B-aligned\n",
+ src_addr
+ );
+ return Err(EINVAL);
+ }
+
+ if src_addr >> 40 > u64::from(regs::NV_PFALCON_FALCON_DMATRFBASE1::BASE_MASK) {
+ dev_err!(
+ self.dev,
+ "raw DMA: source address {:#x} does not fit DMATRFBASE\n",
+ src_addr
+ );
+ return Err(ERANGE);
+ }
+
+ let (src_offset, set_dmtag) = match src {
+ FalconDmaSrcOffset::Offset(offset) => (offset, false),
+ // `SET_DMTAG` applies to DMEM only.
+ FalconDmaSrcOffset::DmemVa(_) if target_mem != FalconMem::Dmem => return Err(EINVAL),
+ FalconDmaSrcOffset::DmemVa(va) => (va, true),
+ };
+
+ let num_transfers = len.div_ceil(DMA_LEN);
+
+ self.bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFBASE::zeroed().with_base(
+ // CAST: `as u32` is used on purpose since we do want to strip the upper bits,
+ // which will be written to `NV_PFALCON_FALCON_DMATRFBASE1`.
+ (src_addr >> 8) as u32,
+ ),
+ );
+ self.bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFBASE1::zeroed().try_with_base(src_addr >> 40)?,
+ );
+
+ let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed()
+ .with_size(DmaTrfCmdSize::Size256B)
+ .try_with_ctxdma(u32::from(ctx_dma))?
+ .with_falcon_mem(target_mem)
+ .with_set_dmtag(set_dmtag);
+
+ for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
+ self.bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed().try_with_offs(dst_offset + pos)?,
+ );
+ self.bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_offset + pos),
+ );
+
+ self.bar.write(WithBase::of::<E>(), cmd);
+
+ read_poll_timeout(
+ || Ok(self.bar.read(regs::NV_PFALCON_FALCON_DMATRFCMD::of::<E>())),
+ |r| r.idle(),
+ Delta::ZERO,
+ Delta::from_secs(2),
+ )?;
+ }
+
+ Ok(())
+ }
+
/// Perform a DMA load into `IMEM` and `DMEM` of `fw`, and prepare the falcon to run it.
fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(&self, fw: &F) -> Result {
// DMA object with firmware content as the source of the DMA engine.
@@ -647,6 +763,24 @@ pub(crate) fn wait_till_halted(&self) -> Result<()> {
Ok(())
}
+ /// Wait until the GSP processor has suspended.
+ ///
+ /// The RISC-V GSP signals suspension by setting bit 31 (`0x8000_0000`) in `MAILBOX0`, rather
+ /// than through `CPUCTL.halted`.
+ #[expect(dead_code)]
+ pub(crate) fn wait_for_processor_suspend(&self) -> Result<()> {
+ const INTERRUPT_PROCESSOR_SUSPENDED: u32 = 0x8000_0000;
+
+ read_poll_timeout(
+ || Ok(self.read_mailbox0()),
+ |val| (*val & INTERRUPT_PROCESSOR_SUSPENDED) != 0,
+ Delta::ZERO,
+ Delta::from_secs(2),
+ )?;
+
+ Ok(())
+ }
+
/// Start the falcon CPU.
pub(crate) fn start(&self) -> Result<()> {
match self
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 01fde2c5e5a6..5d265b5788e3 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -264,7 +264,7 @@ pub(crate) fn usable_fb_size(self) -> u64 {
}
pub(crate) NV_PFALCON_FALCON_DMATRFCMD(u32) @ PFalconBase + 0x00000118 {
- 16:16 set_dmtag;
+ 16:16 set_dmtag => bool;
14:12 ctxdma;
10:8 size ?=> DmaTrfCmdSize;
5:5 is_write => bool;
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (3 preceding siblings ...)
2026-08-19 3:51 ` [PATCH 04/27] gpu: nova-core: add Falcon helpers for r000 LOAD_EXEC events John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:51 ` [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs John Hubbard
` (21 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
GSP-RM allocates the whole radix3 page table region in one block and
zeroes it before writing entries. The booter reads each level a full
page at a time when it walks the table, so every byte of a level page is
firmware-visible whether or not it holds a valid entry.
The driver allocates each level separately and writes only the valid
entries, so the last page of each level is only partly written. The
booter reads the whole page regardless, and a non-zero word in the
unwritten remainder is indistinguishable from an entry.
Zero-pad each level to the next GSP_PAGE_SIZE boundary after writing
entries, and size each level buffer to that padded length so the pad
does not force a reallocation.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/firmware/radix3.rs | 40 +++++++++++++++---------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/radix3.rs b/drivers/gpu/nova-core/firmware/radix3.rs
index b60611c7bea0..f14ad4e82d3d 100644
--- a/drivers/gpu/nova-core/firmware/radix3.rs
+++ b/drivers/gpu/nova-core/firmware/radix3.rs
@@ -67,22 +67,18 @@ pub(crate) fn new<'a>(
Ok(try_pin_init!(Self {
data <- SGTable::new(dev, data, DataDirection::ToDevice, GFP_KERNEL),
level2 <- {
- VVec::<u8>::with_capacity(
- data.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level2| map_into_lvl(&data, level2))
- .map(|level2| SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL))?
+ let level2 = VVec::<u8>::with_capacity(lvl_size(&data), GFP_KERNEL)
+ .map_err(|_| ENOMEM)
+ .and_then(|level2| map_into_lvl(&data, level2))?;
+
+ SGTable::new(dev, level2, DataDirection::ToDevice, GFP_KERNEL)
},
level1 <- {
- VVec::<u8>::with_capacity(
- level2.iter().count() * core::mem::size_of::<u64>(),
- GFP_KERNEL,
- )
- .map_err(|_| ENOMEM)
- .and_then(|level1| map_into_lvl(&level2, level1))
- .map(|level1| SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL))?
+ let level1 = VVec::<u8>::with_capacity(lvl_size(&level2), GFP_KERNEL)
+ .map_err(|_| ENOMEM)
+ .and_then(|level1| map_into_lvl(&level2, level1))?;
+
+ SGTable::new(dev, level1, DataDirection::ToDevice, GFP_KERNEL)
},
level0: {
let level1_entry = level1.iter().next().ok_or(EINVAL)?;
@@ -113,6 +109,17 @@ pub(crate) fn size(&self) -> usize {
}
}
+/// Returns the size, in bytes, of the page table level that maps `sg_table`: one `u64` entry per
+/// 4KB page it spans, rounded up to the page boundary that [`map_into_lvl`] pads to.
+fn lvl_size(sg_table: &SGTable<Owned<VVec<u8>>>) -> usize {
+ let entries: usize = sg_table
+ .iter()
+ .map(|sg_entry| usize::from_safe_cast(sg_entry.dma_len()).div_ceil(GSP_PAGE_SIZE))
+ .sum();
+
+ (entries * size_of::<u64>()).next_multiple_of(GSP_PAGE_SIZE)
+}
+
/// Build a page table from a scatter-gather list.
///
/// Takes each DMA-mapped region from `sg_table` and writes page table entries
@@ -129,5 +136,10 @@ fn map_into_lvl(sg_table: &SGTable<Owned<VVec<u8>>>, mut dst: VVec<u8>) -> Resul
}
}
+ // The last page of a level is only partly filled, and the booter DMAs each level a
+ // whole page at a time, so no entry past the last valid one may hold a stale address.
+ let padded = dst.len().next_multiple_of(GSP_PAGE_SIZE);
+ dst.resize(padded, 0, GFP_KERNEL)?;
+
Ok(dst)
}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (4 preceding siblings ...)
2026-08-19 3:51 ` [PATCH 05/27] gpu: nova-core: zero-pad radix3 page table levels to page boundary John Hubbard
@ 2026-08-19 3:51 ` John Hubbard
2026-08-19 3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
` (20 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:51 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The receive path logged every message at the transport layer and did not
distinguish events from command replies. Async sends shared the RPC
sequence with waited-for commands, and GSP-initiated events arrived
with that field unset.
Print a distinct debug line for async send, sync send, event, and
command reply. Number async sends and events with driver-local counters.
IS_ASYNC selects only the send log line. Every command still carries the
next RPC sequence on the wire.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 111 ++++++++++++++----
.../gpu/nova-core/gsp/cmdq/continuation.rs | 1 +
drivers/gpu/nova-core/gsp/commands.rs | 2 +
drivers/gpu/nova-core/gsp/fw.rs | 19 +++
4 files changed, 107 insertions(+), 26 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index ac3e6642031a..d3afe3f9bb37 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -87,6 +87,12 @@ pub(crate) trait CommandToGsp {
/// Function identifying this command to the GSP.
const FUNCTION: MsgFunction;
+ /// Classifies the send-side debug log as an async send.
+ ///
+ /// Default is `false`. Set this only on commands that are sent without waiting for a reply. A
+ /// [`NoReply`] continuation of a waited-for command stays `false`.
+ const IS_ASYNC: bool = false;
+
/// Type generated by [`CommandToGsp::init`], to be written into the command queue buffer.
type Command: FromBytes + AsBytes;
@@ -528,6 +534,8 @@ pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Err
gsp_mem,
elem_seq: 0,
rpc_seq: 0,
+ tx_async_seq: 0,
+ rx_event_seq: 0,
poisoned: Cell::new(false),
}),
}))
@@ -672,6 +680,12 @@ struct CmdqInner {
/// [`CmdqInner::receive_msg`] match that reply to the awaiting command. Advances once per
/// logical command.
rpc_seq: u32,
+ /// Debug-log sequence for async sends. Those commands do not wait for a reply, so this
+ /// counter is the number printed in the send log.
+ tx_async_seq: u32,
+ /// Debug-log sequence for GSP-initiated events. The GSP leaves the RPC sequence unset on
+ /// those messages, so the driver numbers them itself.
+ rx_event_seq: u32,
/// Set once a message with corrupt framing or a bad checksum is seen. Such a message has an
/// untrusted length, so the queue cannot be advanced past it, and every later receive fails
/// until the queue is torn down and reset.
@@ -738,13 +752,24 @@ fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M, rpc_seq: u32) ->
dst.contents.1,
])));
- dev_dbg!(
- &self.dev,
- "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
- rpc_seq,
- M::FUNCTION,
- dst.header.length(),
- );
+ if M::IS_ASYNC {
+ dev_dbg!(
+ &self.dev,
+ "GSP RPC: async send: seq# {}, function={:?}, length=0x{:x}\n",
+ self.tx_async_seq,
+ M::FUNCTION,
+ dst.header.length(),
+ );
+ self.tx_async_seq += 1;
+ } else {
+ dev_dbg!(
+ &self.dev,
+ "GSP RPC: send: seq# {}, function={:?}, length=0x{:x}\n",
+ rpc_seq,
+ M::FUNCTION,
+ dst.header.length(),
+ );
+ }
// All set - update the write pointer and inform the GSP of the new command.
let elem_count = dst.header.element_count();
@@ -826,14 +851,6 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
return Err(EIO);
};
- dev_dbg!(
- &self.dev,
- "GSP RPC: receive: seq# {}, function={:?}, length=0x{:x}\n",
- header.sequence(),
- header.function(),
- header.length(),
- );
-
let payload_length = header.payload_length();
// Check that the driver read area is large enough for the message.
@@ -877,6 +894,41 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
})
}
+ /// Writes the classified receive debug line for a decoded GSP message.
+ ///
+ /// Events print [`Self::rx_event_seq`]. Command replies print `seq`. An unrecognized function
+ /// code prints nothing here.
+ fn log_received(&self, function: Result<MsgFunction, u32>, seq: u32, length: usize) {
+ match function {
+ Ok(f) if f.is_event() => {
+ dev_dbg!(
+ &self.dev,
+ "GSP RPC: async received: seq# {}, function={:?}, length=0x{:x}\n",
+ self.rx_event_seq,
+ f,
+ length,
+ );
+ }
+ Ok(f) => {
+ dev_dbg!(
+ &self.dev,
+ "GSP RPC: response received: seq# {}, function={:?}, length=0x{:x}\n",
+ seq,
+ f,
+ length,
+ );
+ }
+ Err(_) => {}
+ }
+ }
+
+ /// Advances [`Self::rx_event_seq`] after a GSP-initiated event has been consumed.
+ fn advance_rx_event_seq(&mut self, function: Result<MsgFunction, u32>) {
+ if matches!(function, Ok(f) if f.is_event()) {
+ self.rx_event_seq += 1;
+ }
+ }
+
/// Receive a message from the GSP.
///
/// The expected message type is given by the `M` generic parameter. With `expected_seq` set,
@@ -910,9 +962,12 @@ fn receive_msg<M: MessageFromGsp>(
let message = self.wait_for_msg(timeout)?;
let function = message.header.function();
let seq = message.header.sequence();
+ let length = message.header.length();
let func_matches = matches!(function, Ok(f) if f == M::FUNCTION);
let matched = func_matches && expected_seq.is_none_or(|expected| seq == expected);
+ self.log_received(function, seq, length);
+
// Every path must advance the read pointer past this message, including a failed decode.
let result = if matched {
match M::Message::from_bytes_prefix(message.contents.0) {
@@ -938,9 +993,10 @@ fn receive_msg<M: MessageFromGsp>(
};
// Advance the read pointer past this message.
- self.gsp_mem.advance_cpu_read_ptr(u32::try_from(
- message.header.length().div_ceil(GSP_PAGE_SIZE),
- )?);
+ self.gsp_mem
+ .advance_cpu_read_ptr(u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?);
+
+ self.advance_rx_event_seq(function);
if !matched {
if func_matches {
@@ -962,8 +1018,8 @@ fn receive_msg<M: MessageFromGsp>(
/// Routes a GSP message that is not the reply a caller is waiting for.
///
/// GSP-reported errors are logged at error level and unrecognized function codes at warning
- /// level. Every other known function code is consumed without a log line, because the RPC
- /// receive trace in [`Self::wait_for_msg`] already records its arrival.
+ /// level. Every other known function code is consumed without a log line, because
+ /// [`Self::log_received`] already records its arrival.
fn dispatch_event(&self, function: Result<MsgFunction, u32>, seq: u32) {
match function {
Ok(MsgFunction::OsErrorLog) => {
@@ -1004,16 +1060,19 @@ fn drain(&mut self) -> Result {
while !self.gsp_mem.driver_read_area().0.is_empty() {
// A message is available, so this returns without waiting.
let msg = self.wait_for_msg(Delta::ZERO)?;
-
- let pages =
- u32::try_from(msg.header.length().div_ceil(GSP_PAGE_SIZE)).map_err(|_| {
- dev_err!(&self.dev, "GSP drain: message length overflow\n");
- EIO
- })?;
let function = msg.header.function();
let seq = msg.header.sequence();
+ let length = msg.header.length();
+
+ self.log_received(function, seq, length);
+
+ let pages = u32::try_from(length.div_ceil(GSP_PAGE_SIZE)).map_err(|_| {
+ dev_err!(&self.dev, "GSP drain: message length overflow\n");
+ EIO
+ })?;
self.gsp_mem.advance_cpu_read_ptr(pages);
+ self.advance_rx_event_seq(function);
self.dispatch_event(function, seq);
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
index 05e904f18097..a606c7be52a7 100644
--- a/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq/continuation.rs
@@ -147,6 +147,7 @@ fn new(command: C, payload: KVVec<u8>) -> Self {
impl<C: CommandToGsp> CommandToGsp for SplitCommand<C> {
const FUNCTION: MsgFunction = C::FUNCTION;
+ const IS_ASYNC: bool = C::IS_ASYNC;
type Command = C::Command;
type Reply = C::Reply;
type InitError = C::InitError;
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 61fe93db9e7e..d5575c036eb9 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -52,6 +52,7 @@ pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Sel
impl<'a> CommandToGsp for SetSystemInfo<'a> {
const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
+ const IS_ASYNC: bool = true;
type Command = fw::commands::GspSetSystemInfo;
type Reply = NoReply;
type InitError = Error;
@@ -122,6 +123,7 @@ pub(crate) fn new(vgpu_state: VgpuState) -> Result<Self> {
impl CommandToGsp for SetRegistry {
const FUNCTION: MsgFunction = MsgFunction::SetRegistry;
+ const IS_ASYNC: bool = true;
type Command = fw::commands::PackedRegistryTable;
type Reply = NoReply;
type InitError = Infallible;
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index d3678f16750a..857d53c77297 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -359,6 +359,25 @@ fn try_from(value: u32) -> Result<MsgFunction> {
}
}
+impl MsgFunction {
+ /// Returns true if this is a GSP-initiated async event (`NV_VGPU_MSG_EVENT_*`), as opposed to
+ /// a command response (`NV_VGPU_MSG_FUNCTION_*`).
+ pub(crate) fn is_event(&self) -> bool {
+ matches!(
+ self,
+ Self::GspInitDone
+ | Self::GspRunCpuSequencer
+ | Self::PostEvent
+ | Self::RcTriggered
+ | Self::MmuFaultQueued
+ | Self::OsErrorLog
+ | Self::GspPostNoCat
+ | Self::GspLockdownNotice
+ | Self::UcodeLibOsPrint //
+ )
+ }
+}
+
impl From<MsgFunction> for u32 {
fn from(value: MsgFunction) -> Self {
// CAST: `MsgFunction` is `repr(u32)` and can thus be cast losslessly.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (5 preceding siblings ...)
2026-08-19 3:51 ` [PATCH 06/27] gpu: nova-core: distinguish async GSP RPC traffic in debug logs John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer John Hubbard
` (19 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The new GSP firmware takes an optional ucodes image, a bindata blob of
microcode that GSP-RM loads at runtime. Open RM fetches it for every
chip family except GB10Y and fails firmware load without it, so the
driver will require it too once it boots the new firmware. The helper
added here reports a missing metadata file rather than failing, and
leaves that policy to its caller.
Load the ucodes metadata with the existing TLV request helper, and add
support for the generic TLV payload convention it uses: either an inline
BLOB, or a FILE basename with its SIZE. A FILE tag may only name a
basename in the metadata file's own directory, so one carrying a path
separator, a special directory name, or a control byte is rejected.
Nothing calls this until the driver switches to the new firmware.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/firmware.rs | 8 +++-
drivers/gpu/nova-core/firmware/bindata.rs | 38 +++++++++++++++
drivers/gpu/nova-core/firmware/tlv.rs | 56 +++++++++++++++++++++++
3 files changed, 101 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/nova-core/firmware/bindata.rs
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index 34657004568c..dfd41364cc77 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -22,6 +22,7 @@
num::IntoSafeCast, //
};
+pub(crate) mod bindata;
pub(crate) mod booter;
pub(crate) mod fsp;
pub(crate) mod fwsec;
@@ -348,7 +349,12 @@ const fn make_entry_chipset(self, chipset: gpu::Chipset) -> Self {
let mut this = self
.make_entry_file(name, "gsp_bootloader.tlv")
.make_entry_file(name, "gsp.tlv")
- .make_entry_file(name, "gsp.bin");
+ .make_entry_file(name, "gsp.bin")
+ .make_entry_file(name, "ucodes.tlv")
+ // `ucodes.tlv` may name another safe basename in its FILE tag, but module firmware
+ // metadata cannot describe a runtime-selected filename. Advertise the conventional
+ // filename used by the distributed metadata.
+ .make_entry_file(name, "ucodes.bin");
// Add the firmware files specific to the GSP boot method of `chipset`.
let boot_files = boot_firmware_files(chipset);
diff --git a/drivers/gpu/nova-core/firmware/bindata.rs b/drivers/gpu/nova-core/firmware/bindata.rs
new file mode 100644
index 000000000000..d9625ab7d738
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/bindata.rs
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! GSP bindata firmware loading.
+
+use kernel::{
+ device,
+ prelude::*, //
+};
+
+use crate::{
+ firmware::tlv::{
+ request_tlv,
+ Tlv, //
+ },
+ gpu::Chipset,
+};
+
+/// Requests the optional `ucodes` bindata metadata and loads its payload.
+///
+/// A missing metadata file is reported as [`None`] so that the eventual caller can decide whether
+/// ucodes are optional for its boot path. Once the metadata has been found, all parse and payload
+/// loading errors, including a missing referenced file, are returned as errors.
+#[expect(dead_code)]
+pub(crate) fn request_ucodes_firmware(
+ dev: &device::Device,
+ chipset: Chipset,
+) -> Result<Option<VVec<u8>>> {
+ let firmware = match request_tlv(dev, chipset, "ucodes") {
+ Ok(firmware) => firmware,
+ Err(e) if e == ENOENT => return Ok(None),
+ Err(e) => return Err(e),
+ };
+
+ let tlv = Tlv::new(firmware.data())?;
+
+ Ok(Some(tlv.load_blob_or_file(dev, chipset)?))
+}
diff --git a/drivers/gpu/nova-core/firmware/tlv.rs b/drivers/gpu/nova-core/firmware/tlv.rs
index 7b879f13a61e..b9aabf9ee952 100644
--- a/drivers/gpu/nova-core/firmware/tlv.rs
+++ b/drivers/gpu/nova-core/firmware/tlv.rs
@@ -198,6 +198,62 @@ fn iter(&self) -> TlvIter<'_, 'a> {
self.iter().find(|b| b.tag == *tag).ok_or(EINVAL)
}
+ fn contains(&self, tag: &[u8; 4]) -> bool {
+ self.iter().any(|b| b.tag == *tag)
+ }
+
+ /// Loads the payload described by this TLV into owned memory.
+ ///
+ /// The generic firmware TLV representation is either an inline `BLOB`, or a `FILE` basename
+ /// together with its `SIZE`. Mixing the representations or specifying only half of the file
+ /// representation is invalid. Referenced files are constrained to the metadata file's
+ /// directory.
+ pub(crate) fn load_blob_or_file(
+ &self,
+ dev: &device::Device,
+ chipset: gpu::Chipset,
+ ) -> Result<VVec<u8>> {
+ let has_blob = self.contains(b"BLOB");
+ let has_file = self.contains(b"FILE");
+ let has_size = self.contains(b"SIZE");
+
+ match (has_blob, has_file, has_size) {
+ (true, false, false) => {
+ let blob = self.get_bytes(b"BLOB")?;
+ let mut data = VVec::with_capacity(blob.len(), GFP_KERNEL).map_err(|_| ENOMEM)?;
+ data.extend_from_slice(blob, GFP_KERNEL)?;
+ Ok(data)
+ }
+ (false, true, true) => {
+ let file = self.get_string(b"FILE")?;
+
+ // FILE is a basename relative to the TLV's directory, not an arbitrary firmware
+ // path. Reject path separators, special directory names, NULs and control bytes.
+ if file.is_empty()
+ || matches!(file, "." | "..")
+ || file
+ .bytes()
+ .any(|b| b == b'/' || b == b'\\' || b.is_ascii_control())
+ {
+ return Err(EINVAL);
+ }
+
+ let size = usize::from_safe_cast(self.get_u32(b"SIZE")?);
+ if size == 0 {
+ return Err(EINVAL); // TODO: Use ENODATA once available.
+ }
+
+ let mut data = VVec::zeroed(size, GFP_KERNEL).map_err(|_| ENOMEM)?;
+ let chip_name = chipset.name();
+ let path = CString::try_from_fmt(fmt!("nvidia/{chip_name}/gsp/{file}"))?;
+ firmware::request_into_buf(&path, dev, data.as_mut_slice())?;
+
+ Ok(data)
+ }
+ _ => Err(EINVAL),
+ }
+ }
+
/// Return a slice of bytes.
///
/// Returns `EINVAL` if the value is empty.
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (6 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 07/27] gpu: nova-core: add optional ucodes firmware loading John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps John Hubbard
` (18 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
GSP-RM on the new firmware logs from six LIBOS3 tasks on GA102 and
later, where r570 logs from three, and it maps a small buffer during
init to report RM state for diagnostics. Turing and GA100 run LIBOS2,
where only the init and RM task logs exist. Two of the six task logs are
a single page, where the rest are 64KB.
Allocate the three missing log buffers and the state monitor buffer,
give LogBuffer const parameters for its size and page count so the
single-page buffers can exist, and expose the new log buffers through
the debugfs directory that already carries the others.
Nothing passes these to GSP-RM yet. The libos init arguments gain them
when the driver switches to the new firmware.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 71 ++++++++++++++++++++++++++++--------
1 file changed, 55 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index f9a622edf299..f19a47cf396f 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -118,33 +118,63 @@ fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
/// then pp points to index into the buffer where the next logging entry will
/// be written. Therefore, the logging data is valid if:
/// 1 <= pp < sizeof(buffer)/sizeof(u64)
-struct LogBuffer(Coherent<[u8; LOG_BUFFER_SIZE]>);
+///
+/// `SIZE` is the buffer size in bytes and `NUM_PAGES` is the same size in GSP pages, checked
+/// against each other at build time. Computing one from the other in a type position is not
+/// stable Rust, so both are parameters.
+struct LogBuffer<const SIZE: usize, const NUM_PAGES: usize>(Coherent<[u8; SIZE]>);
+
+/// Log buffer for a task that GSP-RM logs to at its default size.
+///
+/// Matches the registry defaults for the init, interrupt, RM, and MNOC tasks
+/// (`NV_REG_STR_RM_GSP_LOG_BUFFER_SIZE_TASK_*_DEFAULT`).
+type TaskLogBuffer = LogBuffer<LOG_BUFFER_SIZE, RM_LOG_BUFFER_NUM_PAGES>;
+
+/// Log buffer for a task that GSP-RM gives a single page.
+///
+/// Matches the size GSP-RM hardcodes for the root and RM state monitor tasks.
+type SmallLogBuffer = LogBuffer<GSP_PAGE_SIZE, 1>;
-impl LogBuffer {
+impl<const SIZE: usize, const NUM_PAGES: usize> LogBuffer<SIZE, NUM_PAGES> {
/// Creates a new `LogBuffer` mapped on `dev`.
fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
+ build_assert!(SIZE == NUM_PAGES * GSP_PAGE_SIZE);
+
let obj = Self(Coherent::zeroed(dev, GFP_KERNEL)?);
let start_addr = obj.0.dma_address();
let pte_view = io_project!(
obj.0,
- [build: size_of::<u64>()..][build: ..RM_LOG_BUFFER_NUM_PAGES * size_of::<u64>()]
+ [build: size_of::<u64>()..][build: ..NUM_PAGES * size_of::<u64>()]
)
- .try_cast::<PteArray<RM_LOG_BUFFER_NUM_PAGES>>()?;
+ .try_cast::<PteArray<NUM_PAGES>>()?;
PteArray::init(pte_view, start_addr)?;
Ok(obj)
}
}
+/// Log buffers used by GSP-RM for debug logging.
+///
+/// r000+ firmware expects log buffers for all LIBOS3 tasks. Each buffer is
+/// registered as a libos memory region entry, identified by its id8 name.
+///
+/// The Open RM equivalents are `_kgspInitLibosLoggingStructures`, which allocates the buffers,
+/// and `kgspSetupLibosInitArgs_IMPL`, which builds the `pLibosInitArgs[]` array.
struct LogBuffers {
- /// Init log buffer.
- loginit: LogBuffer,
- /// Interrupts log buffer.
- logintr: LogBuffer,
- /// RM log buffer.
- logrm: LogBuffer,
+ /// Init task log buffer (LOGINIT).
+ loginit: TaskLogBuffer,
+ /// Interrupt task log buffer (LOGINTR).
+ logintr: TaskLogBuffer,
+ /// RM task log buffer (LOGRM).
+ logrm: TaskLogBuffer,
+ /// MNOC task log buffer (LOGMNOC).
+ logmnoc: TaskLogBuffer,
+ /// Root task log buffer (LOGROOT).
+ logroot: SmallLogBuffer,
+ /// RM state monitor task log buffer (LOGRMON).
+ logrmon: SmallLogBuffer,
}
/// GSP runtime data.
@@ -159,6 +189,8 @@ pub(crate) struct Gsp {
pub(crate) cmdq: Arc<Cmdq>,
/// RM arguments.
rmargs: Coherent<GspArgumentsPadded>,
+ /// RM state monitor buffer (required by r000+ GSP-RM for diagnostics).
+ rm_state_monitor: Coherent<[u8; GSP_PAGE_SIZE]>,
}
impl Gsp {
@@ -167,16 +199,17 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
pin_init::pin_init_scope(move || {
let dev = pdev.as_ref();
- let loginit = LogBuffer::new(dev)?;
- let logintr = LogBuffer::new(dev)?;
- let logrm = LogBuffer::new(dev)?;
+ let loginit = TaskLogBuffer::new(dev)?;
+ let logintr = TaskLogBuffer::new(dev)?;
+ let logrm = TaskLogBuffer::new(dev)?;
+ let logmnoc = TaskLogBuffer::new(dev)?;
+ let logroot = SmallLogBuffer::new(dev)?;
+ let logrmon = SmallLogBuffer::new(dev)?;
- // Initialise the logging structures. The OpenRM equivalents are in:
- // _kgspInitLibosLoggingStructures (allocates memory for buffers)
- // kgspSetupLibosInitArgs_IMPL (creates pLibosInitArgs[] array)
Ok(try_pin_init!(Self {
cmdq: Arc::pin_init(Cmdq::new(dev), GFP_KERNEL)?,
rmargs: Coherent::init(dev, GFP_KERNEL, GspArgumentsPadded::new(cmdq.as_ref()))?,
+ rm_state_monitor: Coherent::zeroed(dev, GFP_KERNEL)?,
libos: {
let mut libos = CoherentBox::zeroed_slice(
dev,
@@ -196,6 +229,9 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
loginit,
logintr,
logrm,
+ logmnoc,
+ logroot,
+ logrmon,
};
#[allow(static_mut_refs)]
@@ -212,6 +248,9 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
dir.read_binary_file(c"loginit", &logs.loginit.0);
dir.read_binary_file(c"logintr", &logs.logintr.0);
dir.read_binary_file(c"logrm", &logs.logrm.0);
+ dir.read_binary_file(c"logmnoc", &logs.logmnoc.0);
+ dir.read_binary_file(c"logroot", &logs.logroot.0);
+ dir.read_binary_file(c"logrmon", &logs.logrmon.0);
})
},
}))
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (7 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 08/27] gpu: nova-core: add LIBOS3 log buffers and state monitor buffer John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image John Hubbard
` (17 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
A raw log buffer dump cannot be decoded without knowing which firmware
build produced it, and which GPU and metadata format it belongs to.
GSP-RM's own decoder takes that from a header ahead of the data, in the
LIBOS_LOG_NVLOG_BUFFER_V2 layout.
Prepend that header to each debugfs dump. The build ID comes from the
BLID tag of gsp.tlv, so request the metadata once when the GSP manager
is created and hand it to the firmware loader rather than requesting it
again during boot. A buffer whose build ID is missing serves its
contents with no header.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/firmware.rs | 34 +++++
drivers/gpu/nova-core/firmware/gsp.rs | 8 +-
drivers/gpu/nova-core/gpu.rs | 2 +-
drivers/gpu/nova-core/gsp.rs | 185 ++++++++++++++++++++++----
drivers/gpu/nova-core/gsp/boot.rs | 2 +-
5 files changed, 200 insertions(+), 31 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index dfd41364cc77..e0befe84aa3e 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -31,6 +31,40 @@
pub(crate) mod riscv;
pub(crate) mod tlv;
+/// Maximum length of a build ID, matching Open RM's `BUILD_ID_MAX_LENGTH`.
+const BUILD_ID_MAX_LENGTH: usize = 32;
+
+/// Build ID extracted from firmware, used to correlate debugfs log buffer dumps
+/// with the correct firmware symbols.
+pub(crate) struct BuildId {
+ bytes: [u8; BUILD_ID_MAX_LENGTH],
+ len: u8,
+}
+
+impl BuildId {
+ /// Constructs a [`BuildId`] from raw descriptor bytes.
+ ///
+ /// Returns `None` if `data` is empty or exceeds [`BUILD_ID_MAX_LENGTH`].
+ pub(crate) fn from_raw(data: &[u8]) -> Option<Self> {
+ if data.is_empty() || data.len() > BUILD_ID_MAX_LENGTH {
+ return None;
+ }
+
+ let mut bytes = [0u8; BUILD_ID_MAX_LENGTH];
+ bytes[..data.len()].copy_from_slice(data);
+
+ Some(Self {
+ bytes,
+ len: data.len() as u8,
+ })
+ }
+
+ /// Returns the build ID bytes.
+ pub(crate) fn as_bytes(&self) -> &[u8] {
+ &self.bytes[..usize::from(self.len)]
+ }
+}
+
/// Structure used to describe some firmwares, notably FWSEC-FRTS.
#[repr(C)]
#[derive(Debug, Clone, FromBytes)]
diff --git a/drivers/gpu/nova-core/firmware/gsp.rs b/drivers/gpu/nova-core/firmware/gsp.rs
index 48c73a676ecb..832debb82767 100644
--- a/drivers/gpu/nova-core/firmware/gsp.rs
+++ b/drivers/gpu/nova-core/firmware/gsp.rs
@@ -16,8 +16,8 @@
radix3::Radix3,
riscv::RiscvFirmware, //
tlv::{
- request_tlv, //
- Tlv,
+ request_tlv,
+ Tlv, //
},
},
gpu::Chipset,
@@ -48,10 +48,10 @@ impl GspFirmware {
pub(crate) fn new<'a>(
dev: &'a device::Device<device::Bound>,
chipset: Chipset,
+ gsp_tlv: &'a firmware::Firmware,
) -> impl PinInit<Self, Error> + 'a {
pin_init::pin_init_scope(move || {
- let firmware = request_tlv(dev, chipset, "gsp")?;
- let tlv = Tlv::new(firmware.data())?;
+ let tlv = Tlv::new(gsp_tlv.data())?;
let fw_version = CString::try_from_fmt(fmt!("{}", tlv.get_string(b"VERS")?))?;
dev_dbg!(
dev,
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 11a66a597298..c0ba561a3bf1 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -396,7 +396,7 @@ pub(crate) fn new(
vgpu: VgpuManager::new(pdev, spec.chipset, fsp.as_mut()),
- gsp <- Gsp::new(pdev),
+ gsp <- Gsp::new(pdev, spec.chipset),
// This member must be initialized last, so the `UnloadBundle` can never be dropped
// from outside of the constructed `GspResources`, ensuring that the unload sequence
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index f19a47cf396f..232905638169 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -12,6 +12,7 @@
CoherentView,
DmaAddress, //
},
+ fs::file,
io::{
io_project,
io_write,
@@ -19,7 +20,8 @@
},
pci,
prelude::*,
- sync::Arc, //
+ sync::Arc,
+ uaccess::UserSliceWriter, //
};
pub(crate) mod cmdq;
@@ -45,6 +47,13 @@
sec2::Sec2 as Sec2Falcon,
Falcon, //
},
+ firmware::{
+ tlv::{
+ request_tlv,
+ Tlv, //
+ },
+ BuildId, //
+ },
fsp::Fsp,
gpu::Chipset,
gsp::{
@@ -104,6 +113,50 @@ fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
}
}
+/// Size of the header prepended to debugfs log buffer dumps.
+///
+/// This header makes each dump self-describing so that decoding tools can
+/// identify the firmware build, GPU architecture, and metadata format without
+/// out-of-band information.
+const LOG_BUFFER_HEADER_SIZE: usize = 0x48;
+
+/// Build a log buffer header from GPU and firmware metadata.
+///
+/// Layout (all little-endian):
+/// 0x00 gpuArch (u32)
+/// 0x04 gpuImpl (u32)
+/// 0x08 version (u32) = 2
+/// 0x0C buildIdLength (u32)
+/// 0x10 taskPrefix[8]
+/// 0x18 localToGlobalTimerDelta (u64) = 0
+/// 0x20 buildId[32]
+/// 0x40 flags (u32) = 1 (packed metadata)
+/// 0x44 reserved (u32) = 0
+fn build_log_buffer_header(
+ chipset: Chipset,
+ build_id: &BuildId,
+ task_prefix: &str,
+) -> [u8; LOG_BUFFER_HEADER_SIZE] {
+ let mut h = [0u8; LOG_BUFFER_HEADER_SIZE];
+ let chipset_val = chipset as u32;
+
+ h[0x00..0x04].copy_from_slice(&(chipset_val >> 4).to_le_bytes());
+ h[0x04..0x08].copy_from_slice(&(chipset_val & 0xF).to_le_bytes());
+ h[0x08..0x0C].copy_from_slice(&2u32.to_le_bytes());
+
+ let bid = build_id.as_bytes();
+ h[0x0C..0x10].copy_from_slice(&(bid.len() as u32).to_le_bytes());
+
+ let prefix = task_prefix.as_bytes();
+ let prefix_len = prefix.len().min(8);
+ h[0x10..0x10 + prefix_len].copy_from_slice(&prefix[..prefix_len]);
+
+ h[0x20..0x20 + bid.len()].copy_from_slice(bid);
+ h[0x40..0x44].copy_from_slice(&1u32.to_le_bytes());
+
+ h
+}
+
/// 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.
@@ -122,7 +175,13 @@ fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
/// `SIZE` is the buffer size in bytes and `NUM_PAGES` is the same size in GSP pages, checked
/// against each other at build time. Computing one from the other in a type position is not
/// stable Rust, so both are parameters.
-struct LogBuffer<const SIZE: usize, const NUM_PAGES: usize>(Coherent<[u8; SIZE]>);
+///
+/// When a build ID is available, the debugfs file for this buffer prepends
+/// a header so the dump is self-describing.
+struct LogBuffer<const SIZE: usize, const NUM_PAGES: usize> {
+ header: Option<[u8; LOG_BUFFER_HEADER_SIZE]>,
+ buffer: Coherent<[u8; SIZE]>,
+}
/// Log buffer for a task that GSP-RM logs to at its default size.
///
@@ -137,21 +196,72 @@ fn init(view: CoherentView<'_, Self>, start: DmaAddress) -> Result<()> {
impl<const SIZE: usize, const NUM_PAGES: usize> LogBuffer<SIZE, NUM_PAGES> {
/// Creates a new `LogBuffer` mapped on `dev`.
- fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
+ fn new(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ build_id: Option<&BuildId>,
+ task_prefix: &str,
+ ) -> Result<Self> {
build_assert!(SIZE == NUM_PAGES * GSP_PAGE_SIZE);
- let obj = Self(Coherent::zeroed(dev, GFP_KERNEL)?);
-
- let start_addr = obj.0.dma_address();
+ let buffer = Coherent::zeroed(dev, GFP_KERNEL)?;
+ let start_addr = buffer.dma_address();
let pte_view = io_project!(
- obj.0,
+ buffer,
[build: size_of::<u64>()..][build: ..NUM_PAGES * size_of::<u64>()]
)
.try_cast::<PteArray<NUM_PAGES>>()?;
PteArray::init(pte_view, start_addr)?;
- Ok(obj)
+ let header = build_id.map(|bid| build_log_buffer_header(chipset, bid, task_prefix));
+
+ Ok(Self { header, buffer })
+ }
+}
+
+impl<const SIZE: usize, const NUM_PAGES: usize> debugfs::BinaryWriter
+ for LogBuffer<SIZE, NUM_PAGES>
+{
+ fn write_to_slice(
+ &self,
+ writer: &mut UserSliceWriter,
+ offset: &mut file::Offset,
+ ) -> Result<usize> {
+ if offset.is_negative() {
+ return Err(EINVAL);
+ }
+
+ let offset_val: usize = (*offset).try_into().map_err(|_| EINVAL)?;
+ let header = self.header.as_ref().map_or(&[][..], |h| h.as_slice());
+ let total_len = header.len() + self.buffer.size();
+
+ if offset_val >= total_len {
+ return Ok(0);
+ }
+
+ let count = (total_len - offset_val).min(writer.len());
+ if count == 0 {
+ return Ok(0);
+ }
+
+ let mut written = 0;
+
+ if offset_val < header.len() {
+ let hdr_count = (header.len() - offset_val).min(count);
+ writer.write_slice(&header[offset_val..offset_val + hdr_count])?;
+ written += hdr_count;
+ }
+
+ if written < count {
+ let buf_start = offset_val.saturating_sub(header.len());
+ let buf_count = count - written;
+ writer.write_dma(&self.buffer, buf_start, buf_count)?;
+ written += buf_count;
+ }
+
+ *offset += written as i64;
+ Ok(written)
}
}
@@ -180,9 +290,11 @@ struct LogBuffers {
/// GSP runtime data.
#[pin_data]
pub(crate) struct Gsp {
+ /// Preloaded GSP firmware TLV metadata used during boot.
+ gsp_tlv: kernel::firmware::Firmware,
/// Libos arguments.
pub(crate) libos: Coherent<[LibosMemoryRegionInitArgument]>,
- /// Log buffers, optionally exposed via debugfs.
+ /// Log buffers for all LIBOS3 tasks, exposed via debugfs.
#[pin]
logs: debugfs::Scope<LogBuffers>,
/// Command queue, shared with the GSP event interrupt handler.
@@ -195,18 +307,32 @@ pub(crate) struct Gsp {
impl Gsp {
// Creates an in-place initializer for a `Gsp` manager for `pdev`.
- pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error> + '_ {
+ pub(crate) fn new(
+ pdev: &pci::Device<device::Bound>,
+ chipset: Chipset,
+ ) -> impl PinInit<Self, Error> + '_ {
pin_init::pin_init_scope(move || {
let dev = pdev.as_ref();
- let loginit = TaskLogBuffer::new(dev)?;
- let logintr = TaskLogBuffer::new(dev)?;
- let logrm = TaskLogBuffer::new(dev)?;
- let logmnoc = TaskLogBuffer::new(dev)?;
- let logroot = SmallLogBuffer::new(dev)?;
- let logrmon = SmallLogBuffer::new(dev)?;
+ let gsp_tlv = request_tlv(dev, chipset, "gsp")?;
+ let tlv = Tlv::new(gsp_tlv.data())?;
+ let build_id = tlv.get_bytes(b"BLID").ok().and_then(BuildId::from_raw);
+ if build_id.is_none() {
+ dev_warn!(
+ pdev,
+ "GSP firmware build ID not found, log buffer headers omitted\n"
+ );
+ }
+
+ let loginit = TaskLogBuffer::new(dev, chipset, build_id.as_ref(), "INIT")?;
+ let logintr = TaskLogBuffer::new(dev, chipset, build_id.as_ref(), "INTR")?;
+ let logrm = TaskLogBuffer::new(dev, chipset, build_id.as_ref(), "RM")?;
+ let logmnoc = TaskLogBuffer::new(dev, chipset, build_id.as_ref(), "MNOC")?;
+ let logroot = SmallLogBuffer::new(dev, chipset, build_id.as_ref(), "ROOT")?;
+ let logrmon = SmallLogBuffer::new(dev, chipset, build_id.as_ref(), "RMON")?;
Ok(try_pin_init!(Self {
+ gsp_tlv,
cmdq: Arc::pin_init(Cmdq::new(dev), GFP_KERNEL)?,
rmargs: Coherent::init(dev, GFP_KERNEL, GspArgumentsPadded::new(cmdq.as_ref()))?,
rm_state_monitor: Coherent::zeroed(dev, GFP_KERNEL)?,
@@ -217,9 +343,18 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
GFP_KERNEL,
)?;
- libos.init_at(0, LibosMemoryRegionInitArgument::new("LOGINIT", &loginit.0))?;
- libos.init_at(1, LibosMemoryRegionInitArgument::new("LOGINTR", &logintr.0))?;
- libos.init_at(2, LibosMemoryRegionInitArgument::new("LOGRM", &logrm.0))?;
+ libos.init_at(
+ 0,
+ LibosMemoryRegionInitArgument::new("LOGINIT", &loginit.buffer),
+ )?;
+ libos.init_at(
+ 1,
+ LibosMemoryRegionInitArgument::new("LOGINTR", &logintr.buffer),
+ )?;
+ libos.init_at(
+ 2,
+ LibosMemoryRegionInitArgument::new("LOGRM", &logrm.buffer),
+ )?;
libos.init_at(3, LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
libos.into()
@@ -245,12 +380,12 @@ pub(crate) fn new(pdev: &pci::Device<device::Bound>) -> impl PinInit<Self, Error
.expect("DEBUGFS_ROOT not initialized");
log_parent.scope(log_buffers, dev.name(), |logs, dir| {
- dir.read_binary_file(c"loginit", &logs.loginit.0);
- dir.read_binary_file(c"logintr", &logs.logintr.0);
- dir.read_binary_file(c"logrm", &logs.logrm.0);
- dir.read_binary_file(c"logmnoc", &logs.logmnoc.0);
- dir.read_binary_file(c"logroot", &logs.logroot.0);
- dir.read_binary_file(c"logrmon", &logs.logrmon.0);
+ dir.read_binary_file(c"loginit", &logs.loginit);
+ dir.read_binary_file(c"logintr", &logs.logintr);
+ dir.read_binary_file(c"logrm", &logs.logrm);
+ dir.read_binary_file(c"logmnoc", &logs.logmnoc);
+ dir.read_binary_file(c"logroot", &logs.logroot);
+ dir.read_binary_file(c"logrmon", &logs.logrmon);
})
},
}))
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index e03700ee7bea..a64313dca0a1 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -42,7 +42,7 @@ pub(crate) fn boot(
let dev = pdev.as_ref();
let hal = super::hal::gsp_hal(chipset);
- let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset), GFP_KERNEL)?;
+ let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, &self.gsp_tlv), GFP_KERNEL)?;
// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_bundle = hal.boot(&self, &mut ctx, &gsp_fw)?.or_else(|| {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (8 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 09/27] gpu: nova-core: add build ID headers to debugfs log buffer dumps John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
` (16 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The framebuffer region this field describes holds the GSP firmware
image. It is an ELF under 570.144 and is not one under the firmware this
series moves to, so name the field for what it holds rather than for its
current format.
No functional change.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/fb.rs | 18 +++++++++---------
drivers/gpu/nova-core/gsp/fw.rs | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index f208d6deb5e3..6952f8a6bf9b 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -159,7 +159,7 @@ pub(crate) struct FbRanges {
/// Memory area containing the GSP bootloader image.
pub(crate) boot: FbRange,
/// Memory area containing the GSP firmware image.
- pub(crate) elf: FbRange,
+ pub(crate) fw_image: FbRange,
/// WPR2 heap.
pub(crate) wpr2_heap: FbRange,
/// WPR2 region range, starting with an instance of `GspFwWprMeta`.
@@ -234,25 +234,25 @@ pub(crate) fn new(
FbRange(bootloader_base..bootloader_base + bootloader_size)
};
- let elf = {
- const ELF_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
- let elf_size = u64::from_safe_cast(gsp_fw.size());
- let elf_addr = (boot.start - elf_size).align_down(ELF_DOWN_ALIGN);
+ let fw_image = {
+ const FW_IMAGE_DOWN_ALIGN: Alignment = Alignment::new::<SZ_64K>();
+ let fw_image_size = u64::from_safe_cast(gsp_fw.size());
+ let fw_image_addr = (boot.start - fw_image_size).align_down(FW_IMAGE_DOWN_ALIGN);
- FbRange(elf_addr..elf_addr + elf_size)
+ 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 wpr2_heap = {
const WPR2_HEAP_DOWN_ALIGN: Alignment = Alignment::new::<SZ_1M>();
- let wpr2_heap_addr = elf
+ let wpr2_heap_addr = fw_image
.start
.checked_sub(wpr2_heap_size)
.ok_or(EOVERFLOW)?
.align_down(WPR2_HEAP_DOWN_ALIGN);
- FbRange(wpr2_heap_addr..(elf.start).align_down(WPR2_HEAP_DOWN_ALIGN))
+ FbRange(wpr2_heap_addr..(fw_image.start).align_down(WPR2_HEAP_DOWN_ALIGN))
};
let wpr2 = {
@@ -273,7 +273,7 @@ pub(crate) fn new(
vga_workspace,
frts,
boot,
- elf,
+ fw_image,
wpr2_heap,
wpr2,
non_wpr_heap,
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 857d53c77297..d10677e277c5 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -206,7 +206,7 @@ pub(crate) fn from_ranges<'a>(
gspFwWprStart: ranges.wpr2.start,
gspFwHeapOffset: ranges.wpr2_heap.start,
gspFwHeapSize: ranges.wpr2_heap.len(),
- gspFwOffset: ranges.elf.start,
+ gspFwOffset: ranges.fw_image.start,
bootBinOffset: ranges.boot.start,
frtsOffset: ranges.frts.start,
frtsSize: ranges.frts.len(),
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (9 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 10/27] gpu: nova-core: rename the FbRanges elf field to fw_image John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals John Hubbard
` (15 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
Msgq v2 moves the four ring pointers out of the in-memory headers and
into BAR0. NV_PGSP_QUEUE_HEAD already exists as the v0 doorbell, so add
NV_PGSP_QUEUE_TAIL, NV_PGSP_MSGQ_HEAD, and NV_PGSP_MSGQ_TAIL at the
queue-0 offsets.
Nova only uses queue 0, so the registers are scalars rather than
8-element arrays. A comment records the full hardware layout for future
multi-queue work.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/regs.rs | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/regs.rs b/drivers/gpu/nova-core/gsp/regs.rs
index 9a48aa87e7fb..d2e40ed13123 100644
--- a/drivers/gpu/nova-core/gsp/regs.rs
+++ b/drivers/gpu/nova-core/gsp/regs.rs
@@ -5,6 +5,18 @@
use crate::regs::NV_PBUS_SW_SCRATCH;
// PGSP
+//
+// The msgq v2 wire protocol replaces the in-memory ring pointers with four
+// BAR0 registers per queue:
+//
+// NV_PGSP_QUEUE_HEAD[i] @ 0x00110c00 + i*8 (CPU TX write, doorbell)
+// NV_PGSP_QUEUE_TAIL[i] @ 0x00110c04 + i*8 (GSP TX read)
+// NV_PGSP_MSGQ_HEAD[i] @ 0x00110c80 + i*8 (GSP RX write)
+// NV_PGSP_MSGQ_TAIL[i] @ 0x00110c84 + i*8 (CPU RX read)
+//
+// Nova only uses queue 0, so the four registers are declared as single
+// scalars at the i=0 offsets. NV_PGSP_QUEUE_HEAD is also used as the v0
+// doorbell, which is why it predates the others.
register! {
pub(super) NV_PGSP_QUEUE_HEAD(u32) @ 0x00110c00 {
@@ -12,6 +24,24 @@
}
}
+register! {
+ pub(super) NV_PGSP_QUEUE_TAIL(u32) @ 0x00110c04 {
+ 31:0 address;
+ }
+}
+
+register! {
+ pub(super) NV_PGSP_MSGQ_HEAD(u32) @ 0x00110c80 {
+ 31:0 address;
+ }
+}
+
+register! {
+ pub(super) NV_PGSP_MSGQ_TAIL(u32) @ 0x00110c84 {
+ 31:0 address;
+ }
+}
+
// PBUS
register! {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (10 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 11/27] gpu: nova-core: regs: add msgq v2 BAR0 register declarations John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers John Hubbard
` (14 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
Msgq v2 moves the four ring pointers out of shared memory into BAR0
registers and treats them as monotonic counters, so head == tail
distinguishes empty from full and the ring uses every slot. The pointers
can also be out of step, because a GSP reset zeroes them while the ring
keeps its contents.
Add the v2 TX header and the v2 ring helpers in parallel to the v0 ones,
so the flip commit can swap call sites without writing new logic. Read
the queue as empty while the read pointer is ahead of the write pointer,
rather than taking the difference as a page count.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 140 ++++++++++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 36 ++++++++
2 files changed, 176 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d3afe3f9bb37..1d86d2927a94 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -467,6 +467,146 @@ fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
}
}
+// Msgq v2 internals.
+//
+// Msgq v2 keeps the four ring pointers in BAR0 registers as monotonic `u32`
+// counters that wrap on overflow. The slot index is `ptr % MSGQ_NUM_PAGES`
+// only at the point of use, and the ring uses every slot (no "leave one
+// empty" rule, since `head == tail` distinguishes empty from full).
+//
+// Register-to-role mapping for queue 0:
+//
+// CPU TX write/doorbell: NV_PGSP_QUEUE_HEAD
+// GSP TX read: NV_PGSP_QUEUE_TAIL
+// GSP RX write: NV_PGSP_MSGQ_HEAD
+// CPU RX read: NV_PGSP_MSGQ_TAIL
+//
+// A GSP reset zeroes all four counters while the in-memory ring keeps its
+// contents, so the two ends can be out of step. Between the reset and
+// GSP-RM writing its counter back the read pointer is ahead of the write
+// pointer, which `driver_read_area_v2` reports as an empty ring.
+//
+// TODO: suspend/resume reset-recovery is not implemented. A power cycle
+// loses the driver-side counters as well, so both ends have to be
+// re-established.
+#[expect(dead_code)]
+impl DmaGspMem {
+ fn gsp_write_ptr_v2(bar: Bar0<'_>) -> u32 {
+ *bar.read(regs::NV_PGSP_MSGQ_HEAD).address()
+ }
+
+ fn gsp_read_ptr_v2(bar: Bar0<'_>) -> u32 {
+ *bar.read(regs::NV_PGSP_QUEUE_TAIL).address()
+ }
+
+ fn cpu_read_ptr_v2(bar: Bar0<'_>) -> u32 {
+ *bar.read(regs::NV_PGSP_MSGQ_TAIL).address()
+ }
+
+ fn cpu_write_ptr_v2(bar: Bar0<'_>) -> u32 {
+ *bar.read(regs::NV_PGSP_QUEUE_HEAD).address()
+ }
+
+ fn advance_cpu_read_ptr_v2(bar: Bar0<'_>, count: u32) {
+ let new_rptr = Self::cpu_read_ptr_v2(bar).wrapping_add(count);
+
+ // Order all reads from the message data ahead of the read-pointer
+ // update so the GSP cannot recycle the slots while we are still
+ // looking at them.
+ fence(Ordering::SeqCst);
+
+ bar.write_reg(regs::NV_PGSP_MSGQ_TAIL::zeroed().with_address(new_rptr));
+ }
+
+ fn advance_cpu_write_ptr_v2(bar: Bar0<'_>, count: u32) {
+ let new_wptr = Self::cpu_write_ptr_v2(bar).wrapping_add(count);
+
+ // Order all writes to the message data ahead of the write-pointer
+ // update. Writing the head register doubles as the GSP doorbell.
+ fence(Ordering::SeqCst);
+
+ bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(new_wptr));
+ }
+
+ /// Returns the region of the CPU message queue that the driver is currently allowed to write
+ /// to.
+ ///
+ /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
+ /// that case the second slice will have a non-zero length.
+ fn driver_write_area_v2(
+ &mut self,
+ bar: Bar0<'_>,
+ ) -> (&mut [[u8; GSP_PAGE_SIZE]], &mut [[u8; GSP_PAGE_SIZE]]) {
+ let raw_w = Self::cpu_write_ptr_v2(bar);
+ let raw_r = Self::gsp_read_ptr_v2(bar);
+
+ let used = raw_w.wrapping_sub(raw_r);
+ let avail = num::u32_as_usize(MSGQ_NUM_PAGES.saturating_sub(used));
+ let w_slot = num::u32_as_usize(raw_w % MSGQ_NUM_PAGES);
+
+ // Pointer to the first entry of the CPU message queue.
+ let data = ptr::project!(mut self.0.as_mut_ptr(), .cpuq.msgq.data[build: 0]);
+
+ // SAFETY:
+ // - `data` points to `MSGQ_NUM_PAGES` valid message queue entries.
+ // - We will only access the driver-owned part of the shared memory.
+ // - Per the safety statement of the function, no concurrent access will be performed.
+ let data =
+ unsafe { core::slice::from_raw_parts_mut(data, num::u32_as_usize(MSGQ_NUM_PAGES)) };
+ let (before_w, after_w) = data.split_at_mut(w_slot);
+
+ let in_after = avail.min(after_w.len());
+ let in_before = avail - in_after;
+ (&mut after_w[..in_after], &mut before_w[..in_before])
+ }
+
+ /// Returns the size, in bytes, of the region of the CPU message queue that the driver is
+ /// currently allowed to write to.
+ fn driver_write_area_size_v2(bar: Bar0<'_>) -> usize {
+ let used = Self::cpu_write_ptr_v2(bar).wrapping_sub(Self::gsp_read_ptr_v2(bar));
+ let slots = MSGQ_NUM_PAGES.saturating_sub(used);
+ num::u32_as_usize(slots) * GSP_PAGE_SIZE
+ }
+
+ /// Returns the region of the GSP message queue that the driver is currently allowed to read
+ /// from.
+ ///
+ /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
+ /// that case the second slice will have a non-zero length.
+ fn driver_read_area_v2(
+ &self,
+ bar: Bar0<'_>,
+ ) -> (&[[u8; GSP_PAGE_SIZE]], &[[u8; GSP_PAGE_SIZE]]) {
+ let raw_w = Self::gsp_write_ptr_v2(bar);
+ let raw_r = Self::cpu_read_ptr_v2(bar);
+
+ // A difference wider than the ring means the GSP has been reset and has not yet written
+ // its own counter back, so the read pointer is momentarily ahead of it. Report nothing
+ // readable, which holds the callers in their poll until GSP-RM restores the real value.
+ let pending = raw_w.wrapping_sub(raw_r);
+ let avail = if pending > MSGQ_NUM_PAGES {
+ 0
+ } else {
+ num::u32_as_usize(pending)
+ };
+ let r_slot = num::u32_as_usize(raw_r % MSGQ_NUM_PAGES);
+
+ // Pointer to the first entry of the GSP message queue.
+ let data = ptr::project!(self.0.as_ptr(), .gspq.msgq.data[build: 0]);
+
+ // SAFETY:
+ // - `data` points to `MSGQ_NUM_PAGES` valid message queue entries.
+ // - We will only access the driver-owned part of the shared memory.
+ // - Per the safety statement of the function, no concurrent access will be performed.
+ let data = unsafe { core::slice::from_raw_parts(data, num::u32_as_usize(MSGQ_NUM_PAGES)) };
+ let (before_r, after_r) = data.split_at(r_slot);
+
+ let in_after = avail.min(after_r.len());
+ let in_before = avail - in_after;
+ (&after_r[..in_after], &before_r[..in_before])
+ }
+}
+
/// A command ready to be sent on the command queue.
///
/// This is the type returned by [`DmaGspMem::allocate_command`].
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index d10677e277c5..79a7820ce99f 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -758,6 +758,42 @@ pub(crate) fn set_write_ptr(this: CoherentView<'_, Self>, val: u32) {
// SAFETY: Padding is explicit and does not contain uninitialized data.
unsafe impl AsBytes for MsgqTxHeader {}
+/// TX header for setting up a message queue with the GSP, msgq v2 layout.
+///
+/// Same wire size as [`MsgqTxHeader`] (32 bytes) with a different field
+/// layout: the v0 `writePtr`, `flags`, and `rxHdrOff` fields are gone, the
+/// `version` `u32` becomes split `versionMajor`/`versionMinor` `u16`s, and
+/// the trailing space holds three reserved `u32`s.
+#[repr(transparent)]
+pub(crate) struct MsgqTxHeaderV2(r000_00::msgqTxHeader);
+
+#[expect(dead_code)]
+impl MsgqTxHeaderV2 {
+ /// Creates a new v2 TX queue header.
+ ///
+ /// # Arguments
+ ///
+ /// * `msgq_size` - Total size of the message queue structure, in bytes.
+ /// * `msg_size` - Size of each message slot, in bytes.
+ /// * `msg_count` - Number of message slots in the ring.
+ /// * `entry_off` - Byte offset from the start of the queue at which the
+ /// message data array begins.
+ pub(crate) fn new(msgq_size: u32, msg_size: u32, msg_count: u32, entry_off: u32) -> Self {
+ Self(r000_00::msgqTxHeader {
+ versionMajor: 2,
+ versionMinor: 0,
+ size: msgq_size,
+ msgSize: msg_size,
+ msgCount: msg_count,
+ entryOff: entry_off,
+ reserved: [0; 3],
+ })
+ }
+}
+
+// SAFETY: Padding is explicit and does not contain uninitialized data.
+unsafe impl AsBytes for MsgqTxHeaderV2 {}
+
/// RX header for setting up a message queue with the GSP.
#[repr(transparent)]
pub(crate) struct MsgqRxHeader(bindings::msgqRxHeader);
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (11 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 12/27] gpu: nova-core: gsp: add msgq v2 internals John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
` (13 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The GMC command path uses a different message element header from the
VGPU-style RPC path, but the two share the queue, the space accounting,
and the wrapping rules. Make allocate_command() and GspCommand generic
over the header type, defaulting to GspMsgElement so the existing RPC
callers are unchanged.
No functional changes.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 1d86d2927a94..0dab3d279426 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -365,6 +365,8 @@ fn driver_write_area_size(&self) -> usize {
/// Allocates a region on the command queue that is large enough to send a command of `size`
/// bytes, waiting for space to become available based on the provided timeout.
///
+ /// The type parameter `H` selects the message element header, [`GspMsgElement`] for RM RPC.
+ ///
/// This returns a [`GspCommand`] ready to be written to by the caller.
///
/// # Errors
@@ -372,13 +374,17 @@ fn driver_write_area_size(&self) -> usize {
/// - `EMSGSIZE` if the command is larger than [`GSP_MSG_QUEUE_ELEMENT_SIZE_MAX`].
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the command header is not properly aligned.
- fn allocate_command(&mut self, size: usize, timeout: Delta) -> Result<GspCommand<'_>> {
- if size_of::<GspMsgElement>() + size > GSP_MSG_QUEUE_ELEMENT_SIZE_MAX {
+ fn allocate_command<H: FromBytes + AsBytes>(
+ &mut self,
+ size: usize,
+ timeout: Delta,
+ ) -> Result<GspCommand<'_, H>> {
+ if size_of::<H>() + size > GSP_MSG_QUEUE_ELEMENT_SIZE_MAX {
return Err(EMSGSIZE);
}
read_poll_timeout(
|| Ok(self.driver_write_area_size()),
- |available_bytes| *available_bytes >= size_of::<GspMsgElement>() + size,
+ |available_bytes| *available_bytes >= size_of::<H>() + size,
Delta::from_micros(1),
timeout,
)?;
@@ -390,8 +396,8 @@ fn allocate_command(&mut self, size: usize, timeout: Delta) -> Result<GspCommand
(slice_1.as_flattened_mut(), slice_2.as_flattened_mut())
};
- // Extract area for the `GspMsgElement`.
- let (header, slice_1) = GspMsgElement::from_bytes_mut_prefix(slice_1).ok_or(EIO)?;
+ // Extract area for the message element header.
+ let (header, slice_1) = H::from_bytes_mut_prefix(slice_1).ok_or(EIO)?;
// Create the contents area.
let (slice_1, slice_2) = if slice_1.len() > size {
@@ -609,10 +615,12 @@ fn driver_read_area_v2(
/// A command ready to be sent on the command queue.
///
+/// The type parameter `H` is the message element header type, [`GspMsgElement`] for RM RPC.
+///
/// This is the type returned by [`DmaGspMem::allocate_command`].
-struct GspCommand<'a> {
+struct GspCommand<'a, H = GspMsgElement> {
// Writable reference to the header of the command.
- header: &'a mut GspMsgElement,
+ header: &'a mut H,
// Writable slices to the contents of the command. The second slice is zero unless the command
// loops over the command queue.
contents: (&'a mut [u8], &'a mut [u8]),
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 14/27] gpu: nova-core: add GMC API message types
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (12 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 13/27] gpu: nova-core: generalize allocate_command() for variable headers John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
` (12 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The new firmware carries GMC commands over the same MCTP/NVDM transport
as the RPC path, under NVDM type 0x26 and with its own fixed header in
place of rpc_message_header_v. Add that header as GmcApiHeader, matching
Open RM's GMCAPI_HEADER, and the queue element that wraps it.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/fw.rs | 127 ++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/mctp.rs | 2 +
2 files changed, 129 insertions(+)
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 79a7820ce99f..050a6af8c0c7 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -50,6 +50,11 @@
cmdq::Cmdq, //
GSP_PAGE_SIZE,
},
+ mctp::{
+ MctpHeader,
+ NvdmHeader,
+ NvdmType, //
+ },
num::{
self,
FromSafeCast, //
@@ -947,6 +952,128 @@ unsafe impl AsBytes for GspMsgElement {}
// are valid.
unsafe impl FromBytes for GspMsgElement {}
+/// Magic value that opens every MCTP-framed queue element: `"MCTP"` in ASCII.
+const MCTP_MAGIC: u32 = 0x4D43_5450;
+
+/// GMC API message header.
+///
+/// Matches the `GMCAPI_HEADER` struct from Open RM. The `command` field carries
+/// flags in the high byte and a 24-bit command ID in the low bits. `size` is the
+/// payload size for both requests and responses, while the union at offset 16
+/// carries the maximum response size for requests and the status for responses.
+#[repr(C)]
+#[derive(Zeroable)]
+pub(crate) struct GmcApiHeader {
+ /// GMC command identifier with flags in the high byte.
+ pub(crate) command: u32,
+ /// Payload size in bytes.
+ pub(crate) size: u32,
+ /// Sequence number for matching requests to responses.
+ pub(crate) sequence: u64,
+ /// Request: maximum response size. Response: `NV_STATUS` code.
+ pub(crate) max_resp_or_status: u32,
+ reserved: [u32; 5],
+}
+
+static_assert!(size_of::<GmcApiHeader>() == 40);
+
+// SAFETY: All fields are integer types with no uninitialized padding bytes.
+unsafe impl AsBytes for GmcApiHeader {}
+
+// SAFETY: All fields are integer types for which all bit patterns are valid.
+unsafe impl FromBytes for GmcApiHeader {}
+
+/// Header for an unencrypted GMC API message in the GSP command queue.
+///
+/// The fields before `gmc` match `GSP_MSG_QUEUE_ELEMENT` through its
+/// `noEncryption` union member. `gmc` occupies the start of that member's
+/// flexible payload array, and the GMC command payload follows this structure.
+#[repr(C)]
+pub(crate) struct GspGmcMsgElement {
+ mctp_magic: u32,
+ mctp_payload_size: u32,
+ mctp_header: MctpHeader,
+ nvdm_header: NvdmHeader,
+ nvdm_payload_size: u32,
+ reserved: u32,
+ pub(crate) gmc: GmcApiHeader,
+}
+
+static_assert!(
+ core::mem::offset_of!(GspGmcMsgElement, mctp_magic)
+ == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpMagic)
+);
+static_assert!(
+ core::mem::offset_of!(GspGmcMsgElement, mctp_payload_size)
+ == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpPayloadSize)
+);
+static_assert!(
+ core::mem::offset_of!(GspGmcMsgElement, mctp_header)
+ == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpHeader)
+);
+static_assert!(
+ core::mem::offset_of!(GspGmcMsgElement, nvdm_header)
+ == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, nvdmHeader)
+);
+
+#[expect(dead_code)]
+impl GspGmcMsgElement {
+ /// Creates a new GMC message element.
+ ///
+ /// `payload_size` is the size of the command payload following this header.
+ /// `command_id` is the GMC command identifier.
+ /// `sequence` is the sequence number for request/response matching.
+ /// `max_response_size` is the maximum expected response payload size.
+ pub(crate) fn init(
+ command_id: u32,
+ sequence: u64,
+ payload_size: usize,
+ max_response_size: u32,
+ ) -> impl Init<Self, Error> {
+ try_init!(GspGmcMsgElement {
+ mctp_magic: MCTP_MAGIC,
+ // Despite the name, this counts the element header as well as the payload.
+ mctp_payload_size: size_of::<Self>()
+ .checked_add(payload_size)
+ .ok_or(EOVERFLOW)?
+ .try_into()
+ .map_err(|_| EOVERFLOW)?,
+ mctp_header: MctpHeader::single_packet(),
+ nvdm_header: NvdmHeader::new(NvdmType::GmcApi),
+ nvdm_payload_size: size_of::<GmcApiHeader>()
+ .checked_add(payload_size)
+ .ok_or(EOVERFLOW)?
+ .try_into()
+ .map_err(|_| EOVERFLOW)?,
+ reserved: 0u32,
+ gmc: GmcApiHeader {
+ command: command_id,
+ size: payload_size.try_into().map_err(|_| EOVERFLOW)?,
+ sequence,
+ max_resp_or_status: max_response_size,
+ reserved: [0; 5],
+ },
+ })
+ }
+
+ /// Returns the total length of the message, transport and GMC headers included.
+ pub(crate) fn length(&self) -> usize {
+ num::u32_as_usize(self.mctp_payload_size)
+ }
+
+ /// Returns the number of elements (i.e. memory pages) used by this message.
+ pub(crate) fn element_count(&self) -> u32 {
+ self.mctp_payload_size
+ .div_ceil(num::usize_into_u32::<GSP_PAGE_SIZE>())
+ }
+}
+
+// SAFETY: All fields are integer types with no uninitialized padding bytes.
+unsafe impl AsBytes for GspGmcMsgElement {}
+
+// SAFETY: All fields are integer types for which all bit patterns are valid.
+unsafe impl FromBytes for GspGmcMsgElement {}
+
/// Arguments for GSP startup.
#[repr(transparent)]
#[derive(Zeroable)]
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index 03546213da28..0ae88bef2a05 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -28,6 +28,8 @@ pub(crate) enum NvdmType with TryFrom<Bounded<u32, 8>> {
Cot = 0x14,
/// FSP command response.
FspResponse = 0x15,
+ /// GMC API message (GSP command queue).
+ GmcApi = 0x26,
}
}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 15/27] gpu: nova-core: add GMC send path
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (13 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 14/27] gpu: nova-core: add GMC API message types John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
` (11 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP-RM boot protocol requires the driver to send GSP_INIT as a
GMC command. GMC and RPC commands share the command queue and sequence
counter. GMC queue elements carry a GMC API header and no checksum.
Add the GMC send path needed for GSP_INIT.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 59 +++++++++++++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw.rs | 1 -
2 files changed, 59 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 0dab3d279426..88b143c6a7b7 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -52,6 +52,7 @@
driver::Bar0,
gsp::{
fw::{
+ GspGmcMsgElement,
GspMsgElement,
MsgFunction,
MsgqRxHeader,
@@ -963,6 +964,64 @@ fn send_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result<u32>
Ok(rpc_seq)
}
+ /// Sends a GMC API command to the GSP.
+ ///
+ /// `command_id` is the GMC command identifier (from `GMCAPI_COMMANDS`).
+ /// `payload` is the command-specific data following the [`super::fw::GmcApiHeader`].
+ /// `max_response_size` is the maximum expected response payload size.
+ ///
+ /// The command carries the next RPC sequence number, which the GSP echoes in its response.
+ /// The number is consumed whether or not the send succeeds.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the command header is not properly aligned.
+ #[expect(dead_code)]
+ fn send_gmc(
+ &mut self,
+ bar: Bar0<'_>,
+ command_id: u32,
+ payload: &[u8],
+ max_response_size: u32,
+ ) -> Result {
+ let rpc_seq = self.rpc_seq;
+ self.rpc_seq = self.rpc_seq.wrapping_add(1);
+
+ let dst = self
+ .gsp_mem
+ .allocate_command::<GspGmcMsgElement>(payload.len(), Self::ALLOCATE_TIMEOUT)?;
+
+ let msg_element = GspGmcMsgElement::init(
+ command_id,
+ u64::from(rpc_seq),
+ payload.len(),
+ max_response_size,
+ );
+ // SAFETY: `dst.header` points to a valid, writable `GspGmcMsgElement` region.
+ unsafe {
+ msg_element.__init(core::ptr::from_mut(dst.header))?;
+ }
+
+ SBufferIter::new_writer([&mut dst.contents.0[..], &mut dst.contents.1[..]])
+ .write_all(payload)?;
+
+ dev_dbg!(
+ &self.dev,
+ "GSP GMC: send: seq# {}, command_id=0x{:x}, length=0x{:x}\n",
+ rpc_seq,
+ command_id,
+ dst.header.length(),
+ );
+
+ let elem_count = dst.header.element_count();
+ self.gsp_mem.advance_cpu_write_ptr(elem_count);
+ Cmdq::notify_gsp(bar);
+
+ Ok(())
+ }
+
/// Wait for a message to become available on the message queue.
///
/// This works purely at the transport layer and does not interpret or validate the message
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 050a6af8c0c7..56f255a3d49c 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -1016,7 +1016,6 @@ pub(crate) struct GspGmcMsgElement {
== core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, nvdmHeader)
);
-#[expect(dead_code)]
impl GspGmcMsgElement {
/// Creates a new GMC message element.
///
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 16/27] gpu: nova-core: add GMC transport receive path
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (14 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 15/27] gpu: nova-core: add GMC send path John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
` (10 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
GSP-RM posts GMC and RPC messages on the same message queue. Both use
the same MCTP and NVDM transport headers, but RPC messages continue with
rpc_message_header_v and GMC messages continue with GmcApiHeader.
Existing RPC receive code cannot parse the GMC layout.
Add a GMC receive path that validates the MCTP magic and rejects
payloads whose advertised length exceeds the available queue contents.
On a framing or length failure, poison the queue because the driver
cannot safely advance the read pointer without a trusted length.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 83 ++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/fw.rs | 10 ++++
2 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 88b143c6a7b7..fba94153e744 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -629,7 +629,7 @@ struct GspCommand<'a, H = GspMsgElement> {
/// A message ready to be processed from the message queue.
///
-/// This is the type returned by [`Cmdq::wait_for_msg`].
+/// This is the type returned by [`CmdqInner::wait_for_msg`].
struct GspMessage<'a> {
// Reference to the header of the message.
header: &'a GspMsgElement,
@@ -638,6 +638,18 @@ struct GspMessage<'a> {
contents: (&'a [u8], &'a [u8]),
}
+/// A GMC message ready to be processed from the message queue.
+///
+/// This is the type returned by [`CmdqInner::wait_for_gmc_msg`].
+#[expect(dead_code)]
+struct GmcMessage<'a> {
+ // Reference to the header of the message.
+ header: &'a GspGmcMsgElement,
+ // Slices of the payload following the `GmcApiHeader`. The second slice is empty unless the
+ // payload wraps around the end of the message queue.
+ contents: (&'a [u8], &'a [u8]),
+}
+
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
@@ -1285,4 +1297,73 @@ fn drain(&mut self) -> Result {
Ok(())
}
+
+ /// Wait for a GMC message to become available on the message queue.
+ ///
+ /// This is the GMC counterpart to [`Self::wait_for_msg`] and reads the same queue. Like that
+ /// method it works purely at the transport layer, validating the MCTP framing and the
+ /// advertised length and nothing else.
+ ///
+ /// A [`GspGmcMsgElement`] and a [`GspMsgElement`] share every field through `nvdm_header`,
+ /// so a caller that may see either must check the NVDM type before reading `gmc`. Both
+ /// layouts put the element length in the same place, so the caller can advance the read
+ /// pointer past a returned message either way.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
+ /// - `EIO` if the framing is invalid, or the queue was already poisoned by an earlier such
+ /// failure. Either failure poisons the queue, so recovery requires a reset.
+ #[expect(dead_code)]
+ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
+ if self.poisoned.get() {
+ return Err(EIO);
+ }
+
+ let (slice_1, slice_2) = read_poll_timeout(
+ || Ok(self.gsp_mem.driver_read_area()),
+ |driver_area| !driver_area.0.is_empty(),
+ Delta::from_millis(1),
+ timeout,
+ )
+ .map(|(slice_1, slice_2)| (slice_1.as_flattened(), slice_2.as_flattened()))?;
+
+ let Some((header, slice_1)) = GspGmcMsgElement::from_bytes_prefix(slice_1) else {
+ self.poisoned.set(true);
+ return Err(EIO);
+ };
+
+ // Checked before any length field is read, since a bad magic leaves them untrusted.
+ if !header.has_valid_magic() {
+ dev_err!(&self.dev, "GSP GMC: receive: bad MCTP magic\n");
+ self.poisoned.set(true);
+ return Err(EIO);
+ }
+
+ let payload_length = header.payload_length();
+
+ // Check that the driver read area is large enough for the message.
+ if slice_1.len() + slice_2.len() < payload_length {
+ self.poisoned.set(true);
+ return Err(EIO);
+ }
+
+ // Cut the message slices down to the actual length of the message.
+ let (slice_1, slice_2) = if slice_1.len() > payload_length {
+ // PANIC: we checked above that `slice_1` is at least as long as `payload_length`.
+ (slice_1.split_at(payload_length).0, &slice_2[0..0])
+ } else {
+ (
+ slice_1,
+ // PANIC: we checked above that `slice_1.len() + slice_2.len()` is at least as
+ // large as `payload_length`.
+ slice_2.split_at(payload_length - slice_1.len()).0,
+ )
+ };
+
+ Ok(GmcMessage {
+ header,
+ contents: (slice_1, slice_2),
+ })
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 56f255a3d49c..9e6b5ec6aadb 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -1055,11 +1055,21 @@ pub(crate) fn init(
})
}
+ /// Returns the length of the response payload (data after the [`GmcApiHeader`]).
+ pub(crate) fn payload_length(&self) -> usize {
+ num::u32_as_usize(self.nvdm_payload_size).saturating_sub(size_of::<GmcApiHeader>())
+ }
+
/// Returns the total length of the message, transport and GMC headers included.
pub(crate) fn length(&self) -> usize {
num::u32_as_usize(self.mctp_payload_size)
}
+ /// Returns `true` if the MCTP magic field contains the expected value.
+ pub(crate) fn has_valid_magic(&self) -> bool {
+ self.mctp_magic == MCTP_MAGIC
+ }
+
/// Returns the number of elements (i.e. memory pages) used by this message.
pub(crate) fn element_count(&self) -> u32 {
self.mctp_payload_size
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (15 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 16/27] gpu: nova-core: add GMC transport receive path John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
` (9 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 boot path delivers the load-and-execute steps as GMC events
keyed by command id, where the RPC path keys on a function code, so the
boot loop needs its own dispatch.
Add a GMC receive that dispatches on the command id. A GMC element and
an RPC element share every field through the NVDM header, so this checks
the NVDM type before reading the GMC header and drops an element of the
other kind rather than handing the handler a command id decoded from an
RPC header. An element nobody claims yields Ok(None), and the read
pointer advances on every path.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 69 ++++++++++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/fw.rs | 20 +++++++++
2 files changed, 87 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index fba94153e744..7da61bc9ad92 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -641,7 +641,6 @@ struct GspMessage<'a> {
/// A GMC message ready to be processed from the message queue.
///
/// This is the type returned by [`CmdqInner::wait_for_gmc_msg`].
-#[expect(dead_code)]
struct GmcMessage<'a> {
// Reference to the header of the message.
header: &'a GspGmcMsgElement,
@@ -791,6 +790,22 @@ fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
self.inner.lock().receive_msg(timeout, None)
}
+ /// Receives one GMC event from the GSP and passes its command id and raw payload slices to
+ /// `handler`.
+ ///
+ /// This method may sleep while waiting. The [`CmdqInner`] mutex stays locked across the wait
+ /// and across the `handler` call, so `handler` must not call back into this [`Cmdq`].
+ ///
+ /// See [`CmdqInner::receive_gmc_and_dispatch`] for return values, queue state, and errors.
+ #[expect(dead_code)]
+ pub(crate) fn receive_gmc_and_dispatch<R>(
+ &self,
+ timeout: Delta,
+ handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+ ) -> Result<Option<R>> {
+ self.inner.lock().receive_gmc_and_dispatch(timeout, handler)
+ }
+
/// Waits for an unsolicited GSP event of type `M`, dispatching any other event that arrives
/// first.
///
@@ -1314,7 +1329,6 @@ fn drain(&mut self) -> Result {
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if the framing is invalid, or the queue was already poisoned by an earlier such
/// failure. Either failure poisons the queue, so recovery requires a reset.
- #[expect(dead_code)]
fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
if self.poisoned.get() {
return Err(EIO);
@@ -1366,4 +1380,55 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
contents: (slice_1, slice_2),
})
}
+
+ /// Receive the next GMC event from the GSP and dispatch it through a handler.
+ ///
+ /// The handler receives the GMC command id and the raw payload slices that follow the
+ /// [`super::fw::GmcApiHeader`] (two slices because the circular buffer may wrap). It returns
+ /// `None` for an event it does not handle.
+ ///
+ /// Where [`Self::receive_msg`] keys on [`MsgFunction`], this keys on the GMC command id,
+ /// which is the form the r000 firmware uses for boot events.
+ ///
+ /// Returns `Ok(None)` when nothing claimed the element, either because it is not a GMC
+ /// element or because the handler declined it. The read pointer is advanced on every path.
+ ///
+ /// # Errors
+ ///
+ /// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
+ /// - `EIO` if the queue is poisoned or the element fails framing validation (see
+ /// [`Self::wait_for_gmc_msg`]).
+ fn receive_gmc_and_dispatch<R>(
+ &mut self,
+ timeout: Delta,
+ handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+ ) -> Result<Option<R>> {
+ let message = self.wait_for_gmc_msg(timeout)?;
+ let header = message.header;
+ let length = header.length();
+
+ // The RPC and GMC elements share every field through `nvdm_header`, so `gmc` holds an
+ // RPC header rather than a GMC one unless the NVDM type says otherwise.
+ let result = if header.is_gmc_api() {
+ let command_id = header.gmc.command_id();
+
+ dev_dbg!(
+ &self.dev,
+ "GSP GMC: event: seq# {}, command_id=0x{:x}, length=0x{:x}\n",
+ header.gmc.sequence,
+ command_id,
+ length,
+ );
+
+ handler(command_id, message.contents.0, message.contents.1)
+ } else {
+ dev_warn!(&self.dev, "GSP GMC: dropping non-GMC queue element\n");
+ None
+ };
+
+ self.gsp_mem
+ .advance_cpu_read_ptr(u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?);
+
+ Ok(result)
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 9e6b5ec6aadb..83f7d2042aa1 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -975,8 +975,19 @@ pub(crate) struct GmcApiHeader {
reserved: [u32; 5],
}
+/// Command identifier bits of [`GmcApiHeader::command`], matching Open RM's
+/// `GMCAPI_HEADER_COMMAND_ID_MASK`. The remaining byte carries flags.
+const GMCAPI_COMMAND_ID_MASK: u32 = 0x00ff_ffff;
+
static_assert!(size_of::<GmcApiHeader>() == 40);
+impl GmcApiHeader {
+ /// Returns the command identifier, without the flag byte.
+ pub(crate) fn command_id(&self) -> u32 {
+ self.command & GMCAPI_COMMAND_ID_MASK
+ }
+}
+
// SAFETY: All fields are integer types with no uninitialized padding bytes.
unsafe impl AsBytes for GmcApiHeader {}
@@ -1070,6 +1081,15 @@ pub(crate) fn has_valid_magic(&self) -> bool {
self.mctp_magic == MCTP_MAGIC
}
+ /// Returns `true` if the NVDM header routes this element to the GSP's GMC dispatch.
+ ///
+ /// A [`GspMsgElement`] and a [`GspGmcMsgElement`] share every field through `nvdm_header`,
+ /// so the NVDM type is what distinguishes the two on the queue, and `gmc` holds an RPC
+ /// header rather than a GMC one when this returns `false`.
+ pub(crate) fn is_gmc_api(&self) -> bool {
+ self.nvdm_header.validate(NvdmType::GmcApi)
+ }
+
/// Returns the number of elements (i.e. memory pages) used by this message.
pub(crate) fn element_count(&self) -> u32 {
self.mctp_payload_size
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (16 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 17/27] gpu: nova-core: gsp: add GMC dispatch on receive John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event John Hubbard
` (8 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP boot protocol sends a load-and-execute event for the
generic falcon bootloader.
Nova-core stored the image and its load parameters in the FWSEC firmware
wrapper, which kept the r000 boot path from loading it independently.
Separate the bootloader image and load parameters from the FWSEC
wrapper. Place the bootloader at the end of the target falcon's
HWCFG-reported IMEM rather than below a fixed 64 KiB ceiling, so its
placement remains within bounds on falcons with less IMEM.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 15 +++
drivers/gpu/nova-core/firmware.rs | 1 +
.../nova-core/firmware/fwsec/bootloader.rs | 64 ++---------
.../gpu/nova-core/firmware/gen_bootloader.rs | 102 ++++++++++++++++++
drivers/gpu/nova-core/gsp/hal/tu102.rs | 6 +-
drivers/gpu/nova-core/regs.rs | 5 +
6 files changed, 135 insertions(+), 58 deletions(-)
create mode 100644 drivers/gpu/nova-core/firmware/gen_bootloader.rs
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 20a288050c37..b8013d2eb582 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -387,6 +387,21 @@ pub(crate) fn new(
})
}
+ /// Returns the size of this falcon's IMEM, in bytes.
+ ///
+ /// `NV_PFALCON_FALCON_IMEMC` addresses IMEM with a 16-bit byte offset, so the result never
+ /// exceeds 64KiB.
+ pub(crate) fn imem_size(&self) -> usize {
+ let blocks = usize::from_safe_cast(
+ *self
+ .bar
+ .read(regs::NV_PFALCON_FALCON_HWCFG::of::<E>())
+ .imem_size(),
+ );
+
+ blocks * MEM_BLOCK_ALIGNMENT
+ }
+
/// Resets DMA-related registers.
pub(crate) fn dma_reset(&self) {
self.bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
diff --git a/drivers/gpu/nova-core/firmware.rs b/drivers/gpu/nova-core/firmware.rs
index e0befe84aa3e..9ff764930bcd 100644
--- a/drivers/gpu/nova-core/firmware.rs
+++ b/drivers/gpu/nova-core/firmware.rs
@@ -26,6 +26,7 @@
pub(crate) mod booter;
pub(crate) mod fsp;
pub(crate) mod fwsec;
+pub(crate) mod gen_bootloader;
pub(crate) mod gsp;
pub(crate) mod radix3;
pub(crate) mod riscv;
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index ec4d92317a93..6670d17b4eeb 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -14,18 +14,12 @@
dma::Coherent,
io::{register::WithBase, Io},
prelude::*,
- ptr::{
- Alignable,
- Alignment, //
- },
- sizes,
transmute::AsBytes,
};
use crate::{
driver::Bar0,
falcon::{
- self,
gsp::Gsp,
Falcon,
FalconBromParams,
@@ -39,10 +33,7 @@
},
firmware::{
fwsec::FwsecFirmware,
- tlv::{
- request_tlv, //
- Tlv,
- },
+ gen_bootloader::GenericBootloader, //
},
gpu::Chipset,
num::FromSafeCast, //
@@ -101,16 +92,12 @@ unsafe impl AsBytes for BootloaderDmemDescV2 {}
pub(crate) struct FwsecFirmwareWithBl {
/// DMA object the bootloader will copy the firmware from.
_firmware_dma: Coherent<[u8]>,
- /// Code of the bootloader to be loaded into non-secure IMEM.
- ucode: KVec<u8>,
+ /// Bootloader that performs the load.
+ bootloader: GenericBootloader,
/// Descriptor to be loaded into DMEM for the bootloader to read.
dmem_desc: BootloaderDmemDescV2,
- /// Range-validated start offset of the firmware code in IMEM.
- imem_dst_start: u16,
/// BROM parameters of the loaded firmware.
brom_params: FalconBromParams,
- /// Range-validated `desc.start_tag`.
- start_tag: u16,
}
impl FwsecFirmwareWithBl {
@@ -120,29 +107,9 @@ pub(crate) fn new(
firmware: FwsecFirmware,
dev: &Device<device::Bound>,
chipset: Chipset,
+ falcon: &Falcon<'_, Gsp>,
) -> Result<Self> {
- let fw = request_tlv(dev, chipset, "gen_bootloader")?;
- let tlv = Tlv::new(fw.data())?;
- dev_dbg!(
- dev,
- "loaded generic bootloader firmware v{}\n",
- tlv.get_string(b"VERS")?
- );
-
- let ucode = {
- let blob = tlv.get_bytes(b"BLOB")?;
- let code_size = usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
- let code = blob.get(..code_size).ok_or(EINVAL)?;
- let aligned_code_size = code_size
- .align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>())
- .ok_or(EINVAL)?;
-
- let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
- ucode.extend_from_slice(code, GFP_KERNEL)?;
- ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
-
- ucode
- };
+ let bootloader = GenericBootloader::new(dev, chipset, falcon)?;
// `BootloaderDmemDescV2` expects the source to be a mirror image of the destination and
// uses the same offset parameter for both.
@@ -213,21 +180,11 @@ pub(crate) fn new(
}
};
- // The bootloader's code must be loaded in the area right below the first 64K of IMEM.
- const BOOTLOADER_LOAD_CEILING: usize = sizes::SZ_64K;
- let imem_dst_start = BOOTLOADER_LOAD_CEILING
- .checked_sub(ucode.len())
- .ok_or(EOVERFLOW)?;
-
- let start_tag = u16::try_from(tlv.get_u32(b"STRT")?)?;
-
Ok(Self {
_firmware_dma: firmware_dma,
- ucode,
+ bootloader,
dmem_desc,
brom_params: firmware.brom_params(),
- imem_dst_start: u16::try_from(imem_dst_start)?,
- start_tag,
})
}
@@ -282,7 +239,7 @@ fn brom_params(&self) -> FalconBromParams {
fn boot_addr(&self) -> u32 {
// On V2 platforms, the boot address is extracted from the generic bootloader, because the
// gbl is what actually copies FWSEC into memory, so that is what needs to be booted.
- u32::from(self.start_tag) << 8
+ self.bootloader.boot_addr()
}
}
@@ -292,12 +249,7 @@ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
}
fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
- Some(FalconPioImemLoadTarget {
- data: self.ucode.as_ref(),
- dst_start: self.imem_dst_start,
- secure: false,
- start_tag: self.start_tag,
- })
+ Some(self.bootloader.imem_load_params())
}
fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
new file mode 100644
index 000000000000..f949223af2d0
--- /dev/null
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! The generic falcon bootloader.
+//!
+//! A small program loaded into falcon IMEM using PIO, which then DMAs a larger image into IMEM and
+//! DMEM from a descriptor the driver leaves in DMEM at offset 0. Open RM's
+//! `ksec2GetGenericBlUcode` supplies the same image for both SEC2 and GSP.
+
+use kernel::{
+ device,
+ prelude::*,
+ ptr::{
+ Alignable,
+ Alignment, //
+ },
+};
+
+use crate::{
+ falcon::{
+ self,
+ Falcon,
+ FalconEngine,
+ FalconPioImemLoadTarget, //
+ },
+ firmware::tlv::{
+ request_tlv, //
+ Tlv,
+ },
+ gpu::Chipset,
+ num::FromSafeCast, //
+};
+
+/// The generic falcon bootloader image and the IMEM placement it was loaded for.
+pub(crate) struct GenericBootloader {
+ /// Bootloader code, zero-padded to a whole number of falcon memory blocks.
+ ucode: KVec<u8>,
+ /// Byte offset in IMEM the code is loaded at.
+ imem_dst_start: u16,
+ /// Tag the first code block is loaded under.
+ start_tag: u16,
+}
+
+impl GenericBootloader {
+ /// Loads the generic bootloader image for `chipset`, placed in the last blocks of `falcon`'s
+ /// IMEM so the image it goes on to load has the rest to itself.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if a required TLV field is absent or the image does not fit in IMEM.
+ /// - `ENOMEM` if the padded copy of the code cannot be allocated.
+ pub(crate) fn new<E: FalconEngine + 'static>(
+ dev: &device::Device<device::Bound>,
+ chipset: Chipset,
+ falcon: &Falcon<'_, E>,
+ ) -> Result<Self> {
+ let fw = request_tlv(dev, chipset, "gen_bootloader")?;
+ let tlv = Tlv::new(fw.data())?;
+ dev_dbg!(
+ dev,
+ "loaded generic bootloader firmware v{}\n",
+ tlv.get_string(b"VERS")?
+ );
+
+ let ucode = {
+ let blob = tlv.get_bytes(b"BLOB")?;
+ let code_size = usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
+ let code = blob.get(..code_size).ok_or(EINVAL)?;
+ let aligned_code_size = code_size
+ .align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>())
+ .ok_or(EINVAL)?;
+
+ let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
+ ucode.extend_from_slice(code, GFP_KERNEL)?;
+ ucode.resize(aligned_code_size, 0, GFP_KERNEL)?;
+
+ ucode
+ };
+
+ let imem_dst_start = falcon.imem_size().checked_sub(ucode.len()).ok_or(EINVAL)?;
+
+ Ok(Self {
+ ucode,
+ imem_dst_start: u16::try_from(imem_dst_start)?,
+ start_tag: u16::try_from(tlv.get_u32(b"STRT")?)?,
+ })
+ }
+
+ /// Returns the address the falcon must boot from to run this bootloader.
+ pub(crate) fn boot_addr(&self) -> u32 {
+ u32::from(self.start_tag) << 8
+ }
+
+ /// Returns the PIO parameters that place this bootloader in non-secure IMEM.
+ pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
+ FalconPioImemLoadTarget {
+ data: self.ucode.as_ref(),
+ dst_start: self.imem_dst_start,
+ secure: false,
+ start_tag: self.start_tag,
+ }
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index a5c0ca355493..68a48c882c0f 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -166,7 +166,7 @@ fn run_fwsec_frts(
)?;
if self.needs_fwsec_bootloader {
- let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset)?;
+ let fwsec_frts_bl = FwsecFirmwareWithBl::new(fwsec_frts, dev, chipset, falcon)?;
// Load and run the bootloader, which will load FWSEC-FRTS and run it.
fwsec_frts_bl.run(dev, falcon, bar)?;
} else {
@@ -224,7 +224,9 @@ fn build_unload_bundle(
// Load the FWSEC SB firmware, as well as its bootloader if required.
let fwsec_sb = FwsecFirmware::new(dev, gsp_falcon, bios, FwsecCommand::Sb)?;
let fwsec_sb = if self.needs_fwsec_bootloader {
- FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(fwsec_sb, dev, chipset)?)
+ FwsecUnloadFirmware::WithBl(FwsecFirmwareWithBl::new(
+ fwsec_sb, dev, chipset, gsp_falcon,
+ )?)
} else {
FwsecUnloadFirmware::WithoutBl(fwsec_sb)
};
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 5d265b5788e3..5501c36a56af 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -247,6 +247,11 @@ pub(crate) fn usable_fb_size(self) -> u64 {
31:0 value => u32;
}
+ pub(crate) NV_PFALCON_FALCON_HWCFG(u32) @ PFalconBase + 0x00000108 {
+ /// Size of this falcon's IMEM, in blocks of [`crate::falcon::MEM_BLOCK_ALIGNMENT`] bytes.
+ 8:0 imem_size;
+ }
+
pub(crate) NV_PFALCON_FALCON_DMACTL(u32) @ PFalconBase + 0x0000010c {
7:7 secure_stat => bool;
6:3 dmaq_num;
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (17 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 18/27] gpu: nova-core: separate the generic falcon bootloader from FWSEC John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event John Hubbard
` (7 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP boot protocol asks the driver to run a high-security binary
on the GSP falcon, and sends the framebuffer addresses of its code and
data in the event payload.
Nova-core has no handler for that event. It also has no way to restart
GSP-RM afterwards, which every load-and-execute event needs.
Add the handler. It DMAs the image into falcon memory, programs the BROM
registers that make the falcon verify the PKC signature, runs the binary,
and resumes GSP-RM. Write FLCN_ERR_BINARY_NOT_STARTED to MAILBOX0 before
starting, which Open RM does so a binary that never runs is
distinguishable from one that succeeded, and which also clears the
suspend bit the next event polls.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 7 +-
drivers/gpu/nova-core/gsp/boot.rs | 229 +++++++++++++++++++++++++++++-
drivers/gpu/nova-core/regs.rs | 1 +
3 files changed, 232 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index b8013d2eb582..87cb6cf4f79d 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -140,7 +140,6 @@ pub(crate) enum FalconMem {
}
/// Source offset of a raw falcon DMA transfer, added to the DMA base address.
-#[expect(dead_code)]
#[derive(Copy, Clone)]
pub(crate) enum FalconDmaSrcOffset {
/// Byte offset from the DMA base address.
@@ -636,7 +635,6 @@ fn dma_wr(
/// 256 bytes, or if a [`FalconDmaSrcOffset::DmemVa`] source offset is paired with an IMEM
/// target.
/// - `ERANGE` if `src_addr` does not fit the `DMATRFBASE` register pair.
- #[expect(dead_code)]
pub(crate) fn raw_dma_transfer(
&self,
ctx_dma: u8,
@@ -782,7 +780,10 @@ pub(crate) fn wait_till_halted(&self) -> Result<()> {
///
/// The RISC-V GSP signals suspension by setting bit 31 (`0x8000_0000`) in `MAILBOX0`, rather
/// than through `CPUCTL.halted`.
- #[expect(dead_code)]
+ ///
+ /// Nothing else clears that bit, so the caller must write `MAILBOX0` before starting the
+ /// falcon CPU. A caller that runs this twice without an intervening write sees the first
+ /// suspension both times.
pub(crate) fn wait_for_processor_suspend(&self) -> Result<()> {
const INTERRUPT_PROCESSOR_SUSPENDED: u32 = 0x8000_0000;
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index a64313dca0a1..ae2aca5d935a 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -3,9 +3,15 @@
use kernel::{
bits,
- io::poll::read_poll_timeout,
+ device,
+ io::{
+ poll::read_poll_timeout,
+ register::WithBase,
+ Io, //
+ },
prelude::*,
time::Delta,
+ transmute::FromBytes,
types::ScopeGuard, //
};
@@ -13,13 +19,20 @@
driver::Bar0,
falcon::{
gsp::Gsp,
- Falcon, //
+ sec2::Sec2,
+ Falcon,
+ FalconDmaSrcOffset,
+ FalconFbifMemType,
+ FalconFbifTarget,
+ FalconMem,
+ FalconModSelAlgo, //
},
firmware::gsp::GspFirmware,
gsp::{
cmdq::Cmdq,
commands, //
},
+ regs, //
};
impl super::Gsp {
@@ -86,6 +99,182 @@ pub(crate) fn boot(
Ok(unload_guard.dismiss().1)
}
+ /// Restart GSP-RM once a load-and-execute image has run to completion.
+ ///
+ /// Resets the GSP falcon into RISC-V, hands it the libos boot arguments address through its
+ /// mailboxes, and starts SEC2, which is what brings GSP-RM back up. Open RM calls this
+ /// `kgspExecuteCoreResume`.
+ ///
+ /// # Errors
+ ///
+ /// - `EIO` if SEC2 reports a failure, or if the GSP is not running RISC-V afterwards.
+ /// - `ETIMEDOUT` if SEC2 does not complete the reload in time.
+ fn core_resume(
+ gsp_falcon: &Falcon<'_, Gsp>,
+ sec2_falcon: &Falcon<'_, Sec2>,
+ dev: &device::Device,
+ bootloader_app_version: u32,
+ libos_dma_handle: u64,
+ ) -> Result {
+ gsp_falcon.reset()?;
+
+ gsp_falcon.write_mailboxes(
+ Some(libos_dma_handle as u32),
+ Some((libos_dma_handle >> 32) as u32),
+ );
+
+ sec2_falcon.start()?;
+
+ gsp_falcon
+ .check_reload_completed(Delta::from_secs(2))
+ .inspect_err(|_| {
+ let mbox0 = sec2_falcon.read_mailbox0();
+ dev_err!(
+ dev,
+ "Timeout waiting for SEC2 to resume GSP-RM (SEC2 mbox0={:#x})\n",
+ mbox0
+ );
+ })?;
+
+ let sec2_mbox0 = sec2_falcon.read_mailbox0();
+ if sec2_mbox0 != 0 {
+ dev_err!(
+ dev,
+ "SEC2 reported error during core resume: {:#x}\n",
+ sec2_mbox0
+ );
+ return Err(EIO);
+ }
+
+ gsp_falcon.write_os_version(bootloader_app_version);
+
+ if !gsp_falcon.is_riscv_active() {
+ dev_err!(dev, "GSP RISC-V not active after core resume\n");
+ return Err(EIO);
+ }
+
+ Ok(())
+ }
+
+ /// Handle a `GSP_LOAD_EXEC_HS_BINARY` event.
+ ///
+ /// The GSP asks the driver to run a high-security binary that it has already placed in the
+ /// framebuffer. The driver DMAs the image into falcon memory, programs the BROM registers
+ /// that make the falcon verify its PKC signature, runs it, and resumes GSP-RM.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the payload is shorter than the parameter block, or the ucode id does not
+ /// fit the BROM register field.
+ /// - `ETIMEDOUT` if the GSP does not suspend, or the binary does not halt, in time.
+ #[expect(dead_code)]
+ #[allow(clippy::too_many_arguments)]
+ fn handle_load_exec_hs_binary(
+ payload: &[u8],
+ gsp_falcon: &Falcon<'_, Gsp>,
+ sec2_falcon: &Falcon<'_, Sec2>,
+ bar: Bar0<'_>,
+ dev: &device::Device,
+ bootloader_app_version: u32,
+ libos_dma_handle: u64,
+ ) -> Result {
+ let params = HsBinaryParams::from_bytes_prefix(payload).ok_or(EINVAL)?.0;
+
+ gsp_falcon.wait_for_processor_suspend().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for GSP suspend (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })?;
+
+ gsp_falcon.reset()?;
+
+ gsp_falcon.dma_reset();
+ bar.update(
+ regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>().at(usize::from(HS_BINARY_CTX_DMA)),
+ |v| {
+ v.with_target(FalconFbifTarget::LocalFb)
+ .with_mem_type(FalconFbifMemType::Physical)
+ .with_engine_id_flag(true)
+ },
+ );
+
+ if params.ucode_imem_size > 0 {
+ gsp_falcon.raw_dma_transfer(
+ HS_BINARY_CTX_DMA,
+ params.imem_phys_addr,
+ FalconMem::ImemSecure,
+ FalconDmaSrcOffset::Offset(params.ucode_imem_va),
+ params.ucode_imem_pa,
+ params.ucode_imem_size,
+ )?;
+ }
+
+ if params.ucode_dmem_size > 0 {
+ // A valid DMEM virtual address makes the engine tag each loaded block with it, which
+ // is how the binary reaches its data.
+ let src = if params.ucode_dmem_va == FLCN_DMEM_VA_INVALID {
+ FalconDmaSrcOffset::Offset(0)
+ } else {
+ FalconDmaSrcOffset::DmemVa(params.ucode_dmem_va)
+ };
+
+ gsp_falcon.raw_dma_transfer(
+ HS_BINARY_CTX_DMA,
+ params.dmem_phys_addr,
+ FalconMem::Dmem,
+ src,
+ params.ucode_dmem_pa,
+ params.ucode_dmem_size,
+ )?;
+ }
+
+ bar.write(
+ WithBase::of::<Gsp>().at(0),
+ regs::NV_PFALCON2_FALCON_BROM_PARAADDR::zeroed().with_value(params.hs_sig_dmem_addr),
+ );
+ bar.write(
+ WithBase::of::<Gsp>(),
+ regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::zeroed().with_value(params.engine_id_mask),
+ );
+ bar.write(
+ WithBase::of::<Gsp>(),
+ regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::zeroed()
+ .with_ucode_id(u8::try_from(params.ucode_id).map_err(|_| EINVAL)?),
+ );
+ bar.write(
+ WithBase::of::<Gsp>(),
+ regs::NV_PFALCON2_FALCON_MOD_SEL::zeroed().with_algo(FalconModSelAlgo::Rsa3k),
+ );
+
+ // Also clears the suspend bit that `wait_for_processor_suspend` polls, so the next
+ // load-and-execute event does not read this one's suspension.
+ gsp_falcon.write_mailboxes(Some(FLCN_ERR_BINARY_NOT_STARTED), None);
+
+ bar.write(
+ WithBase::of::<Gsp>(),
+ regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(params.ucode_imem_va),
+ );
+
+ gsp_falcon.start()?;
+ gsp_falcon.wait_till_halted().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for HS binary to halt (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })?;
+
+ Self::core_resume(
+ gsp_falcon,
+ sec2_falcon,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ )
+ }
+
/// Shut down the GSP and wait until it is offline.
fn shutdown_gsp(
cmdq: &Cmdq,
@@ -146,3 +335,39 @@ pub(crate) fn unload(
res.inspect(|()| dev_info!(dev, "GSP successfully unloaded\n"))
}
}
+
+/// Value Open RM leaves in `MAILBOX0` before starting a falcon binary, so a binary that never
+/// runs is distinguishable from one that ran and returned success.
+const FLCN_ERR_BINARY_NOT_STARTED: u32 = 0xfe;
+
+/// `ucode_dmem_va` value meaning the binary has no DMEM virtual address.
+const FLCN_DMEM_VA_INVALID: u32 = 0xffff_ffff;
+
+/// Context DMA slot the HS binary is loaded through. Open RM hardcodes slot 0 for this event and
+/// points it at local framebuffer.
+const HS_BINARY_CTX_DMA: u8 = 0;
+
+/// Parameters for loading and executing an HS (High-Security) binary.
+///
+/// Sent by GSP-RM as the payload of `GSP_LOAD_EXEC_HS_BINARY`. The firmware
+/// code and data are located in framebuffer memory at the given physical addresses.
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct HsBinaryParams {
+ imem_phys_addr: u64,
+ dmem_phys_addr: u64,
+ _reserved64: [u64; 2],
+ ucode_imem_va: u32,
+ ucode_imem_pa: u32,
+ ucode_imem_size: u32,
+ ucode_dmem_va: u32,
+ ucode_dmem_pa: u32,
+ ucode_dmem_size: u32,
+ hs_sig_dmem_addr: u32,
+ engine_id_mask: u32,
+ ucode_id: u32,
+ _reserved32: [u32; 3],
+}
+
+// SAFETY: This struct only contains integer types for which all bit patterns are valid.
+unsafe impl FromBytes for HsBinaryParams {}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 5501c36a56af..88bb60c8a259 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -353,6 +353,7 @@ pub(crate) fn usable_fb_size(self) -> u64 {
}
pub(crate) NV_PFALCON_FBIF_TRANSCFG(u32)[8] @ PFalconBase + 0x00000600 {
+ 3:3 engine_id_flag => bool;
2:2 mem_type => FalconFbifMemType;
1:0 target ?=> FalconFbifTarget;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (18 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 19/27] gpu: nova-core: handle the r000 load-and-execute HS binary event John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
` (6 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP boot protocol asks the driver to run an image on the GSP
falcon, and passes a bootloader descriptor naming where the image lives.
Open RM does not copy the image itself. It runs the generic falcon
bootloader, which reads that descriptor from DMEM and does the copy.
Nova-core has no handler for that event, and keeps its copy of the
descriptor in the FWSEC module that first needed one.
Move the descriptor next to the bootloader that reads it, and add the
handler. It points the requested FBIF aperture at the image, loads the
descriptor and the bootloader, runs it, and restores the aperture.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
.../nova-core/firmware/fwsec/bootloader.rs | 52 +-----
.../gpu/nova-core/firmware/gen_bootloader.rs | 115 ++++++++++++-
drivers/gpu/nova-core/gsp/boot.rs | 161 +++++++++++++++++-
3 files changed, 278 insertions(+), 50 deletions(-)
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 6670d17b4eeb..43908eb11f7d 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -33,60 +33,16 @@
},
firmware::{
fwsec::FwsecFirmware,
- gen_bootloader::GenericBootloader, //
+ gen_bootloader::{
+ BootloaderDmemDescV2,
+ GenericBootloader, //
+ },
},
gpu::Chipset,
num::FromSafeCast, //
regs,
};
-/// Structure used by the boot-loader to load the rest of the code.
-///
-/// This has to be filled by the GPU driver and copied into DMEM at offset
-/// [`BootloaderDesc.dmem_load_off`].
-#[repr(C, packed)]
-#[derive(Debug, Clone)]
-struct BootloaderDmemDescV2 {
- /// Reserved, should always be first element.
- reserved: [u32; 4],
- /// 16B signature for secure code, 0s if no secure code.
- signature: [u32; 4],
- /// DMA context used by the bootloader while loading code/data.
- ctx_dma: u32,
- /// 256B-aligned physical FB address where code is located.
- code_dma_base: u64,
- /// Offset from `code_dma_base` where the non-secure code is located.
- ///
- /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
- /// expected to be a mirror image of its loaded state.
- ///
- /// Must be multiple of 256.
- non_sec_code_off: u32,
- /// Size of the non-secure code part.
- non_sec_code_size: u32,
- /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
- ///
- /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
- /// to be a mirror image of its loaded state.
- ///
- /// Must be multiple of 256.
- sec_code_off: u32,
- /// Size of the secure code part.
- sec_code_size: u32,
- /// Code entry point invoked by the bootloader after code is loaded.
- code_entry_point: u32,
- /// 256B-aligned physical FB address where data is located.
- data_dma_base: u64,
- /// Size of data block (should be multiple of 256B).
- data_size: u32,
- /// Number of arguments to be passed to the target firmware being loaded.
- argc: u32,
- /// Arguments to be passed to the target firmware being loaded.
- argv: u32,
-}
-// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
-unsafe impl AsBytes for BootloaderDmemDescV2 {}
-
/// Wrapper for [`FwsecFirmware`] that includes the bootloader performing the actual load
/// operation.
pub(crate) struct FwsecFirmwareWithBl {
diff --git a/drivers/gpu/nova-core/firmware/gen_bootloader.rs b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
index f949223af2d0..f0a6c841d2aa 100644
--- a/drivers/gpu/nova-core/firmware/gen_bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/gen_bootloader.rs
@@ -13,14 +13,23 @@
Alignable,
Alignment, //
},
+ transmute::{
+ AsBytes,
+ FromBytes, //
+ },
};
use crate::{
falcon::{
self,
+ gsp::Gsp,
Falcon,
+ FalconBromParams,
FalconEngine,
- FalconPioImemLoadTarget, //
+ FalconFirmware,
+ FalconPioDmemLoadTarget,
+ FalconPioImemLoadTarget,
+ FalconPioLoadable, //
},
firmware::tlv::{
request_tlv, //
@@ -30,6 +39,57 @@
num::FromSafeCast, //
};
+/// Structure the generic bootloader reads from DMEM offset 0 to find the image it must load.
+///
+/// Mirrors Open RM's `RM_FLCN_BL_DMEM_DESC`. The driver fills one in when it loads a firmware
+/// through the bootloader, and GSP-RM sends one in a load-and-execute event.
+#[repr(C, packed)]
+#[derive(Debug, Clone)]
+pub(crate) struct BootloaderDmemDescV2 {
+ /// Reserved, should always be first element.
+ pub(crate) reserved: [u32; 4],
+ /// 16B signature for secure code, 0s if no secure code.
+ pub(crate) signature: [u32; 4],
+ /// DMA context used by the bootloader while loading code/data.
+ pub(crate) ctx_dma: u32,
+ /// 256B-aligned physical FB address where code is located.
+ pub(crate) code_dma_base: u64,
+ /// Offset from `code_dma_base` where the non-secure code is located.
+ ///
+ /// Also used as destination IMEM offset of non-secure code as the DMA firmware object is
+ /// expected to be a mirror image of its loaded state.
+ ///
+ /// Must be multiple of 256.
+ pub(crate) non_sec_code_off: u32,
+ /// Size of the non-secure code part.
+ pub(crate) non_sec_code_size: u32,
+ /// Offset from `code_dma_base` where the secure code is located (must be multiple of 256).
+ ///
+ /// Also used as destination IMEM offset of secure code as the DMA firmware object is expected
+ /// to be a mirror image of its loaded state.
+ ///
+ /// Must be multiple of 256.
+ pub(crate) sec_code_off: u32,
+ /// Size of the secure code part.
+ pub(crate) sec_code_size: u32,
+ /// Code entry point invoked by the bootloader after code is loaded.
+ pub(crate) code_entry_point: u32,
+ /// 256B-aligned physical FB address where data is located.
+ pub(crate) data_dma_base: u64,
+ /// Size of data block (should be multiple of 256B).
+ pub(crate) data_size: u32,
+ /// Number of arguments to be passed to the target firmware being loaded.
+ pub(crate) argc: u32,
+ /// Arguments to be passed to the target firmware being loaded.
+ pub(crate) argv: u32,
+}
+
+// SAFETY: This struct doesn't contain uninitialized bytes and doesn't have interior mutability.
+unsafe impl AsBytes for BootloaderDmemDescV2 {}
+
+// SAFETY: This struct only contains integer types for which all bit patterns are valid.
+unsafe impl FromBytes for BootloaderDmemDescV2 {}
+
/// The generic falcon bootloader image and the IMEM placement it was loaded for.
pub(crate) struct GenericBootloader {
/// Bootloader code, zero-padded to a whole number of falcon memory blocks.
@@ -99,4 +159,57 @@ pub(crate) fn imem_load_params(&self) -> FalconPioImemLoadTarget<'_> {
start_tag: self.start_tag,
}
}
+
+ /// Pairs this bootloader with the descriptor of the image it is to load, giving something
+ /// [`Falcon::pio_load`] accepts.
+ pub(crate) fn with_descriptor<'a>(
+ &'a self,
+ dmem_desc: &'a BootloaderDmemDescV2,
+ ) -> GenericBootloaderLoad<'a> {
+ GenericBootloaderLoad {
+ bootloader: self,
+ dmem_desc,
+ }
+ }
+}
+
+/// The generic bootloader together with the descriptor it reads from DMEM offset 0.
+pub(crate) struct GenericBootloaderLoad<'a> {
+ bootloader: &'a GenericBootloader,
+ dmem_desc: &'a BootloaderDmemDescV2,
+}
+
+impl FalconFirmware for GenericBootloaderLoad<'_> {
+ type Target = Gsp;
+
+ fn brom_params(&self) -> FalconBromParams {
+ // The bootloader is not signed. Every chipset that loads it this way uses a falcon HAL
+ // whose BROM programming is a no-op, so these values are never written to hardware.
+ FalconBromParams {
+ pkc_data_offset: 0,
+ engine_id_mask: 0,
+ ucode_id: 0,
+ }
+ }
+
+ fn boot_addr(&self) -> u32 {
+ self.bootloader.boot_addr()
+ }
+}
+
+impl FalconPioLoadable for GenericBootloaderLoad<'_> {
+ fn imem_sec_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
+ None
+ }
+
+ fn imem_ns_load_params(&self) -> Option<FalconPioImemLoadTarget<'_>> {
+ Some(self.bootloader.imem_load_params())
+ }
+
+ fn dmem_load_params(&self) -> FalconPioDmemLoadTarget<'_> {
+ FalconPioDmemLoadTarget {
+ data: self.dmem_desc.as_bytes(),
+ dst_start: 0,
+ }
+ }
}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index ae2aca5d935a..d62a5c834ccf 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -27,7 +27,13 @@
FalconMem,
FalconModSelAlgo, //
},
- firmware::gsp::GspFirmware,
+ firmware::{
+ gen_bootloader::{
+ BootloaderDmemDescV2,
+ GenericBootloader, //
+ },
+ gsp::GspFirmware,
+ },
gsp::{
cmdq::Cmdq,
commands, //
@@ -156,6 +162,101 @@ fn core_resume(
Ok(())
}
+ /// Handle a `GSP_LOAD_EXEC_GENERIC_BOOTLOADER` event.
+ ///
+ /// The driver does not copy the image the GSP asks for. It writes the descriptor the event
+ /// carries to DMEM offset 0, places the generic bootloader in IMEM, points the requested FBIF
+ /// aperture at wherever the image lives, and runs the bootloader, which does the copy from
+ /// the descriptor and jumps to the image. The aperture is restored afterwards.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the payload is shorter than the parameter block, the descriptor is not the
+ /// size this driver mirrors, or the event names a context DMA slot or an aperture that does
+ /// not exist.
+ /// - `ETIMEDOUT` if the GSP does not suspend, or the image does not halt, in time.
+ #[expect(dead_code)]
+ #[allow(clippy::too_many_arguments)]
+ fn handle_load_exec_bootloader(
+ payload: &[u8],
+ bootloader: &GenericBootloader,
+ gsp_falcon: &Falcon<'_, Gsp>,
+ sec2_falcon: &Falcon<'_, Sec2>,
+ bar: Bar0<'_>,
+ dev: &device::Device,
+ bootloader_app_version: u32,
+ libos_dma_handle: u64,
+ ) -> Result {
+ let params = LoadExecGenericBootloaderParams::from_bytes_prefix(payload)
+ .ok_or(EINVAL)?
+ .0;
+
+ let desc_size =
+ u32::try_from(core::mem::size_of::<BootloaderDmemDescV2>()).map_err(|_| EOVERFLOW)?;
+ if params.dmem_desc_size != desc_size {
+ dev_err!(
+ dev,
+ "Load-exec descriptor is {} bytes, expected {}\n",
+ params.dmem_desc_size,
+ desc_size
+ );
+ return Err(EINVAL);
+ }
+
+ let ctx_dma = params.ctx_dma()?;
+ let fbif_target = params.fbif_target()?;
+ let transcfg = || {
+ regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>()
+ .try_at(usize::from(ctx_dma))
+ .ok_or(EINVAL)
+ };
+
+ gsp_falcon.wait_for_processor_suspend().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for GSP suspend (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })?;
+
+ gsp_falcon.reset()?;
+ gsp_falcon.dma_reset();
+
+ let saved_transcfg = bar.read(transcfg()?);
+ bar.update(transcfg()?, |v| {
+ v.with_target(fbif_target)
+ .with_mem_type(FalconFbifMemType::Physical)
+ });
+
+ let run = (|| -> Result {
+ gsp_falcon.pio_load(&bootloader.with_descriptor(¶ms.dmem_desc))?;
+
+ // Also clears the suspend bit that `wait_for_processor_suspend` polls, so the next
+ // load-and-execute event does not read this one's suspension.
+ gsp_falcon.write_mailboxes(Some(FLCN_ERR_BINARY_NOT_STARTED), None);
+
+ gsp_falcon.start()?;
+ gsp_falcon.wait_till_halted().inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Timeout waiting for the loaded image to halt (mbox0={:#x})\n",
+ gsp_falcon.read_mailbox0()
+ );
+ })
+ })();
+
+ bar.update(transcfg()?, |_| saved_transcfg);
+ run?;
+
+ Self::core_resume(
+ gsp_falcon,
+ sec2_falcon,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ )
+ }
+
/// Handle a `GSP_LOAD_EXEC_HS_BINARY` event.
///
/// The GSP asks the driver to run a high-security binary that it has already placed in the
@@ -347,6 +448,64 @@ pub(crate) fn unload(
/// points it at local framebuffer.
const HS_BINARY_CTX_DMA: u8 = 0;
+/// Number of FBIF context DMA slots a falcon has.
+const NUM_CTX_DMA: usize = 8;
+
+/// Parameters for loading and executing the generic bootloader.
+///
+/// Sent by GSP-RM as the payload of `GSP_LOAD_EXEC_GENERIC_BOOTLOADER`. The descriptor carries
+/// the code and data addresses, while `addr_space` and `cpu_cache_attrib` say which FBIF aperture
+/// reaches them.
+#[repr(C)]
+struct LoadExecGenericBootloaderParams {
+ dmem_desc: BootloaderDmemDescV2,
+ dmem_desc_size: u32,
+ addr_space: u32,
+ cpu_cache_attrib: u32,
+ _reserved: [u32; 4],
+}
+
+impl LoadExecGenericBootloaderParams {
+ const ADDR_SYSMEM: u32 = 1;
+ const ADDR_FBMEM: u32 = 2;
+ const NV_MEMORY_CACHED: u32 = 0;
+ const NV_MEMORY_UNCACHED: u32 = 1;
+
+ /// Returns the context DMA slot the bootloader is to fetch the image through.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the slot is outside the FBIF `TRANSCFG` array.
+ fn ctx_dma(&self) -> Result<u8> {
+ let ctx_dma = self.dmem_desc.ctx_dma;
+
+ u8::try_from(ctx_dma)
+ .ok()
+ .filter(|slot| usize::from(*slot) < NUM_CTX_DMA)
+ .ok_or(EINVAL)
+ }
+
+ /// Returns the FBIF aperture that reaches the image.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if the address space and cache attribute pair is not one this driver maps.
+ fn fbif_target(&self) -> Result<FalconFbifTarget> {
+ match (self.addr_space, self.cpu_cache_attrib) {
+ (Self::ADDR_FBMEM, _) => Ok(FalconFbifTarget::LocalFb),
+ (Self::ADDR_SYSMEM, Self::NV_MEMORY_CACHED) => Ok(FalconFbifTarget::CoherentSysmem),
+ (Self::ADDR_SYSMEM, Self::NV_MEMORY_UNCACHED) => {
+ Ok(FalconFbifTarget::NoncoherentSysmem)
+ }
+ _ => Err(EINVAL),
+ }
+ }
+}
+
+// SAFETY: The nested descriptor is `FromBytes`, and every other field is an integer type for
+// which all bit patterns are valid.
+unsafe impl FromBytes for LoadExecGenericBootloaderParams {}
+
/// Parameters for loading and executing an HS (High-Security) binary.
///
/// Sent by GSP-RM as the payload of `GSP_LOAD_EXEC_HS_BINARY`. The firmware
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (19 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 20/27] gpu: nova-core: handle the r000 load-and-execute bootloader event John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
` (5 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP boot protocol sends its two load-and-execute events as GMC
commands, each identified by a command id.
Nova-core has a handler for each of the two events, but nothing that
reads a command id and selects between them.
Add the dispatcher, keyed on the two command ids from the r000 bindings.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 62 +++++++++++++++++++++++++++++--
drivers/gpu/nova-core/gsp/fw.rs | 10 +++++
2 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index d62a5c834ccf..15933ecc061b 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -36,7 +36,11 @@
},
gsp::{
cmdq::Cmdq,
- commands, //
+ commands,
+ fw::{
+ GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER,
+ GMCAPI_CMD_EXEC_HS_BINARY, //
+ }, //
},
regs, //
};
@@ -162,6 +166,60 @@ fn core_resume(
Ok(())
}
+ /// Dispatch one GMC boot event to its load-and-execute handler.
+ ///
+ /// LIBOS2 chipsets send `GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER` and LIBOS3 chipsets send
+ /// `GMCAPI_CMD_EXEC_HS_BINARY`, so a given GPU only ever reaches one of the two handlers.
+ ///
+ /// # Errors
+ ///
+ /// - `EINVAL` if `command_id` is not a load-and-execute command.
+ ///
+ /// Errors from the handlers are propagated as-is.
+ #[expect(dead_code)]
+ #[allow(clippy::too_many_arguments)]
+ fn dispatch_gmc_boot_event(
+ command_id: u32,
+ payload: &[u8],
+ bootloader: &GenericBootloader,
+ gsp_falcon: &Falcon<'_, Gsp>,
+ sec2_falcon: &Falcon<'_, Sec2>,
+ bar: Bar0<'_>,
+ dev: &device::Device,
+ bootloader_app_version: u32,
+ libos_dma_handle: u64,
+ ) -> Result {
+ match command_id {
+ GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER => Self::handle_load_exec_bootloader(
+ payload,
+ bootloader,
+ gsp_falcon,
+ sec2_falcon,
+ bar,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ ),
+ GMCAPI_CMD_EXEC_HS_BINARY => Self::handle_load_exec_hs_binary(
+ payload,
+ gsp_falcon,
+ sec2_falcon,
+ bar,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ ),
+ _ => {
+ dev_err!(
+ dev,
+ "Unexpected GMC boot event: command_id={:#010x}\n",
+ command_id
+ );
+ Err(EINVAL)
+ }
+ }
+ }
+
/// Handle a `GSP_LOAD_EXEC_GENERIC_BOOTLOADER` event.
///
/// The driver does not copy the image the GSP asks for. It writes the descriptor the event
@@ -175,7 +233,6 @@ fn core_resume(
/// size this driver mirrors, or the event names a context DMA slot or an aperture that does
/// not exist.
/// - `ETIMEDOUT` if the GSP does not suspend, or the image does not halt, in time.
- #[expect(dead_code)]
#[allow(clippy::too_many_arguments)]
fn handle_load_exec_bootloader(
payload: &[u8],
@@ -268,7 +325,6 @@ fn handle_load_exec_bootloader(
/// - `EINVAL` if the payload is shorter than the parameter block, or the ucode id does not
/// fit the BROM register field.
/// - `ETIMEDOUT` if the GSP does not suspend, or the binary does not halt, in time.
- #[expect(dead_code)]
#[allow(clippy::too_many_arguments)]
fn handle_load_exec_hs_binary(
payload: &[u8],
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 83f7d2042aa1..4772af362117 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -979,6 +979,16 @@ pub(crate) struct GmcApiHeader {
/// `GMCAPI_HEADER_COMMAND_ID_MASK`. The remaining byte carries flags.
const GMCAPI_COMMAND_ID_MASK: u32 = 0x00ff_ffff;
+/// GMC command asking the driver to run the generic falcon bootloader against a descriptor the
+/// GSP supplies.
+pub(crate) const GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER: u32 =
+ r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER;
+
+/// GMC command asking the driver to run a high-security binary the GSP has placed in the
+/// framebuffer.
+pub(crate) const GMCAPI_CMD_EXEC_HS_BINARY: u32 =
+ r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_HS_BINARY;
+
static_assert!(size_of::<GmcApiHeader>() == 40);
impl GmcApiHeader {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (20 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 21/27] gpu: nova-core: gsp: add the GMC boot event dispatcher John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply John Hubbard
` (4 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 GSP boot protocol replaces the separate system-info, registry
and static-info commands with one GSP_INIT request, whose payload is an
NVKV key-value blob rather than a packed struct.
Nova-core has the NVKV codec and the GSP_INIT schema, but nothing that
fills the schema in from a PCI device.
Add the builder. It encodes the PCI identifiers and config mirror range
the RPC path already sends, and the registry entries, whose names carry
the NULL that GSP-RM counts in the encoded length.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/commands.rs | 52 +++++++++++++++++++++
drivers/gpu/nova-core/gsp/fw/commands.rs | 58 +++++++++++++++++++++++-
2 files changed, 108 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index d5575c036eb9..0c3832ccf726 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -30,8 +30,16 @@
},
fw::{
self,
+ commands::{
+ GspInitRequest,
+ RegKey, //
+ },
MsgFunction, //
},
+ nvkv::{
+ Encodeable,
+ Encoder, //
+ },
},
sbuffer::SBufferIter,
vgpu::VgpuState, //
@@ -259,6 +267,50 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
}
}
+/// Registry entries the driver sends to GSP-RM on every boot, each with the NULL terminator that
+/// Open RM counts in the encoded name length.
+///
+/// `RMSecBusResetEnable` enables PCI secondary bus reset. `RMForcePcieConfigSave` makes GSP-RM
+/// preserve PCI configuration registers across any PCI reset. `RMDevidCheckIgnore` lets GSP-RM
+/// boot when the PCI device id is absent from its product name database.
+///
+/// [`SetRegistry::new`] carries the same entries for the RPC path, where the names have no
+/// terminator because that encoding appends one.
+const REGISTRY_ENTRIES: &[(&[u8], u32)] = &[
+ (b"RMSecBusResetEnable\0", 1),
+ (b"RMForcePcieConfigSave\0", 1),
+ (b"RMDevidCheckIgnore\0", 1),
+];
+
+/// Builds the NVKV-encoded payload of a `GSP_INIT` request.
+///
+/// The payload carries the system information GSP-RM reads before it starts, and
+/// [`REGISTRY_ENTRIES`] as `REGKEY_NAME` and `REGKEY_VALUE_U32` pairs. GSP-RM requires each name
+/// to be followed by its value, which is the order [`RegKey`] declares them in.
+///
+/// # Errors
+///
+/// - `ENOMEM` if the registry list or the encoder buffer cannot be allocated.
+#[expect(dead_code)]
+pub(crate) fn build_gsp_init_payload(
+ pdev: &pci::Device<device::Bound>,
+ chipset: Chipset,
+ vgpu_state: VgpuState,
+) -> Result<KVVec<u64>> {
+ let mut regkeys = KVVec::new();
+ for &(name, value) in REGISTRY_ENTRIES {
+ regkeys.push(RegKey::new(name, value), GFP_KERNEL)?;
+ }
+ if matches!(vgpu_state, VgpuState::Enabled { .. }) {
+ regkeys.push(RegKey::new(b"RMSetSriovMode\0", 1), GFP_KERNEL)?;
+ }
+
+ let mut encoder = Encoder::new();
+ GspInitRequest::new(pdev, chipset, regkeys).encode(&mut encoder)?;
+
+ Ok(encoder.finish())
+}
+
pub(crate) use fw::commands::PowerStateLevel;
/// The `UnloadingGuestDriver` command, used to shut down the GSP.
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 5cd2d9646de3..bfd756813c64 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -245,6 +245,25 @@ pub(crate) enum OorArch {
Riscv64 = 5,
}
+impl OorArch {
+ /// Returns the variant naming the architecture this kernel is built for.
+ fn host() -> Self {
+ if cfg!(target_arch = "x86_64") {
+ Self::X86_64
+ } else if cfg!(target_arch = "aarch64") {
+ Self::Aarch64
+ } else if cfg!(target_arch = "powerpc64") {
+ Self::Ppc64le
+ } else if cfg!(target_arch = "arm") {
+ Self::Arm
+ } else if cfg!(target_arch = "riscv64") {
+ Self::Riscv64
+ } else {
+ Self::None
+ }
+ }
+}
+
// TODO[FPRI]: This is a temporary solution to be replaced with the corresponding derive macros once
// they land.
impl TryFrom<u32> for OorArch {
@@ -271,7 +290,7 @@ fn from(value: OorArch) -> Self {
nvkv_encode! {
/// A GSP registry entry.
- struct RegKey {
+ pub(crate) struct RegKey {
key_name: Key<&'static [u8], { Self::REGKEY_NAME_KEY }>,
key_value: Key<u32, { Self::REGKEY_VALUE_U32_KEY }>,
}
@@ -280,6 +299,15 @@ struct RegKey {
impl RegKey {
const REGKEY_NAME_KEY: KeyId = 0x3070;
const REGKEY_VALUE_U32_KEY: KeyId = 0x3071;
+
+ /// Creates a registry entry. `key_name` must include its NULL terminator, which GSP-RM counts
+ /// in the encoded name length.
+ pub(crate) fn new(key_name: &'static [u8], key_value: u32) -> Self {
+ Self {
+ key_name: key_name.into(),
+ key_value: key_value.into(),
+ }
+ }
}
impl Encodeable for KVVec<RegKey> {
@@ -317,7 +345,7 @@ impl VfInfo {
nvkv_encode! {
/// Payload of the `GSP_INIT` command.
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
- struct GspInitRequest {
+ pub(crate) struct GspInitRequest {
pci_device_id: Key<u32, { Self::PCI_DEVICE_ID_KEY }>,
pci_sub_device_id: Key<u32, { Self::PCI_SUBDEVICE_ID_KEY }>,
pci_revision_id: Key<u32, { Self::PCI_REVISION_ID_KEY }>,
@@ -339,6 +367,32 @@ impl GspInitRequest {
const PCI_CONFIG_MIRROR_SIZE_KEY: KeyId = 0x0011;
const OOR_ARCH_KEY: KeyId = 0x0070;
const NV_DOMAIN_BUS_DEVICE_FUNC_KEY: KeyId = 0x1020;
+
+ /// Describes `dev` to GSP-RM and asks it to apply `regkeys`.
+ ///
+ /// The same identifiers reach GSP-RM through [`GspSetSystemInfo::init`] on the RPC path.
+ pub(crate) fn new(
+ dev: &pci::Device<device::Bound>,
+ chipset: Chipset,
+ regkeys: KVVec<RegKey>,
+ ) -> Self {
+ let mirror = chipset.pci_config_mirror_range();
+ let device_id = (u32::from(dev.device_id()) << 16) | u32::from(dev.vendor_id().as_raw());
+ let sub_device_id =
+ (u32::from(dev.subsystem_device_id()) << 16) | u32::from(dev.subsystem_vendor_id());
+
+ Self {
+ pci_device_id: device_id.into(),
+ pci_sub_device_id: sub_device_id.into(),
+ pci_revision_id: u32::from(dev.revision_id()).into(),
+ pci_config_mirror_base: mirror.start.into(),
+ pci_config_mirror_size: (mirror.end - mirror.start).into(),
+ oor_arch: OorArch::host().into(),
+ bus_device_func: u64::from(dev.dev_id()).into(),
+ regkeys,
+ vf_info: None,
+ }
+ }
}
// Decode:
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (21 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 22/27] gpu: nova-core: gsp: add the GSP_INIT request builder John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
` (3 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
GSP-RM answers a GSP_INIT request with the static GPU configuration, and
that reply is also what signals it has finished starting. It raises
load-and-execute events in the meantime, so a caller must handle them
rather than wait through them.
Nova-core can build the request but cannot send it: the GMC sender is
private, and the receive path drops the field carrying the reply status.
Add the sender and the receive-side status field it needs, and decode
the reply into the static-info type the RPC path already produces.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp/cmdq.rs | 49 ++++++--
drivers/gpu/nova-core/gsp/commands.rs | 140 ++++++++++++++++++++++-
drivers/gpu/nova-core/gsp/fw.rs | 4 +
drivers/gpu/nova-core/gsp/fw/commands.rs | 35 +++++-
4 files changed, 213 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 7da61bc9ad92..82ff46620911 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -790,22 +790,43 @@ fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
self.inner.lock().receive_msg(timeout, None)
}
- /// Receives one GMC event from the GSP and passes its command id and raw payload slices to
- /// `handler`.
+ /// Receives one GMC element from the GSP and passes its command id, the `max_resp_or_status`
+ /// field, and the raw payload slices to `handler`.
///
/// This method may sleep while waiting. The [`CmdqInner`] mutex stays locked across the wait
/// and across the `handler` call, so `handler` must not call back into this [`Cmdq`].
///
/// See [`CmdqInner::receive_gmc_and_dispatch`] for return values, queue state, and errors.
- #[expect(dead_code)]
pub(crate) fn receive_gmc_and_dispatch<R>(
&self,
timeout: Delta,
- handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+ handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
) -> Result<Option<R>> {
self.inner.lock().receive_gmc_and_dispatch(timeout, handler)
}
+ /// Sends a GMC API command to the GSP without waiting for its response.
+ ///
+ /// A caller that expects a response reads it with [`Self::receive_gmc_and_dispatch`], which
+ /// lets it handle the events GSP-RM interleaves before the response arrives.
+ ///
+ /// # Errors
+ ///
+ /// - `EMSGSIZE` if the command exceeds the maximum queue element size.
+ /// - `ETIMEDOUT` if space does not become available within the timeout.
+ /// - `EIO` if the command header is not properly aligned.
+ pub(crate) fn send_gmc_no_wait(
+ &self,
+ bar: Bar0<'_>,
+ command_id: u32,
+ payload: &[u8],
+ max_response_size: u32,
+ ) -> Result {
+ self.inner
+ .lock()
+ .send_gmc(bar, command_id, payload, max_response_size)
+ }
+
/// Waits for an unsolicited GSP event of type `M`, dispatching any other event that arrives
/// first.
///
@@ -1005,7 +1026,6 @@ fn send_command<M>(&mut self, bar: Bar0<'_>, command: M) -> Result<u32>
/// - `EMSGSIZE` if the command exceeds the maximum queue element size.
/// - `ETIMEDOUT` if space does not become available within the timeout.
/// - `EIO` if the command header is not properly aligned.
- #[expect(dead_code)]
fn send_gmc(
&mut self,
bar: Bar0<'_>,
@@ -1383,9 +1403,13 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
/// Receive the next GMC event from the GSP and dispatch it through a handler.
///
- /// The handler receives the GMC command id and the raw payload slices that follow the
- /// [`super::fw::GmcApiHeader`] (two slices because the circular buffer may wrap). It returns
- /// `None` for an event it does not handle.
+ /// The handler receives the GMC command id, the header's `max_resp_or_status` field, and the
+ /// raw payload slices that follow the [`super::fw::GmcApiHeader`] (two slices because the
+ /// circular buffer may wrap). It returns `None` for an element it does not handle.
+ ///
+ /// `max_resp_or_status` is a union: GSP-RM writes an `NV_STATUS` there when the element is a
+ /// response, and the maximum response size when it is a request. Only a handler that knows
+ /// which one it asked for can read it.
///
/// Where [`Self::receive_msg`] keys on [`MsgFunction`], this keys on the GMC command id,
/// which is the form the r000 firmware uses for boot events.
@@ -1401,7 +1425,7 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
fn receive_gmc_and_dispatch<R>(
&mut self,
timeout: Delta,
- handler: impl FnOnce(u32, &[u8], &[u8]) -> Option<R>,
+ handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
) -> Result<Option<R>> {
let message = self.wait_for_gmc_msg(timeout)?;
let header = message.header;
@@ -1420,7 +1444,12 @@ fn receive_gmc_and_dispatch<R>(
length,
);
- handler(command_id, message.contents.0, message.contents.1)
+ handler(
+ command_id,
+ header.gmc.max_resp_or_status,
+ message.contents.0,
+ message.contents.1,
+ )
} else {
dev_warn!(&self.dev, "GSP GMC: dropping non-GMC queue element\n");
None
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 0c3832ccf726..d55faf1a4e04 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -20,6 +20,7 @@
};
use crate::{
+ driver::Bar0,
gpu::Chipset,
gsp::{
cmdq::{
@@ -32,13 +33,18 @@
self,
commands::{
GspInitRequest,
+ GspInitResponse,
+ GspInitResponseSchema,
RegKey, //
},
- MsgFunction, //
+ MsgFunction,
+ GMCAPI_CMD_GSP_INIT, //
},
nvkv::{
+ Decoder,
Encodeable,
- Encoder, //
+ Encoder,
+ UnknownKeyPolicy, //
},
},
sbuffer::SBufferIter,
@@ -311,6 +317,136 @@ pub(crate) fn build_gsp_init_payload(
Ok(encoder.finish())
}
+/// Size of the buffer GSP-RM may fill with static configuration, matching the allocation Open RM
+/// makes in `kgspSendInitRpcs`.
+const GSP_INIT_MAX_RESPONSE_SIZE: u32 = 48 * 1024;
+
+/// Sends `GSP_INIT` and returns the static configuration its reply carries.
+///
+/// GSP-RM interleaves load-and-execute events between the request and the reply, and those events
+/// drive the falcon loads that let it finish starting, so each one is passed to `on_boot_event`
+/// rather than skipped. The reply arrives only once GSP-RM is up, which is what makes it the
+/// signal that boot is complete.
+///
+/// `payload` is the blob from [`build_gsp_init_payload`].
+///
+/// # Errors
+///
+/// - `EIO` if GSP-RM reports a failure status, or if the reply is not a whole number of NVKV
+/// words.
+/// - `ETIMEDOUT` if neither the reply nor another element arrives within
+/// [`Cmdq::RECEIVE_TIMEOUT`].
+///
+/// Errors from `on_boot_event` and from decoding the reply are propagated as-is.
+#[expect(dead_code)]
+pub(crate) fn gsp_init(
+ cmdq: &Cmdq,
+ bar: Bar0<'_>,
+ payload: &[u64],
+ mut on_boot_event: impl FnMut(u32, &[u8]) -> Result,
+) -> Result<GetGspStaticInfoReply> {
+ // Qualified because `zerocopy::IntoBytes` also gives `[T]` an `as_bytes`.
+ let payload = AsBytes::as_bytes(payload);
+
+ cmdq.send_gmc_no_wait(
+ bar,
+ GMCAPI_CMD_GSP_INIT,
+ payload,
+ GSP_INIT_MAX_RESPONSE_SIZE,
+ )?;
+
+ loop {
+ let reply = cmdq.receive_gmc_and_dispatch(
+ Cmdq::RECEIVE_TIMEOUT,
+ |command_id, max_resp_or_status, payload_0, payload_1| {
+ if command_id == GMCAPI_CMD_GSP_INIT {
+ Some(decode_gsp_init_reply(
+ max_resp_or_status,
+ payload_0,
+ payload_1,
+ ))
+ } else {
+ // A boot event. Keep waiting for the reply unless handling it failed.
+ match on_boot_event(command_id, payload_0) {
+ Ok(()) => None,
+ Err(e) => Some(Err(e)),
+ }
+ }
+ },
+ )?;
+
+ if let Some(reply) = reply {
+ return reply;
+ }
+ }
+}
+
+/// Decodes the `GSP_INIT` reply, whose `max_resp_or_status` field carries an `NV_STATUS`.
+fn decode_gsp_init_reply(
+ status: u32,
+ payload_0: &[u8],
+ payload_1: &[u8],
+) -> Result<GetGspStaticInfoReply> {
+ if status != 0 {
+ return Err(EIO);
+ }
+
+ decode_gsp_info(&nvkv_words(payload_0, payload_1)?)
+}
+
+/// Joins the two halves of a wrapped payload into the `u64` words an NVKV stream is made of.
+///
+/// # Errors
+///
+/// - `EIO` if the combined length is not a whole number of words.
+/// - `ENOMEM` if the buffer cannot be allocated.
+fn nvkv_words(payload_0: &[u8], payload_1: &[u8]) -> Result<KVVec<u64>> {
+ let bytes = SBufferIter::new_reader([payload_0, payload_1]).flush_into_kvec(GFP_KERNEL)?;
+ let words = bytes.chunks_exact(size_of::<u64>());
+ if !words.remainder().is_empty() {
+ return Err(EIO);
+ }
+
+ let mut out = KVVec::with_capacity(bytes.len() / size_of::<u64>(), GFP_KERNEL)?;
+ for word in words {
+ let word: [u8; size_of::<u64>()] = word.try_into().map_err(|_| EIO)?;
+ out.push(u64::from_le_bytes(word), GFP_KERNEL)?;
+ }
+
+ Ok(out)
+}
+
+/// Decodes the static GPU configuration from an NVKV stream.
+///
+/// # Errors
+///
+/// - `EINVAL` if the stream is malformed or omits a required key.
+/// - `ENOMEM` if the decoded regions cannot be allocated.
+fn decode_gsp_info(words: &[u64]) -> Result<GetGspStaticInfoReply> {
+ let decoder = Decoder::new(words, UnknownKeyPolicy::Ignore);
+ let decoded = KBox::try_init(
+ decoder.decode(GspInitResponseSchema::default())?,
+ GFP_KERNEL,
+ )?;
+
+ let mut gpu_name = [0u8; GspInitResponse::MAX_GPU_NAME_LEN];
+ let name = decoded.gpu_name();
+ gpu_name
+ .get_mut(..name.len())
+ .ok_or(EINVAL)?
+ .copy_from_slice(name);
+
+ let mut usable_fb_regions = KVec::new();
+ for region in decoded.usable_fb_regions() {
+ usable_fb_regions.push(region, GFP_KERNEL)?;
+ }
+
+ Ok(GetGspStaticInfoReply {
+ gpu_name,
+ usable_fb_regions,
+ })
+}
+
pub(crate) use fw::commands::PowerStateLevel;
/// The `UnloadingGuestDriver` command, used to shut down the GSP.
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 4772af362117..6958ee3a2e4e 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -979,6 +979,10 @@ pub(crate) struct GmcApiHeader {
/// `GMCAPI_HEADER_COMMAND_ID_MASK`. The remaining byte carries flags.
const GMCAPI_COMMAND_ID_MASK: u32 = 0x00ff_ffff;
+/// GMC command that hands GSP-RM its system information and registry keys and returns the static
+/// GPU configuration. Its reply is also what signals that GSP-RM has finished starting.
+pub(crate) const GMCAPI_CMD_GSP_INIT: u32 = r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_GSP_INIT;
+
/// GMC command asking the driver to run the generic falcon bootloader against a descriptor the
/// GSP supplies.
pub(crate) const GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER: u32 =
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index bfd756813c64..00e40a435053 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -402,7 +402,7 @@ pub(crate) fn new(
/// Schema for the `GSP_INIT` response.
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
#[derive(Default)]
- struct GspInitResponseSchema => GspInitResponse {
+ pub(crate) struct GspInitResponseSchema => GspInitResponse {
gpu_name:
Array<u8, { GspInitResponse::MAX_GPU_NAME_LEN }, { Self::GPU_NAME_STRING_KEY }>,
fb_regions: Accumulated<FbRegionSchema>,
@@ -420,7 +420,7 @@ impl GspInitResponseSchema {
/// Payload of the `GSP_INIT` response.
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
-struct GspInitResponse {
+pub(crate) struct GspInitResponse {
gpu_name: ArrayVec<u8, { Self::MAX_GPU_NAME_LEN }>,
fb_regions: KVVec<FbRegion>,
bar1_pde_base: u64,
@@ -428,7 +428,36 @@ struct GspInitResponse {
}
impl GspInitResponse {
- const MAX_GPU_NAME_LEN: usize = 64;
+ pub(crate) const MAX_GPU_NAME_LEN: usize = 64;
+
+ /// A region with no tag is general-purpose memory. A tagged region is reserved for a
+ /// firmware-internal use that the tag identifies.
+ const FB_REGION_TAG_NONE: u32 = 0;
+
+ /// Returns the GPU name, which GSP-RM sends with its NULL terminator.
+ pub(crate) fn gpu_name(&self) -> &[u8] {
+ self.gpu_name.as_slice()
+ }
+
+ /// Iterates over the FB regions the driver may allocate from.
+ ///
+ /// A region qualifies when it is untagged, unprotected, and supports both compression and
+ /// isochronous access, which is the same set the RPC path selects from
+ /// [`GspStaticConfigInfo::usable_fb_regions`].
+ pub(crate) fn usable_fb_regions(&self) -> impl Iterator<Item = Range<u64>> + '_ {
+ self.fb_regions.iter().filter_map(|region| {
+ if region.limit >= region.base
+ && region.tag == Self::FB_REGION_TAG_NONE
+ && !region.flags.protected()
+ && region.flags.support_compressed()
+ && region.flags.support_iso()
+ {
+ region.limit.checked_add(1).map(|end| region.base..end)
+ } else {
+ None
+ }
+ })
+ }
}
nvkv_decode! {
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (22 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 23/27] gpu: nova-core: gsp: send GSP_INIT and decode its reply John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware John Hubbard
` (2 subsequent siblings)
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The libos loader finds each log buffer by looking up its eight-byte name
in the init argument array, so a buffer the array does not name is never
written to.
Nova-core allocates six log buffers and gives all six a debugfs file,
but names only three, so the other three can never read back anything
but zeros.
Name all six. RMARGS moves to the end of the array, which does not
matter to a loader that searches by name.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 232905638169..f1bd66024b72 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -355,7 +355,19 @@ pub(crate) fn new(
2,
LibosMemoryRegionInitArgument::new("LOGRM", &logrm.buffer),
)?;
- libos.init_at(3, LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
+ libos.init_at(
+ 3,
+ LibosMemoryRegionInitArgument::new("LOGMNOC", &logmnoc.buffer),
+ )?;
+ libos.init_at(
+ 4,
+ LibosMemoryRegionInitArgument::new("LOGROOT", &logroot.buffer),
+ )?;
+ libos.init_at(
+ 5,
+ LibosMemoryRegionInitArgument::new("LOGRMON", &logrmon.buffer),
+ )?;
+ libos.init_at(6, LibosMemoryRegionInitArgument::new("RMARGS", rmargs))?;
libos.into()
},
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (23 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 24/27] gpu: nova-core: gsp: pass the remaining log buffers to GSP-RM John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs John Hubbard
2026-08-19 3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings John Hubbard
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 firmware changes four things about how it talks to the driver.
Queue elements carry MCTP and NVDM headers and no checksum. Msgq version
2 keeps all four ring pointers in BAR0, and its head-register write
doubles as the doorbell. Load-and-execute steps arrive as GMC commands
rather than as RPC events. And a single GSP_INIT request replaces the
system-info, registry and static-info commands, with its reply standing
in for the init-done event.
None of the four can change on its own, because only one firmware
version is present at a time, so a commit that changed only some of them
would not boot at all.
Switch all four together. The startup arguments gain the ucodes image
and the state-monitor buffer, and the FMC boot parameters gain the magic
and size header that r000 expects. A missing ucodes image is now fatal,
which is what Open RM requires everywhere except GB10Y. Only Turing and
GA100 raise the generic-bootloader event, so the bootloader image is
requested only on those chipsets.
Each load-and-execute event ends in a GSP falcon reset, which zeroes all
four msgq counters. GSP-RM writes back the two counters it owns and
carries on counting from where it stopped. Write the driver's two
counters back the same way, counting the element just handled as
consumed.
Name the r000 module in the bindings alias, so that every constant comes
from the r000 headers rather than the r570 ones. The r570 headers size
the GSP heap at 14 MiB of RM boot working memory on Hopper and later,
and the r000 headers size it at 18 MiB.
Msgq version 2 holds the read pointer in BAR0, so the r000 headers
define no RX header. Remove the driver's copy and the space it occupied.
No offset changes, because the message data is page-aligned and already
sits at 4096.
The r000 firmware also sizes two framebuffer carveouts differently, and
the driver reserves them. Its PMU region above FRTS covers the backing
store, the communication surfaces and the misc memory, which come to
25 MiB + 384 KiB, and GB20x asks for a flat 3 MiB of non-WPR heap. Both
are larger than the r570 amounts, so carrying those forward
under-reserves memory the firmware expects to own.
The boot events and the GSP_INIT reply replace the CPU sequencer and the
wait for init-done, so neither has a caller left. Remove both, and the
run-CPU-sequencer entry in the message-function table with them, so that
code arriving on the queue is reported as unknown rather than decoded
into types nothing acts on.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/driver.rs | 2 +-
drivers/gpu/nova-core/fb/hal/gb100.rs | 25 +-
drivers/gpu/nova-core/fb/hal/gb202.rs | 5 +-
drivers/gpu/nova-core/firmware/bindata.rs | 1 -
drivers/gpu/nova-core/gpu.rs | 21 +-
drivers/gpu/nova-core/gsp.rs | 34 +-
drivers/gpu/nova-core/gsp/boot.rs | 139 ++++--
drivers/gpu/nova-core/gsp/cmdq.rs | 365 +++++---------
drivers/gpu/nova-core/gsp/commands.rs | 68 +--
drivers/gpu/nova-core/gsp/fw.rs | 564 ++++++----------------
drivers/gpu/nova-core/gsp/hal.rs | 22 +-
drivers/gpu/nova-core/gsp/hal/tu102.rs | 12 -
drivers/gpu/nova-core/gsp/sequencer.rs | 379 ---------------
drivers/gpu/nova-core/irq/gsp.rs | 2 +-
drivers/gpu/nova-core/mctp.rs | 2 +
15 files changed, 456 insertions(+), 1185 deletions(-)
delete mode 100644 drivers/gpu/nova-core/gsp/sequencer.rs
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 21d7df0744ed..eb657cc0062e 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -135,7 +135,7 @@ fn probe<'bound>(
// SAFETY: as for the `bar` borrow above.
let bar = unsafe { &*core::ptr::from_ref(bar) };
crate::irq::gsp::enable(bar, gpu.chipset(), vectors_ref.irq_type());
- gpu.cmdq().drain()?;
+ gpu.cmdq().drain(bar)?;
},
_reg: auxiliary::Registration::new(
pdev.as_ref(),
diff --git a/drivers/gpu/nova-core/fb/hal/gb100.rs b/drivers/gpu/nova-core/fb/hal/gb100.rs
index d9e4d62ae632..2e2beb60cf3b 100644
--- a/drivers/gpu/nova-core/fb/hal/gb100.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb100.rs
@@ -80,10 +80,27 @@ fn write_sysmem_flush_page_gb100(bar: Bar0<'_>, addr: Bounded<u64, 52>) {
);
}
-// This PMU reservation size is r570-specific.
+/// PMU backing store (see Open RM: `kpmuReservedMemoryBackingStoreSizeGet`).
+const PMU_BACKING_STORE_SIZE: usize = 9 * SZ_1M;
+
+/// PMU communication surfaces (see Open RM: `gpuGetPmuReservedMemorySurfacesSize`).
+const PMU_SURFACES_SIZE: usize = SZ_16M + SZ_256K;
+
+/// Miscellaneous PMU memory (see Open RM: `kpmuReservedMemoryMiscSizeGet`).
+const PMU_MISC_SIZE: usize = SZ_4K;
+
+// Open RM reserves the backing store, the surfaces and the misc memory as one region above
+// FRTS, aligned to KPMU_RESERVED_MEMORY_ALIGNMENT (see kpmuReservedMemorySizeGet).
pub(super) const fn pmu_reserved_size_gb100() -> u32 {
- usize_into_u32::<{ const_align_up(SZ_8M + SZ_16M + SZ_4K, Alignment::new::<SZ_128K>()).unwrap() }>(
- )
+ usize_into_u32::<
+ {
+ const_align_up(
+ PMU_BACKING_STORE_SIZE + PMU_SURFACES_SIZE + PMU_MISC_SIZE,
+ Alignment::new::<SZ_128K>(),
+ )
+ .unwrap()
+ },
+ >()
}
impl FbHal for Gb100 {
@@ -112,7 +129,7 @@ fn pmu_reserved_size(&self) -> u32 {
}
fn non_wpr_heap_size(&self) -> u64 {
- // Non-WPR heap for GB10x (see Open RM: kgspGetNonWprHeapSize, GB100/GB102).
+ // Open RM's kgspGetNonWprHeapSize returns a flat 2 MiB for GB10x.
u64::SZ_2M
}
diff --git a/drivers/gpu/nova-core/fb/hal/gb202.rs b/drivers/gpu/nova-core/fb/hal/gb202.rs
index 4341ecf36188..de063e518b50 100644
--- a/drivers/gpu/nova-core/fb/hal/gb202.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb202.rs
@@ -72,9 +72,8 @@ fn pmu_reserved_size(&self) -> u32 {
}
fn non_wpr_heap_size(&self) -> u64 {
- // Non-WPR heap for GB20x (see Open RM: kgspGetNonWprHeapSize, GB202+).
- // This size is r570-specific.
- u64::SZ_2M + u64::SZ_128K
+ // Open RM's kgspGetNonWprHeapSize returns a flat 3 MiB for GB20x.
+ 3 * u64::SZ_1M
}
fn frts_size(&self) -> u64 {
diff --git a/drivers/gpu/nova-core/firmware/bindata.rs b/drivers/gpu/nova-core/firmware/bindata.rs
index d9625ab7d738..c303518b25b1 100644
--- a/drivers/gpu/nova-core/firmware/bindata.rs
+++ b/drivers/gpu/nova-core/firmware/bindata.rs
@@ -21,7 +21,6 @@
/// A missing metadata file is reported as [`None`] so that the eventual caller can decide whether
/// ucodes are optional for its boot path. Once the metadata has been found, all parse and payload
/// loading errors, including a missing referenced file, are returned as errors.
-#[expect(dead_code)]
pub(crate) fn request_ucodes_firmware(
dev: &device::Device,
chipset: Chipset,
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index c0ba561a3bf1..a9b91db9fd27 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -27,7 +27,6 @@
gsp::{
self,
cmdq::Cmdq,
- commands::GetGspStaticInfoReply,
Gsp,
GspBootContext, //
},
@@ -276,16 +275,14 @@ struct GspResources<'gpu> {
/// GSP runtime data.
#[pin]
gsp: Gsp,
- /// GSP unload firmware bundle, if any.
- unload_bundle: Option<gsp::UnloadBundle>,
+ /// What the GSP boot sequence produced, including the unload bundle.
+ boot_result: gsp::BootResult,
}
/// Structure holding the resources required to operate the GPU.
#[pin_data]
pub(crate) struct Gpu<'gpu> {
spec: Spec,
- /// Static GPU information as provided by the GSP.
- gsp_static_info: GetGspStaticInfoReply,
/// GSP and its resources.
#[pin]
gsp_resources: GspResources<'gpu>,
@@ -303,7 +300,7 @@ fn drop(self: Pin<&mut Self>) {
let this = self.project();
let device = *this.device;
let bar = *this.bar;
- let bundle = this.unload_bundle.take();
+ let bundle = this.boot_result.take_unload_bundle();
let _ = this
.gsp
@@ -398,10 +395,10 @@ pub(crate) fn new(
gsp <- Gsp::new(pdev, spec.chipset),
- // This member must be initialized last, so the `UnloadBundle` can never be dropped
+ // This member must be initialized last, so the unload bundle can never be dropped
// from outside of the constructed `GspResources`, ensuring that the unload sequence
// is properly run in case of failure.
- unload_bundle: gsp.boot(GspBootContext {
+ boot_result: gsp.boot(GspBootContext {
pdev,
bar,
chipset: spec.chipset,
@@ -412,9 +409,9 @@ pub(crate) fn new(
})?,
}),
- gsp_static_info: {
- // Obtain and display basic GPU information.
- let info = gsp_resources.gsp.get_static_info(bar)?;
+ _: {
+ // The `GSP_INIT` reply already carried this.
+ let info = &gsp_resources.boot_result.static_info;
match info.gpu_name() {
Ok(name) => dev_info!(dev, "GPU name: {}\n", name),
Err(e) => dev_warn!(dev, "GPU name unavailable: {:?}\n", e),
@@ -434,8 +431,6 @@ pub(crate) fn new(
/ u64::SZ_1M
);
}
-
- info
}
})
}
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index f1bd66024b72..8e912eaf8e2e 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -30,7 +30,6 @@
#[cfg_attr(not(CONFIG_KUNIT), allow(dead_code))]
mod nvkv;
mod regs;
-mod sequencer;
pub(crate) use fw::{
GspFmcBootParams,
@@ -334,8 +333,12 @@ pub(crate) fn new(
Ok(try_pin_init!(Self {
gsp_tlv,
cmdq: Arc::pin_init(Cmdq::new(dev), GFP_KERNEL)?,
- rmargs: Coherent::init(dev, GFP_KERNEL, GspArgumentsPadded::new(cmdq.as_ref()))?,
rm_state_monitor: Coherent::zeroed(dev, GFP_KERNEL)?,
+ rmargs: Coherent::init(
+ dev,
+ GFP_KERNEL,
+ GspArgumentsPadded::new(cmdq.as_ref(), None, rm_state_monitor),
+ )?,
libos: {
let mut libos = CoherentBox::zeroed_slice(
dev,
@@ -405,6 +408,9 @@ pub(crate) fn new(
}
/// Query the GSP for the static GPU information.
+ ///
+ /// The r000 boot path gets the same information from the `GSP_INIT` reply instead.
+ #[expect(dead_code)]
pub(crate) fn get_static_info(&self, bar: Bar0<'_>) -> Result<commands::GetGspStaticInfoReply> {
self.cmdq.send_command(bar, commands::GetGspStaticInfo)
}
@@ -417,3 +423,27 @@ pub(crate) fn cmdq(&self) -> Arc<Cmdq> {
/// Opaque bundle required to unload the GSP. Created by [`Gsp::boot`], consumed by [`Gsp::unload`].
pub(crate) struct UnloadBundle(KBox<dyn hal::UnloadBundle>);
+
+/// What a successful [`Gsp::boot`] leaves the caller.
+pub(crate) struct BootResult {
+ unload_bundle: Option<UnloadBundle>,
+ /// Static GPU configuration, as reported in the `GSP_INIT` reply.
+ pub(crate) static_info: commands::GetGspStaticInfoReply,
+}
+
+impl BootResult {
+ pub(super) fn new(
+ unload_bundle: Option<UnloadBundle>,
+ static_info: commands::GetGspStaticInfoReply,
+ ) -> Self {
+ Self {
+ unload_bundle,
+ static_info,
+ }
+ }
+
+ /// Takes the unload bundle out, leaving none behind, for the teardown path.
+ pub(crate) fn take_unload_bundle(&mut self) -> Option<UnloadBundle> {
+ self.unload_bundle.take()
+ }
+}
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 15933ecc061b..3e9a6a539cae 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -28,20 +28,28 @@
FalconModSelAlgo, //
},
firmware::{
+ bindata::request_ucodes_firmware,
gen_bootloader::{
BootloaderDmemDescV2,
GenericBootloader, //
},
gsp::GspFirmware,
+ radix3::Radix3, //
},
gsp::{
- cmdq::Cmdq,
+ cmdq::{
+ Cmdq,
+ QueuePointers, //
+ },
commands,
fw::{
+ BindataArgs,
+ GspArgumentsPadded,
GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER,
GMCAPI_CMD_EXEC_HS_BINARY, //
}, //
},
+ num,
regs, //
};
@@ -52,12 +60,17 @@ impl super::Gsp {
/// user-space, patching them with signatures, and building firmware-specific intricate data
/// structures that the GSP will use at runtime.
///
- /// Upon return, the GSP is up and running, and its unload bundle (to be given as argument to
- /// [`Self::unload`]) returned.
+ /// Upon return, the GSP is up and running, and the static configuration it reported plus its
+ /// unload bundle (to be given as argument to [`Self::unload`]) are returned.
+ ///
+ /// # Errors
+ ///
+ /// - `ENOENT` if the ucodes firmware image is absent. GSP-RM requires it on every chipset
+ /// this driver supports.
pub(crate) fn boot(
self: Pin<&mut Self>,
mut ctx: super::GspBootContext<'_, '_>,
- ) -> Result<Option<super::UnloadBundle>> {
+ ) -> Result<super::BootResult> {
let pdev = ctx.pdev;
let bar = ctx.bar;
let chipset = ctx.chipset;
@@ -67,6 +80,19 @@ pub(crate) fn boot(
let gsp_fw = KBox::pin_init(GspFirmware::new(dev, chipset, &self.gsp_tlv), GFP_KERNEL)?;
+ // GSP-RM reads the ucodes image through a radix3 page table, so the mapping has to
+ // outlive initialization.
+ let ucodes = request_ucodes_firmware(dev, chipset)?.ok_or(ENOENT)?;
+ let ucodes_size = ucodes.len();
+ let ucodes_radix3 = KBox::pin_init(Radix3::new(dev, ucodes), GFP_KERNEL)?;
+ GspArgumentsPadded::set_bindata(
+ &self.rmargs,
+ Some(&BindataArgs {
+ radix3: ucodes_radix3.dma_address(),
+ size: num::usize_as_u64(ucodes_size),
+ }),
+ );
+
// Perform the chipset-specific boot sequence, and retrieve the unload bundle.
let unload_bundle = hal.boot(&self, &mut ctx, &gsp_fw)?.or_else(|| {
dev_warn!(dev, "The GSP won't be able to unload properly on unbind.\n");
@@ -96,17 +122,41 @@ pub(crate) fn boot(
dev_dbg!(pdev, "RISC-V active? {}\n", gsp_falcon.is_riscv_active(),);
- self.cmdq
- .send_command_no_wait(bar, commands::SetSystemInfo::new(pdev, chipset))?;
- self.cmdq
- .send_command_no_wait(bar, commands::SetRegistry::new(ctx.vgpu.state())?)?;
-
- hal.post_boot(&self, ctx, &gsp_fw)?;
-
- // Wait until GSP is fully initialized.
- commands::wait_gsp_init_done(&self.cmdq)?;
+ // GSP-RM discards any RPC that reaches it before GSP_INIT, so the system information and
+ // the registry keys ride inside that one request. Its reply is also what says GSP-RM has
+ // finished starting, and the load-and-execute events it raises first are dispatched as
+ // they arrive.
+ let init_payload = commands::build_gsp_init_payload(pdev, chipset, ctx.vgpu.state())?;
+ // Only the chipsets that raise `GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER` are shipped a
+ // `gen_bootloader.tlv`, so requesting it elsewhere fails the whole boot with `ENOENT`.
+ let bootloader = if super::hal::uses_generic_bootloader(chipset) {
+ Some(GenericBootloader::new(dev, chipset, gsp_falcon)?)
+ } else {
+ None
+ };
+ let bootloader_app_version = gsp_fw.bootloader.app_version;
+ let libos_dma_handle = self.libos.dma_address();
+ let sec2_falcon = ctx.sec2_falcon;
+
+ let static_info =
+ commands::gsp_init(&self.cmdq, bar, &init_payload, |command_id, payload| {
+ Self::dispatch_gmc_boot_event(
+ command_id,
+ payload,
+ bootloader.as_ref(),
+ gsp_falcon,
+ sec2_falcon,
+ bar,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ )
+ })?;
- Ok(unload_guard.dismiss().1)
+ Ok(super::BootResult::new(
+ unload_guard.dismiss().1,
+ static_info,
+ ))
}
/// Restart GSP-RM once a load-and-execute image has run to completion.
@@ -115,6 +165,8 @@ pub(crate) fn boot(
/// mailboxes, and starts SEC2, which is what brings GSP-RM back up. Open RM calls this
/// `kgspExecuteCoreResume`.
///
+ /// The falcon reset zeroes the four msgq v2 pointer registers.
+ ///
/// # Errors
///
/// - `EIO` if SEC2 reports a failure, or if the GSP is not running RISC-V afterwards.
@@ -171,35 +223,48 @@ fn core_resume(
/// LIBOS2 chipsets send `GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER` and LIBOS3 chipsets send
/// `GMCAPI_CMD_EXEC_HS_BINARY`, so a given GPU only ever reaches one of the two handlers.
///
+ /// Both handlers restart GSP-RM, so a successful dispatch returns
+ /// [`QueuePointers::Reset`].
+ ///
/// # Errors
///
- /// - `EINVAL` if `command_id` is not a load-and-execute command.
+ /// - `EINVAL` if `command_id` is not a load-and-execute command, or if the GSP asks for the
+ /// generic bootloader on a chipset that boots without one.
///
/// Errors from the handlers are propagated as-is.
- #[expect(dead_code)]
#[allow(clippy::too_many_arguments)]
fn dispatch_gmc_boot_event(
command_id: u32,
payload: &[u8],
- bootloader: &GenericBootloader,
+ bootloader: Option<&GenericBootloader>,
gsp_falcon: &Falcon<'_, Gsp>,
sec2_falcon: &Falcon<'_, Sec2>,
bar: Bar0<'_>,
dev: &device::Device,
bootloader_app_version: u32,
libos_dma_handle: u64,
- ) -> Result {
+ ) -> Result<QueuePointers> {
match command_id {
- GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER => Self::handle_load_exec_bootloader(
- payload,
- bootloader,
- gsp_falcon,
- sec2_falcon,
- bar,
- dev,
- bootloader_app_version,
- libos_dma_handle,
- ),
+ GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER => {
+ let Some(bootloader) = bootloader else {
+ dev_err!(
+ dev,
+ "GSP asked for the generic bootloader, which this chipset does not use\n"
+ );
+ return Err(EINVAL);
+ };
+
+ Self::handle_load_exec_bootloader(
+ payload,
+ bootloader,
+ gsp_falcon,
+ sec2_falcon,
+ bar,
+ dev,
+ bootloader_app_version,
+ libos_dma_handle,
+ )
+ }
GMCAPI_CMD_EXEC_HS_BINARY => Self::handle_load_exec_hs_binary(
payload,
gsp_falcon,
@@ -227,6 +292,9 @@ fn dispatch_gmc_boot_event(
/// aperture at wherever the image lives, and runs the bootloader, which does the copy from
/// the descriptor and jumps to the image. The aperture is restored afterwards.
///
+ /// Ends in [`Self::core_resume`], so on success the msgq v2 pointer registers read zero and
+ /// the return is [`QueuePointers::Reset`].
+ ///
/// # Errors
///
/// - `EINVAL` if the payload is shorter than the parameter block, the descriptor is not the
@@ -243,7 +311,7 @@ fn handle_load_exec_bootloader(
dev: &device::Device,
bootloader_app_version: u32,
libos_dma_handle: u64,
- ) -> Result {
+ ) -> Result<QueuePointers> {
let params = LoadExecGenericBootloaderParams::from_bytes_prefix(payload)
.ok_or(EINVAL)?
.0;
@@ -311,7 +379,9 @@ fn handle_load_exec_bootloader(
dev,
bootloader_app_version,
libos_dma_handle,
- )
+ )?;
+
+ Ok(QueuePointers::Reset)
}
/// Handle a `GSP_LOAD_EXEC_HS_BINARY` event.
@@ -320,6 +390,9 @@ fn handle_load_exec_bootloader(
/// framebuffer. The driver DMAs the image into falcon memory, programs the BROM registers
/// that make the falcon verify its PKC signature, runs it, and resumes GSP-RM.
///
+ /// Ends in [`Self::core_resume`], so on success the msgq v2 pointer registers read zero and
+ /// the return is [`QueuePointers::Reset`].
+ ///
/// # Errors
///
/// - `EINVAL` if the payload is shorter than the parameter block, or the ucode id does not
@@ -334,7 +407,7 @@ fn handle_load_exec_hs_binary(
dev: &device::Device,
bootloader_app_version: u32,
libos_dma_handle: u64,
- ) -> Result {
+ ) -> Result<QueuePointers> {
let params = HsBinaryParams::from_bytes_prefix(payload).ok_or(EINVAL)?.0;
gsp_falcon.wait_for_processor_suspend().inspect_err(|_| {
@@ -429,7 +502,9 @@ fn handle_load_exec_hs_binary(
dev,
bootloader_app_version,
libos_dma_handle,
- )
+ )?;
+
+ Ok(QueuePointers::Reset)
}
/// Shut down the GSP and wait until it is offline.
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 82ff46620911..8a8b8457d870 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -55,8 +55,7 @@
GspGmcMsgElement,
GspMsgElement,
MsgFunction,
- MsgqRxHeader,
- MsgqTxHeader,
+ MsgqTxHeaderV2,
GSP_MSG_QUEUE_ELEMENT_SIZE_MAX, //
},
PteArray,
@@ -185,17 +184,15 @@ struct MsgqData {
///
/// Contains the data for a message queue, that either the driver or GSP writes to.
///
-/// Note that while the write pointer of `tx` corresponds to the `msgq` of the same instance, the
-/// read pointer of `rx` actually refers to the `Msgq` owned by the other side.
-/// This design ensures that only the driver or GSP ever writes to a given instance of this struct.
+/// Msgq v2 keeps all four ring pointers in BAR0, so this struct carries queue geometry and
+/// message data only. `msgq` is aligned to [`GSP_PAGE_SIZE`], which leaves the space that
+/// carried the msgq v0 read-pointer header as zeroed padding after `tx`.
#[repr(C)]
// There is no struct defined for this in the open-gpu-kernel-source headers.
// Instead it is defined by code in `GspMsgQueuesInit()`.
struct Msgq {
- /// Header for sending messages, including the write pointer.
- tx: MsgqTxHeader,
- /// Header for receiving messages, including the read pointer.
- rx: MsgqRxHeader,
+ /// Header describing the queue geometry.
+ tx: MsgqTxHeaderV2,
/// The message queue proper.
msgq: MsgqData,
}
@@ -248,11 +245,13 @@ impl DmaGspMem {
/// Allocate a new instance and map it for `dev`.
fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
const MSGQ_SIZE: u32 = num::usize_into_u32::<{ size_of::<Msgq>() }>();
- const RX_HDR_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();
+ const MSG_SIZE: u32 = num::usize_into_u32::<GSP_PAGE_SIZE>();
+ const ENTRY_OFF: u32 = num::usize_into_u32::<{ mem::offset_of!(Msgq, msgq) }>();
+ // Msgq v2 keeps all four ring pointers in BAR0, so the in-memory RX header is never
+ // written and the TX header carries geometry only.
let mut gsp_mem = CoherentBox::<GspMem>::zeroed(dev, GFP_KERNEL)?;
- gsp_mem.cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES);
- gsp_mem.cpuq.rx = MsgqRxHeader::new();
+ gsp_mem.cpuq.tx = MsgqTxHeaderV2::new(MSGQ_SIZE, MSG_SIZE, MSGQ_NUM_PAGES, ENTRY_OFF);
let gsp_mem: Coherent<_> = gsp_mem.into();
PteArray::init(io_project!(gsp_mem, .ptes), gsp_mem.dma_address())?;
@@ -260,109 +259,6 @@ fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
Ok(Self(gsp_mem))
}
- /// Returns the region of the CPU message queue that the driver is currently allowed to write
- /// to.
- ///
- /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
- /// that case the second slice will have a non-zero length.
- fn driver_write_area(&mut self) -> (&mut [[u8; GSP_PAGE_SIZE]], &mut [[u8; GSP_PAGE_SIZE]]) {
- let tx = self.cpu_write_ptr();
- let rx = self.gsp_read_ptr();
-
- // Pointer to the first entry of the CPU message queue.
- let data = ptr::project!(mut self.0.as_mut_ptr(), .cpuq.msgq.data[build: 0]);
-
- let (tail_end, wrap_end) = if rx == 0 {
- // The write area is non-wrapping, and stops at the second-to-last entry of the command
- // queue (to leave the last one empty).
- (MSGQ_NUM_PAGES - 1, 0)
- } else if rx <= tx {
- // The write area wraps and continues until `rx - 1`.
- (MSGQ_NUM_PAGES, rx - 1)
- } else {
- // The write area doesn't wrap and stops at `rx - 1`.
- (rx - 1, 0)
- };
-
- // SAFETY:
- // - `data` was created from a valid pointer, and `rx` and `tx` are in the
- // `0..MSGQ_NUM_PAGES` range per the invariants of `cpu_write_ptr` and `gsp_read_ptr`,
- // thus the created slices are valid.
- // - The area starting at `tx` and ending at `rx - 2` modulo `MSGQ_NUM_PAGES`,
- // inclusive, belongs to the driver for writing and is not accessed concurrently by
- // the GSP.
- // - The caller holds a reference to `self` for as long as the returned slices are live,
- // meaning the CPU write pointer cannot be advanced and thus that the returned area
- // remains exclusive to the CPU for the duration of the slices.
- // - The created slices point to non-overlapping sub-ranges of `data` in all
- // branches (in the `rx <= tx` case, the second slice ends at `rx - 1` which is strictly
- // less than `tx` where the first slice starts; in the other cases the second slice is
- // empty), so creating two `&mut` references from them does not violate aliasing rules.
- unsafe {
- (
- core::slice::from_raw_parts_mut(
- data.add(num::u32_as_usize(tx)),
- num::u32_as_usize(tail_end - tx),
- ),
- core::slice::from_raw_parts_mut(data, num::u32_as_usize(wrap_end)),
- )
- }
- }
-
- /// Returns the size of the region of the CPU message queue that the driver is currently allowed
- /// to write to, in bytes.
- fn driver_write_area_size(&self) -> usize {
- let tx = self.cpu_write_ptr();
- let rx = self.gsp_read_ptr();
-
- // `rx` and `tx` are both in `0..MSGQ_NUM_PAGES` per the invariants of `gsp_read_ptr` and
- // `cpu_write_ptr`. The minimum value case is where `rx == 0` and `tx == MSGQ_NUM_PAGES -
- // 1`, which gives `0 + MSGQ_NUM_PAGES - (MSGQ_NUM_PAGES - 1) - 1 == 0`.
- let slots = (rx + MSGQ_NUM_PAGES - tx - 1) % MSGQ_NUM_PAGES;
- num::u32_as_usize(slots) * GSP_PAGE_SIZE
- }
-
- /// Returns the region of the GSP message queue that the driver is currently allowed to read
- /// from.
- ///
- /// As the message queue is a circular buffer, the region may be discontiguous in memory. In
- /// that case the second slice will have a non-zero length.
- fn driver_read_area(&self) -> (&[[u8; GSP_PAGE_SIZE]], &[[u8; GSP_PAGE_SIZE]]) {
- let tx = self.gsp_write_ptr();
- let rx = self.cpu_read_ptr();
-
- // Pointer to the first entry of the GSP message queue.
- let data = ptr::project!(self.0.as_ptr(), .gspq.msgq.data[build: 0]);
-
- let (tail_end, wrap_end) = if rx <= tx {
- // Read area is non-wrapping and stops right before `tx`.
- (tx, 0)
- } else {
- // Read area is wrapping and stops right before `tx`.
- (MSGQ_NUM_PAGES, tx)
- };
-
- // SAFETY:
- // - `data` was created from a valid pointer, and `rx` and `tx` are in the
- // `0..MSGQ_NUM_PAGES` range per the invariants of `gsp_write_ptr` and `cpu_read_ptr`,
- // thus the created slices are valid.
- // - The area starting at `rx` and ending at `tx - 1` modulo `MSGQ_NUM_PAGES`,
- // inclusive, belongs to the driver for reading and is not accessed concurrently by
- // the GSP.
- // - The caller holds a reference to `self` for as long as the returned slices are live,
- // meaning the CPU read pointer cannot be advanced and thus that the returned area
- // remains exclusive to the CPU for the duration of the slices.
- unsafe {
- (
- core::slice::from_raw_parts(
- data.add(num::u32_as_usize(rx)),
- num::u32_as_usize(tail_end - rx),
- ),
- core::slice::from_raw_parts(data, num::u32_as_usize(wrap_end)),
- )
- }
- }
-
/// Allocates a region on the command queue that is large enough to send a command of `size`
/// bytes, waiting for space to become available based on the provided timeout.
///
@@ -377,6 +273,7 @@ fn driver_write_area_size(&self) -> usize {
/// - `EIO` if the command header is not properly aligned.
fn allocate_command<H: FromBytes + AsBytes>(
&mut self,
+ bar: Bar0<'_>,
size: usize,
timeout: Delta,
) -> Result<GspCommand<'_, H>> {
@@ -384,7 +281,7 @@ fn allocate_command<H: FromBytes + AsBytes>(
return Err(EMSGSIZE);
}
read_poll_timeout(
- || Ok(self.driver_write_area_size()),
+ || Ok(Self::driver_write_area_size_v2(bar)),
|available_bytes| *available_bytes >= size_of::<H>() + size,
Delta::from_micros(1),
timeout,
@@ -392,7 +289,7 @@ fn allocate_command<H: FromBytes + AsBytes>(
// Get the current writable area as an array of bytes.
let (slice_1, slice_2) = {
- let (slice_1, slice_2) = self.driver_write_area();
+ let (slice_1, slice_2) = self.driver_write_area_v2(bar);
(slice_1.as_flattened_mut(), slice_2.as_flattened_mut())
};
@@ -415,63 +312,6 @@ fn allocate_command<H: FromBytes + AsBytes>(
contents: (slice_1, slice_2),
})
}
-
- // Returns the index of the memory page the GSP will write the next message to.
- //
- // # Invariants
- //
- // - The returned value is within `0..MSGQ_NUM_PAGES`.
- fn gsp_write_ptr(&self) -> u32 {
- MsgqTxHeader::write_ptr(io_project!(self.0, .gspq.tx)) % MSGQ_NUM_PAGES
- }
-
- // Returns the index of the memory page the GSP will read the next command from.
- //
- // # Invariants
- //
- // - The returned value is within `0..MSGQ_NUM_PAGES`.
- fn gsp_read_ptr(&self) -> u32 {
- MsgqRxHeader::read_ptr(io_project!(self.0, .gspq.rx)) % MSGQ_NUM_PAGES
- }
-
- // Returns the index of the memory page the CPU can read the next message from.
- //
- // # Invariants
- //
- // - The returned value is within `0..MSGQ_NUM_PAGES`.
- fn cpu_read_ptr(&self) -> u32 {
- MsgqRxHeader::read_ptr(io_project!(self.0, .cpuq.rx)) % MSGQ_NUM_PAGES
- }
-
- // Informs the GSP that it can send `elem_count` new pages into the message queue.
- fn advance_cpu_read_ptr(&mut self, elem_count: u32) {
- let rx = io_project!(self.0, .cpuq.rx);
- let rptr = MsgqRxHeader::read_ptr(rx).wrapping_add(elem_count) % MSGQ_NUM_PAGES;
-
- // Ensure read pointer is properly ordered.
- fence(Ordering::SeqCst);
-
- MsgqRxHeader::set_read_ptr(rx, rptr)
- }
-
- // Returns the index of the memory page the CPU can write the next command to.
- //
- // # Invariants
- //
- // - The returned value is within `0..MSGQ_NUM_PAGES`.
- fn cpu_write_ptr(&self) -> u32 {
- MsgqTxHeader::write_ptr(io_project!(self.0, .cpuq.tx)) % MSGQ_NUM_PAGES
- }
-
- // Informs the GSP that it can process `elem_count` new pages from the command queue.
- fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
- let tx = io_project!(self.0, .cpuq.tx);
- let wptr = MsgqTxHeader::write_ptr(tx).wrapping_add(elem_count) % MSGQ_NUM_PAGES;
- MsgqTxHeader::set_write_ptr(tx, wptr);
-
- // Ensure all command data is visible before triggering the GSP read.
- fence(Ordering::SeqCst);
- }
}
// Msgq v2 internals.
@@ -489,14 +329,15 @@ fn advance_cpu_write_ptr(&mut self, elem_count: u32) {
// CPU RX read: NV_PGSP_MSGQ_TAIL
//
// A GSP reset zeroes all four counters while the in-memory ring keeps its
-// contents, so the two ends can be out of step. Between the reset and
-// GSP-RM writing its counter back the read pointer is ahead of the write
-// pointer, which `driver_read_area_v2` reports as an empty ring.
+// contents. Neither end restarts at zero: GSP-RM writes its own two back
+// from its internal state when it resumes, and the driver writes back the
+// two it owns (`restore_cpu_ptrs_v2`). Between the reset and GSP-RM's
+// write-back the read pointer is ahead of the write pointer, which
+// `driver_read_area_v2` reports as an empty ring.
//
// TODO: suspend/resume reset-recovery is not implemented. A power cycle
// loses the driver-side counters as well, so both ends have to be
-// re-established.
-#[expect(dead_code)]
+// re-established rather than restored.
impl DmaGspMem {
fn gsp_write_ptr_v2(bar: Bar0<'_>) -> u32 {
*bar.read(regs::NV_PGSP_MSGQ_HEAD).address()
@@ -514,6 +355,18 @@ fn cpu_write_ptr_v2(bar: Bar0<'_>) -> u32 {
*bar.read(regs::NV_PGSP_QUEUE_HEAD).address()
}
+ /// Writes back the two counters the driver owns, after a GSP reset has zeroed all four.
+ ///
+ /// GSP-RM restores `NV_PGSP_MSGQ_HEAD` and `NV_PGSP_QUEUE_TAIL` from its own state when it
+ /// resumes, and continues counting from where it left off rather than from zero. The driver
+ /// owns the other two and has to do the same, or the two ends address different slots.
+ fn restore_cpu_ptrs_v2(bar: Bar0<'_>, write_ptr: u32, read_ptr: u32) {
+ fence(Ordering::SeqCst);
+
+ bar.write_reg(regs::NV_PGSP_MSGQ_TAIL::zeroed().with_address(read_ptr));
+ bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(write_ptr));
+ }
+
fn advance_cpu_read_ptr_v2(bar: Bar0<'_>, count: u32) {
let new_rptr = Self::cpu_read_ptr_v2(bar).wrapping_add(count);
@@ -649,6 +502,20 @@ struct GmcMessage<'a> {
contents: (&'a [u8], &'a [u8]),
}
+/// State of the msgq v2 pointer registers after a GMC dispatch handler has run.
+///
+/// Resetting the GSP falcon zeroes all four pointer registers, which leaves the read pointer
+/// where consuming the current element would have put it. A handler reports which case applies
+/// so that [`CmdqInner::receive_gmc_and_dispatch`] can tell whether the read pointer still needs
+/// advancing.
+#[derive(PartialEq, Eq)]
+pub(crate) enum QueuePointers {
+ /// The handler left the pointer registers alone.
+ Unchanged,
+ /// The handler reset the GSP, which zeroed the pointer registers.
+ Reset,
+}
+
/// GSP command queue.
///
/// Provides the ability to send commands and receive messages from the GSP using a shared memory
@@ -692,7 +559,6 @@ pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Err
inner <- new_mutex!(CmdqInner {
dev: dev.into(),
gsp_mem,
- elem_seq: 0,
rpc_seq: 0,
tx_async_seq: 0,
rx_event_seq: 0,
@@ -702,24 +568,6 @@ pub(crate) fn new(dev: &device::Device<device::Bound>) -> impl PinInit<Self, Err
})
}
- /// Computes the checksum for the message pointed to by `it`.
- ///
- /// A message is made of several parts, so `it` is an iterator over byte slices representing
- /// these parts.
- fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
- let sum64 = it
- .enumerate()
- .map(|(idx, byte)| (((idx % 8) * 8) as u32, byte))
- .fold(0, |acc, (rol, byte)| acc ^ u64::from(byte).rotate_left(rol));
-
- ((sum64 >> 32) as u32) ^ (sum64 as u32)
- }
-
- /// Notifies the GSP that we have updated the command queue pointers.
- fn notify_gsp(bar: Bar0<'_>) {
- bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
- }
-
/// Sends `command` to the GSP and waits for the reply.
///
/// A message read while waiting that is not the reply goes to
@@ -753,7 +601,7 @@ pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Rep
if remaining.is_negative() {
break Err(ETIMEDOUT);
}
- match inner.receive_msg::<M::Reply>(remaining, Some(expected_seq)) {
+ match inner.receive_msg::<M::Reply>(bar, remaining, Some(expected_seq)) {
Ok(reply) => break Ok(reply),
Err(ERANGE) => continue,
Err(e) => break Err(e),
@@ -770,6 +618,7 @@ pub(crate) fn send_command<M>(&self, bar: Bar0<'_>, command: M) -> Result<M::Rep
/// written to by its [`CommandToGsp::init_variable_payload`] method.
///
/// Error codes returned by the command initializers are propagated as-is.
+ #[expect(dead_code)]
pub(crate) fn send_command_no_wait<M>(&self, bar: Bar0<'_>, command: M) -> Result
where
M: CommandToGsp<Reply = NoReply>,
@@ -782,12 +631,12 @@ pub(crate) fn send_command_no_wait<M>(&self, bar: Bar0<'_>, command: M) -> Resul
///
/// Returns `ERANGE` if the message that arrives is not of type `M`. See
/// [`CmdqInner::receive_msg`].
- fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
+ fn receive_msg<M: MessageFromGsp>(&self, bar: Bar0<'_>, timeout: Delta) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
- self.inner.lock().receive_msg(timeout, None)
+ self.inner.lock().receive_msg(bar, timeout, None)
}
/// Receives one GMC element from the GSP and passes its command id, the `max_resp_or_status`
@@ -799,10 +648,13 @@ fn receive_msg<M: MessageFromGsp>(&self, timeout: Delta) -> Result<M>
/// See [`CmdqInner::receive_gmc_and_dispatch`] for return values, queue state, and errors.
pub(crate) fn receive_gmc_and_dispatch<R>(
&self,
+ bar: Bar0<'_>,
timeout: Delta,
- handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
+ handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> (Option<R>, QueuePointers),
) -> Result<Option<R>> {
- self.inner.lock().receive_gmc_and_dispatch(timeout, handler)
+ self.inner
+ .lock()
+ .receive_gmc_and_dispatch(bar, timeout, handler)
}
/// Sends a GMC API command to the GSP without waiting for its response.
@@ -834,7 +686,8 @@ pub(crate) fn send_gmc_no_wait(
///
/// - `ETIMEDOUT` if the event does not arrive within [`Self::RECEIVE_TIMEOUT`] of the call,
/// however many other events are dispatched while waiting.
- pub(crate) fn await_msg<M: MessageFromGsp>(&self) -> Result<M>
+ #[expect(dead_code)]
+ pub(crate) fn await_msg<M: MessageFromGsp>(&self, bar: Bar0<'_>) -> Result<M>
where
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
@@ -845,7 +698,7 @@ pub(crate) fn await_msg<M: MessageFromGsp>(&self) -> Result<M>
if remaining.is_negative() {
break Err(ETIMEDOUT);
}
- match self.receive_msg::<M>(remaining) {
+ match self.receive_msg::<M>(bar, remaining) {
Ok(msg) => break Ok(msg),
Err(ERANGE) => continue,
Err(e) => break Err(e),
@@ -861,8 +714,8 @@ pub(crate) fn await_msg<M: MessageFromGsp>(&self) -> Result<M>
/// # Errors
///
/// Propagates a receive error, in particular the `EIO` of a queue poisoned by corrupt framing.
- pub(crate) fn drain(&self) -> Result {
- self.inner.lock().drain()
+ pub(crate) fn drain(&self, bar: Bar0<'_>) -> Result {
+ self.inner.lock().drain(bar)
}
}
@@ -870,9 +723,6 @@ pub(crate) fn drain(&self) -> Result {
struct CmdqInner {
/// Device this command queue belongs to.
dev: ARef<device::Device>,
- /// Next transport sequence number for a queue element (the `seqNum` field). Advances once per
- /// queue element, including each continuation record.
- elem_seq: u32,
/// Next RPC sequence number. The GSP echoes it in a command's reply, which lets
/// [`CmdqInner::receive_msg`] match that reply to the awaiting command. Advances once per
/// logical command.
@@ -883,7 +733,7 @@ struct CmdqInner {
/// Debug-log sequence for GSP-initiated events. The GSP leaves the RPC sequence unset on
/// those messages, so the driver numbers them itself.
rx_event_seq: u32,
- /// Set once a message with corrupt framing or a bad checksum is seen. Such a message has an
+ /// Set once a message with corrupt framing or a bad MCTP magic is seen. Such a message has an
/// untrusted length, so the queue cannot be advanced past it, and every later receive fails
/// until the queue is torn down and reset.
///
@@ -916,7 +766,7 @@ fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M, rpc_seq: u32) ->
let size_in_bytes = command.size();
let dst = self
.gsp_mem
- .allocate_command(size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
+ .allocate_command(bar, size_in_bytes, Self::ALLOCATE_TIMEOUT)?;
// Extract area for the command itself. The GSP message header and the command header
// together are guaranteed to fit entirely into a single page, so it's ok to only look
@@ -924,7 +774,7 @@ fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M, rpc_seq: u32) ->
let (cmd, payload_1) = M::Command::from_bytes_mut_prefix(dst.contents.0).ok_or(EIO)?;
// Fill the header and command in-place.
- let msg_element = GspMsgElement::init(self.elem_seq, rpc_seq, size_in_bytes, M::FUNCTION);
+ let msg_element = GspMsgElement::init(rpc_seq, size_in_bytes, M::FUNCTION);
// SAFETY: `msg_header` and `cmd` are valid references, and not touched if the initializer
// fails.
unsafe {
@@ -941,14 +791,6 @@ fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M, rpc_seq: u32) ->
}
drop(sbuffer);
- // Compute checksum now that the whole message is ready.
- dst.header
- .set_checksum(Cmdq::calculate_checksum(SBufferIter::new_reader([
- dst.header.as_bytes(),
- dst.contents.0,
- dst.contents.1,
- ])));
-
if M::IS_ASYNC {
dev_dbg!(
&self.dev,
@@ -970,9 +812,7 @@ fn send_single_command<M>(&mut self, bar: Bar0<'_>, command: M, rpc_seq: u32) ->
// All set - update the write pointer and inform the GSP of the new command.
let elem_count = dst.header.element_count();
- self.elem_seq = self.elem_seq.wrapping_add(1);
- self.gsp_mem.advance_cpu_write_ptr(elem_count);
- Cmdq::notify_gsp(bar);
+ DmaGspMem::advance_cpu_write_ptr_v2(bar, elem_count);
Ok(())
}
@@ -1036,9 +876,11 @@ fn send_gmc(
let rpc_seq = self.rpc_seq;
self.rpc_seq = self.rpc_seq.wrapping_add(1);
- let dst = self
- .gsp_mem
- .allocate_command::<GspGmcMsgElement>(payload.len(), Self::ALLOCATE_TIMEOUT)?;
+ let dst = self.gsp_mem.allocate_command::<GspGmcMsgElement>(
+ bar,
+ payload.len(),
+ Self::ALLOCATE_TIMEOUT,
+ )?;
let msg_element = GspGmcMsgElement::init(
command_id,
@@ -1063,8 +905,7 @@ fn send_gmc(
);
let elem_count = dst.header.element_count();
- self.gsp_mem.advance_cpu_write_ptr(elem_count);
- Cmdq::notify_gsp(bar);
+ DmaGspMem::advance_cpu_write_ptr_v2(bar, elem_count);
Ok(())
}
@@ -1085,14 +926,14 @@ fn send_gmc(
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if the framing or the checksum is invalid, or the queue was already poisoned by an
/// earlier such failure. Either failure poisons the queue, so recovery requires a reset.
- fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
+ fn wait_for_msg(&self, bar: Bar0<'_>, timeout: Delta) -> Result<GspMessage<'_>> {
if self.poisoned.get() {
return Err(EIO);
}
// Wait for a message to arrive from the GSP.
let (slice_1, slice_2) = read_poll_timeout(
- || Ok(self.gsp_mem.driver_read_area()),
+ || Ok(self.gsp_mem.driver_read_area_v2(bar)),
|driver_area| !driver_area.0.is_empty(),
Delta::from_millis(1),
timeout,
@@ -1126,16 +967,10 @@ fn wait_for_msg(&self, timeout: Delta) -> Result<GspMessage<'_>> {
)
};
- // Validate checksum.
- if Cmdq::calculate_checksum(SBufferIter::new_reader([
- header.as_bytes(),
- slice_1,
- slice_2,
- ])) != 0
- {
+ if !header.has_valid_magic() {
dev_err!(
&self.dev,
- "GSP RPC: receive: Call {} - bad checksum\n",
+ "GSP RPC: receive: Call {} - bad MCTP magic\n",
header.sequence()
);
self.poisoned.set(true);
@@ -1206,6 +1041,7 @@ fn advance_rx_event_seq(&mut self, function: Result<MsgFunction, u32>) {
/// Error codes returned by [`MessageFromGsp::read`] are propagated as-is.
fn receive_msg<M: MessageFromGsp>(
&mut self,
+ bar: Bar0<'_>,
timeout: Delta,
expected_seq: Option<u32>,
) -> Result<M>
@@ -1213,7 +1049,7 @@ fn receive_msg<M: MessageFromGsp>(
// This allows all error types, including `Infallible`, to be used for `M::InitError`.
Error: From<M::InitError>,
{
- let message = self.wait_for_msg(timeout)?;
+ let message = self.wait_for_msg(bar, timeout)?;
let function = message.header.function();
let seq = message.header.sequence();
let length = message.header.length();
@@ -1247,8 +1083,7 @@ fn receive_msg<M: MessageFromGsp>(
};
// Advance the read pointer past this message.
- self.gsp_mem
- .advance_cpu_read_ptr(u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?);
+ DmaGspMem::advance_cpu_read_ptr_v2(bar, u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?);
self.advance_rx_event_seq(function);
@@ -1310,10 +1145,10 @@ fn dispatch_event(&self, function: Result<MsgFunction, u32>, seq: u32) {
///
/// Returns the receive error that stopped the drain, in particular the `EIO` of a queue
/// poisoned by corrupt framing (see [`Self::wait_for_msg`]).
- fn drain(&mut self) -> Result {
- while !self.gsp_mem.driver_read_area().0.is_empty() {
+ fn drain(&mut self, bar: Bar0<'_>) -> Result {
+ while !self.gsp_mem.driver_read_area_v2(bar).0.is_empty() {
// A message is available, so this returns without waiting.
- let msg = self.wait_for_msg(Delta::ZERO)?;
+ let msg = self.wait_for_msg(bar, Delta::ZERO)?;
let function = msg.header.function();
let seq = msg.header.sequence();
let length = msg.header.length();
@@ -1325,7 +1160,7 @@ fn drain(&mut self) -> Result {
EIO
})?;
- self.gsp_mem.advance_cpu_read_ptr(pages);
+ DmaGspMem::advance_cpu_read_ptr_v2(bar, pages);
self.advance_rx_event_seq(function);
self.dispatch_event(function, seq);
}
@@ -1349,13 +1184,13 @@ fn drain(&mut self) -> Result {
/// - `ETIMEDOUT` if `timeout` has elapsed before any message becomes available.
/// - `EIO` if the framing is invalid, or the queue was already poisoned by an earlier such
/// failure. Either failure poisons the queue, so recovery requires a reset.
- fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
+ fn wait_for_gmc_msg(&self, bar: Bar0<'_>, timeout: Delta) -> Result<GmcMessage<'_>> {
if self.poisoned.get() {
return Err(EIO);
}
let (slice_1, slice_2) = read_poll_timeout(
- || Ok(self.gsp_mem.driver_read_area()),
+ || Ok(self.gsp_mem.driver_read_area_v2(bar)),
|driver_area| !driver_area.0.is_empty(),
Delta::from_millis(1),
timeout,
@@ -1405,7 +1240,8 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
///
/// The handler receives the GMC command id, the header's `max_resp_or_status` field, and the
/// raw payload slices that follow the [`super::fw::GmcApiHeader`] (two slices because the
- /// circular buffer may wrap). It returns `None` for an element it does not handle.
+ /// circular buffer may wrap). It returns `None` for an element it does not handle, paired
+ /// with the [`QueuePointers`] state it left behind.
///
/// `max_resp_or_status` is a union: GSP-RM writes an `NV_STATUS` there when the element is a
/// response, and the maximum response size when it is a request. Only a handler that knows
@@ -1415,7 +1251,9 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
/// which is the form the r000 firmware uses for boot events.
///
/// Returns `Ok(None)` when nothing claimed the element, either because it is not a GMC
- /// element or because the handler declined it. The read pointer is advanced on every path.
+ /// element or because the handler declined it. The read pointer is advanced past the element
+ /// on every path except a handler reporting [`QueuePointers::Reset`], which has already
+ /// returned both pointers to zero.
///
/// # Errors
///
@@ -1424,16 +1262,22 @@ fn wait_for_gmc_msg(&self, timeout: Delta) -> Result<GmcMessage<'_>> {
/// [`Self::wait_for_gmc_msg`]).
fn receive_gmc_and_dispatch<R>(
&mut self,
+ bar: Bar0<'_>,
timeout: Delta,
- handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> Option<R>,
+ handler: impl FnOnce(u32, u32, &[u8], &[u8]) -> (Option<R>, QueuePointers),
) -> Result<Option<R>> {
- let message = self.wait_for_gmc_msg(timeout)?;
+ let message = self.wait_for_gmc_msg(bar, timeout)?;
let header = message.header;
let length = header.length();
+ // A handler that resets the GSP zeroes both of these, and the values they hold now are
+ // the ones the driver has to put back.
+ let cpu_write_ptr = DmaGspMem::cpu_write_ptr_v2(bar);
+ let cpu_read_ptr = DmaGspMem::cpu_read_ptr_v2(bar);
+
// The RPC and GMC elements share every field through `nvdm_header`, so `gmc` holds an
// RPC header rather than a GMC one unless the NVDM type says otherwise.
- let result = if header.is_gmc_api() {
+ let (result, queue_pointers) = if header.is_gmc_api() {
let command_id = header.gmc.command_id();
dev_dbg!(
@@ -1452,11 +1296,20 @@ fn receive_gmc_and_dispatch<R>(
)
} else {
dev_warn!(&self.dev, "GSP GMC: dropping non-GMC queue element\n");
- None
+ (None, QueuePointers::Unchanged)
};
- self.gsp_mem
- .advance_cpu_read_ptr(u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?);
+ let pages = u32::try_from(length.div_ceil(GSP_PAGE_SIZE))?;
+
+ match queue_pointers {
+ QueuePointers::Unchanged => DmaGspMem::advance_cpu_read_ptr_v2(bar, pages),
+ // The registers read zero now, so a read-modify-write would restart the driver's
+ // side of the ring at zero while GSP-RM carries on from where it stopped. Put the
+ // captured values back instead, with this element counted as consumed.
+ QueuePointers::Reset => {
+ DmaGspMem::restore_cpu_ptrs_v2(bar, cpu_write_ptr, cpu_read_ptr.wrapping_add(pages))
+ }
+ }
Ok(result)
}
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index d55faf1a4e04..26ea07dc4a28 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -13,10 +13,7 @@
device,
pci,
prelude::*,
- transmute::{
- AsBytes,
- FromBytes, //
- }, //
+ transmute::AsBytes, //
};
use crate::{
@@ -27,7 +24,8 @@
Cmdq,
CommandToGsp,
MessageFromGsp,
- NoReply, //
+ NoReply,
+ QueuePointers, //
},
fw::{
self,
@@ -52,11 +50,14 @@
};
/// The `GspSetSystemInfo` command.
+///
+/// The r000 boot path folds this into the `GSP_INIT` payload instead.
pub(crate) struct SetSystemInfo<'a> {
pdev: &'a pci::Device<device::Bound>,
chipset: Chipset,
}
+#[expect(dead_code)]
impl<'a> SetSystemInfo<'a> {
/// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`.
pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Self {
@@ -82,10 +83,13 @@ struct RegistryEntry {
}
/// The `SetRegistry` command.
+///
+/// The r000 boot path folds this into the `GSP_INIT` payload instead.
pub(crate) struct SetRegistry {
entries: KVec<RegistryEntry>,
}
+#[expect(dead_code)]
impl SetRegistry {
/// Creates a new `SetRegistry` command, using a set of hardcoded entries.
pub(crate) fn new(vgpu_state: VgpuState) -> Result<Self> {
@@ -182,31 +186,6 @@ fn init_variable_payload(
}
}
-/// Message type for GSP initialization done notification.
-struct GspInitDone;
-
-// SAFETY: `GspInitDone` is a zero-sized type with no bytes, therefore it
-// trivially has no uninitialized bytes.
-unsafe impl FromBytes for GspInitDone {}
-
-impl MessageFromGsp for GspInitDone {
- const FUNCTION: MsgFunction = MsgFunction::GspInitDone;
- type InitError = Infallible;
- type Message = ();
-
- fn read(
- _msg: &Self::Message,
- _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- Ok(GspInitDone)
- }
-}
-
-/// Waits for GSP initialization to complete.
-pub(crate) fn wait_gsp_init_done(cmdq: &Cmdq) -> Result {
- cmdq.await_msg::<GspInitDone>().map(|_| ())
-}
-
/// The `GetGspStaticInfo` command.
pub(crate) struct GetGspStaticInfo;
@@ -297,7 +276,6 @@ pub(crate) fn gpu_name(&self) -> core::result::Result<&str, GpuNameError> {
/// # Errors
///
/// - `ENOMEM` if the registry list or the encoder buffer cannot be allocated.
-#[expect(dead_code)]
pub(crate) fn build_gsp_init_payload(
pdev: &pci::Device<device::Bound>,
chipset: Chipset,
@@ -325,8 +303,9 @@ pub(crate) fn build_gsp_init_payload(
///
/// GSP-RM interleaves load-and-execute events between the request and the reply, and those events
/// drive the falcon loads that let it finish starting, so each one is passed to `on_boot_event`
-/// rather than skipped. The reply arrives only once GSP-RM is up, which is what makes it the
-/// signal that boot is complete.
+/// rather than skipped. `on_boot_event` returns the [`QueuePointers`] state its handler left
+/// behind, because a handler that resets the GSP also zeroes the queue's pointer registers. The
+/// reply arrives only once GSP-RM is up, which is what makes it the signal that boot is complete.
///
/// `payload` is the blob from [`build_gsp_init_payload`].
///
@@ -338,12 +317,11 @@ pub(crate) fn build_gsp_init_payload(
/// [`Cmdq::RECEIVE_TIMEOUT`].
///
/// Errors from `on_boot_event` and from decoding the reply are propagated as-is.
-#[expect(dead_code)]
pub(crate) fn gsp_init(
cmdq: &Cmdq,
bar: Bar0<'_>,
payload: &[u64],
- mut on_boot_event: impl FnMut(u32, &[u8]) -> Result,
+ mut on_boot_event: impl FnMut(u32, &[u8]) -> Result<QueuePointers>,
) -> Result<GetGspStaticInfoReply> {
// Qualified because `zerocopy::IntoBytes` also gives `[T]` an `as_bytes`.
let payload = AsBytes::as_bytes(payload);
@@ -357,19 +335,25 @@ pub(crate) fn gsp_init(
loop {
let reply = cmdq.receive_gmc_and_dispatch(
+ bar,
Cmdq::RECEIVE_TIMEOUT,
|command_id, max_resp_or_status, payload_0, payload_1| {
if command_id == GMCAPI_CMD_GSP_INIT {
- Some(decode_gsp_init_reply(
- max_resp_or_status,
- payload_0,
- payload_1,
- ))
+ (
+ Some(decode_gsp_init_reply(
+ max_resp_or_status,
+ payload_0,
+ payload_1,
+ )),
+ QueuePointers::Unchanged,
+ )
} else {
// A boot event. Keep waiting for the reply unless handling it failed.
match on_boot_event(command_id, payload_0) {
- Ok(()) => None,
- Err(e) => Some(Err(e)),
+ Ok(queue_pointers) => (None, queue_pointers),
+ // A handler can fail after it has already reset the GSP, so the pointer
+ // registers cannot be assumed intact on this path.
+ Err(e) => (Some(Err(e)), QueuePointers::Reset),
}
}
},
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 6958ee3a2e4e..c24808e41941 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -6,20 +6,14 @@
mod r570_144;
// Alias to avoid repeating the version number with every use.
-use r570_144 as bindings;
+use r000_00 as bindings;
use core::ops::Range;
use kernel::{
bitfield,
- dma::{
- Coherent,
- CoherentView, //
- },
- io::{
- io_read,
- io_write, //
- },
+ dma::Coherent,
+ io::io_write,
prelude::*,
ptr::{
Alignable,
@@ -46,10 +40,7 @@
Architecture,
Chipset, //
},
- gsp::{
- cmdq::Cmdq, //
- GSP_PAGE_SIZE,
- },
+ gsp::{cmdq::Cmdq, GSP_PAGE_SHIFT, GSP_PAGE_SIZE},
mctp::{
MctpHeader,
NvdmHeader,
@@ -62,8 +53,10 @@
};
/// Maximum size of a single GSP message queue element in bytes.
-pub(crate) const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: usize =
- num::u32_as_usize(bindings::GSP_MSG_QUEUE_ELEMENT_SIZE_MAX);
+///
+/// GSP-RM takes this as a runtime field of the message queue init arguments rather than as a
+/// build-time constant, so the driver chooses it and both sides read it from here.
+pub(crate) const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: usize = GSP_PAGE_SIZE * 16;
/// Empty type to group methods related to heap parameters for running the GSP firmware.
enum GspFwHeapParams {}
@@ -97,7 +90,7 @@ fn client_alloc_size() -> u64 {
fn management_overhead(fb_size: u64) -> Result<u64> {
let fb_size_gb = fb_size.div_ceil(u64::SZ_1G);
- u64::from(bindings::GSP_FW_HEAP_PARAM_SIZE_PER_GB_FB)
+ u64::from(bindings::GSP_FW_HEAP_PARAM_SIZE_PER_GB)
.checked_mul(fb_size_gb)
.ok_or(EINVAL)?
.align_up(GSP_HEAP_ALIGNMENT)
@@ -304,7 +297,6 @@ pub(crate) enum MsgFunction {
GspInitDone = bindings::NV_VGPU_MSG_EVENT_GSP_INIT_DONE,
GspLockdownNotice = bindings::NV_VGPU_MSG_EVENT_GSP_LOCKDOWN_NOTICE,
GspPostNoCat = bindings::NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD,
- GspRunCpuSequencer = bindings::NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER,
MmuFaultQueued = bindings::NV_VGPU_MSG_EVENT_MMU_FAULT_QUEUED,
OsErrorLog = bindings::NV_VGPU_MSG_EVENT_OS_ERROR_LOG,
PostEvent = bindings::NV_VGPU_MSG_EVENT_POST_EVENT,
@@ -351,9 +343,6 @@ fn try_from(value: u32) -> Result<MsgFunction> {
bindings::NV_VGPU_MSG_EVENT_GSP_INIT_DONE => Ok(MsgFunction::GspInitDone),
bindings::NV_VGPU_MSG_EVENT_GSP_LOCKDOWN_NOTICE => Ok(MsgFunction::GspLockdownNotice),
bindings::NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD => Ok(MsgFunction::GspPostNoCat),
- bindings::NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER => {
- Ok(MsgFunction::GspRunCpuSequencer)
- }
bindings::NV_VGPU_MSG_EVENT_MMU_FAULT_QUEUED => Ok(MsgFunction::MmuFaultQueued),
bindings::NV_VGPU_MSG_EVENT_OS_ERROR_LOG => Ok(MsgFunction::OsErrorLog),
bindings::NV_VGPU_MSG_EVENT_POST_EVENT => Ok(MsgFunction::PostEvent),
@@ -371,7 +360,6 @@ pub(crate) fn is_event(&self) -> bool {
matches!(
self,
Self::GspInitDone
- | Self::GspRunCpuSequencer
| Self::PostEvent
| Self::RcTriggered
| Self::MmuFaultQueued
@@ -390,277 +378,6 @@ fn from(value: MsgFunction) -> Self {
}
}
-/// Sequencer buffer opcode for GSP sequencer commands.
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[repr(u32)]
-pub(crate) enum SeqBufOpcode {
- // Core operation opcodes
- CoreReset = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET,
- CoreResume = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME,
- CoreStart = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START,
- CoreWaitForHalt = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT,
-
- // Delay opcode
- DelayUs = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US,
-
- // Register operation opcodes
- RegModify = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY,
- RegPoll = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL,
- RegStore = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE,
- RegWrite = bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE,
-}
-
-impl TryFrom<u32> for SeqBufOpcode {
- type Error = kernel::error::Error;
-
- fn try_from(value: u32) -> Result<SeqBufOpcode> {
- match value {
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET => {
- Ok(SeqBufOpcode::CoreReset)
- }
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME => {
- Ok(SeqBufOpcode::CoreResume)
- }
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START => {
- Ok(SeqBufOpcode::CoreStart)
- }
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT => {
- Ok(SeqBufOpcode::CoreWaitForHalt)
- }
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US => Ok(SeqBufOpcode::DelayUs),
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY => {
- Ok(SeqBufOpcode::RegModify)
- }
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL => Ok(SeqBufOpcode::RegPoll),
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE => Ok(SeqBufOpcode::RegStore),
- bindings::GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE => Ok(SeqBufOpcode::RegWrite),
- _ => Err(EINVAL),
- }
- }
-}
-
-impl From<SeqBufOpcode> for u32 {
- fn from(value: SeqBufOpcode) -> Self {
- // CAST: `SeqBufOpcode` is `repr(u32)` and can thus be cast losslessly.
- value as u32
- }
-}
-
-/// Wrapper for GSP sequencer register write payload.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct RegWritePayload(bindings::GSP_SEQ_BUF_PAYLOAD_REG_WRITE);
-
-impl RegWritePayload {
- /// Returns the register address.
- pub(crate) fn addr(&self) -> u32 {
- self.0.addr
- }
-
- /// Returns the value to write.
- pub(crate) fn val(&self) -> u32 {
- self.0.val
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for RegWritePayload {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for RegWritePayload {}
-
-/// Wrapper for GSP sequencer register modify payload.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct RegModifyPayload(bindings::GSP_SEQ_BUF_PAYLOAD_REG_MODIFY);
-
-impl RegModifyPayload {
- /// Returns the register address.
- pub(crate) fn addr(&self) -> u32 {
- self.0.addr
- }
-
- /// Returns the mask to apply.
- pub(crate) fn mask(&self) -> u32 {
- self.0.mask
- }
-
- /// Returns the value to write.
- pub(crate) fn val(&self) -> u32 {
- self.0.val
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for RegModifyPayload {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for RegModifyPayload {}
-
-/// Wrapper for GSP sequencer register poll payload.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct RegPollPayload(bindings::GSP_SEQ_BUF_PAYLOAD_REG_POLL);
-
-impl RegPollPayload {
- /// Returns the register address.
- pub(crate) fn addr(&self) -> u32 {
- self.0.addr
- }
-
- /// Returns the mask to apply.
- pub(crate) fn mask(&self) -> u32 {
- self.0.mask
- }
-
- /// Returns the expected value.
- pub(crate) fn val(&self) -> u32 {
- self.0.val
- }
-
- /// Returns the timeout in microseconds.
- pub(crate) fn timeout(&self) -> u32 {
- self.0.timeout
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for RegPollPayload {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for RegPollPayload {}
-
-/// Wrapper for GSP sequencer delay payload.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct DelayUsPayload(bindings::GSP_SEQ_BUF_PAYLOAD_DELAY_US);
-
-impl DelayUsPayload {
- /// Returns the delay value in microseconds.
- pub(crate) fn val(&self) -> u32 {
- self.0.val
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for DelayUsPayload {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for DelayUsPayload {}
-
-/// Wrapper for GSP sequencer register store payload.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug)]
-pub(crate) struct RegStorePayload(bindings::GSP_SEQ_BUF_PAYLOAD_REG_STORE);
-
-impl RegStorePayload {
- /// Returns the register address.
- pub(crate) fn addr(&self) -> u32 {
- self.0.addr
- }
-
- /// Returns the storage index.
- #[allow(unused)]
- pub(crate) fn index(&self) -> u32 {
- self.0.index
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for RegStorePayload {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for RegStorePayload {}
-
-/// Wrapper for GSP sequencer buffer command.
-#[repr(transparent)]
-pub(crate) struct SequencerBufferCmd(bindings::GSP_SEQUENCER_BUFFER_CMD);
-
-impl SequencerBufferCmd {
- /// Returns the opcode as a `SeqBufOpcode` enum, or error if invalid.
- pub(crate) fn opcode(&self) -> Result<SeqBufOpcode> {
- self.0.opCode.try_into()
- }
-
- /// Returns the register write payload by value.
- ///
- /// Returns an error if the opcode is not `SeqBufOpcode::RegWrite`.
- pub(crate) fn reg_write_payload(&self) -> Result<RegWritePayload> {
- if self.opcode()? != SeqBufOpcode::RegWrite {
- return Err(EINVAL);
- }
- // SAFETY: Opcode is verified to be `RegWrite`, so union contains valid `RegWritePayload`.
- Ok(RegWritePayload(unsafe { self.0.payload.regWrite }))
- }
-
- /// Returns the register modify payload by value.
- ///
- /// Returns an error if the opcode is not `SeqBufOpcode::RegModify`.
- pub(crate) fn reg_modify_payload(&self) -> Result<RegModifyPayload> {
- if self.opcode()? != SeqBufOpcode::RegModify {
- return Err(EINVAL);
- }
- // SAFETY: Opcode is verified to be `RegModify`, so union contains valid `RegModifyPayload`.
- Ok(RegModifyPayload(unsafe { self.0.payload.regModify }))
- }
-
- /// Returns the register poll payload by value.
- ///
- /// Returns an error if the opcode is not `SeqBufOpcode::RegPoll`.
- pub(crate) fn reg_poll_payload(&self) -> Result<RegPollPayload> {
- if self.opcode()? != SeqBufOpcode::RegPoll {
- return Err(EINVAL);
- }
- // SAFETY: Opcode is verified to be `RegPoll`, so union contains valid `RegPollPayload`.
- Ok(RegPollPayload(unsafe { self.0.payload.regPoll }))
- }
-
- /// Returns the delay payload by value.
- ///
- /// Returns an error if the opcode is not `SeqBufOpcode::DelayUs`.
- pub(crate) fn delay_us_payload(&self) -> Result<DelayUsPayload> {
- if self.opcode()? != SeqBufOpcode::DelayUs {
- return Err(EINVAL);
- }
- // SAFETY: Opcode is verified to be `DelayUs`, so union contains valid `DelayUsPayload`.
- Ok(DelayUsPayload(unsafe { self.0.payload.delayUs }))
- }
-
- /// Returns the register store payload by value.
- ///
- /// Returns an error if the opcode is not `SeqBufOpcode::RegStore`.
- pub(crate) fn reg_store_payload(&self) -> Result<RegStorePayload> {
- if self.opcode()? != SeqBufOpcode::RegStore {
- return Err(EINVAL);
- }
- // SAFETY: Opcode is verified to be `RegStore`, so union contains valid `RegStorePayload`.
- Ok(RegStorePayload(unsafe { self.0.payload.regStore }))
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for SequencerBufferCmd {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for SequencerBufferCmd {}
-
-/// Wrapper for GSP run CPU sequencer RPC.
-#[repr(transparent)]
-pub(crate) struct RunCpuSequencer(bindings::rpc_run_cpu_sequencer_v17_00);
-
-impl RunCpuSequencer {
- /// Returns the command index.
- pub(crate) fn cmd_index(&self) -> u32 {
- self.0.cmdIndex
- }
-}
-
-// SAFETY: This struct only contains integer types for which all bit patterns are valid.
-unsafe impl FromBytes for RunCpuSequencer {}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for RunCpuSequencer {}
-
/// Struct containing the arguments required to pass a memory buffer to the GSP
/// for use during initialisation.
///
@@ -722,57 +439,15 @@ fn id8(name: &str) -> u64 {
}
}
-/// TX header for setting up a message queue with the GSP.
-#[repr(transparent)]
-pub(crate) struct MsgqTxHeader(bindings::msgqTxHeader);
-
-impl MsgqTxHeader {
- /// Create a new TX queue header.
- ///
- /// # Arguments
- ///
- /// * `msgq_size` - Total size of the message queue structure, in bytes.
- /// * `rx_hdr_offset` - Offset, in bytes, of the start of the RX header in the message queue
- /// structure.
- /// * `msg_count` - Number of messages that can be sent, i.e. the number of memory pages
- /// allocated for the message queue in the message queue structure.
- pub(crate) fn new(msgq_size: u32, rx_hdr_offset: u32, msg_count: u32) -> Self {
- Self(bindings::msgqTxHeader {
- version: 0,
- size: msgq_size,
- msgSize: num::usize_into_u32::<GSP_PAGE_SIZE>(),
- msgCount: msg_count,
- writePtr: 0,
- flags: 1,
- rxHdrOff: rx_hdr_offset,
- entryOff: num::usize_into_u32::<GSP_PAGE_SIZE>(),
- })
- }
-
- /// Returns the value of the write pointer for this queue.
- pub(crate) fn write_ptr(this: CoherentView<'_, Self>) -> u32 {
- io_read!(this, .0.writePtr)
- }
-
- /// Sets the value of the write pointer for this queue.
- pub(crate) fn set_write_ptr(this: CoherentView<'_, Self>, val: u32) {
- io_write!(this, .0.writePtr, val)
- }
-}
-
-// SAFETY: Padding is explicit and does not contain uninitialized data.
-unsafe impl AsBytes for MsgqTxHeader {}
-
/// TX header for setting up a message queue with the GSP, msgq v2 layout.
///
-/// Same wire size as [`MsgqTxHeader`] (32 bytes) with a different field
+/// Same wire size as the msgq v0 header (32 bytes) with a different field
/// layout: the v0 `writePtr`, `flags`, and `rxHdrOff` fields are gone, the
/// `version` `u32` becomes split `versionMajor`/`versionMinor` `u16`s, and
/// the trailing space holds three reserved `u32`s.
#[repr(transparent)]
-pub(crate) struct MsgqTxHeaderV2(r000_00::msgqTxHeader);
+pub(crate) struct MsgqTxHeaderV2(bindings::msgqTxHeader);
-#[expect(dead_code)]
impl MsgqTxHeaderV2 {
/// Creates a new v2 TX queue header.
///
@@ -784,7 +459,7 @@ impl MsgqTxHeaderV2 {
/// * `entry_off` - Byte offset from the start of the queue at which the
/// message data array begins.
pub(crate) fn new(msgq_size: u32, msg_size: u32, msg_count: u32, entry_off: u32) -> Self {
- Self(r000_00::msgqTxHeader {
+ Self(bindings::msgqTxHeader {
versionMajor: 2,
versionMinor: 0,
size: msgq_size,
@@ -799,31 +474,6 @@ pub(crate) fn new(msgq_size: u32, msg_size: u32, msg_count: u32, entry_off: u32)
// SAFETY: Padding is explicit and does not contain uninitialized data.
unsafe impl AsBytes for MsgqTxHeaderV2 {}
-/// RX header for setting up a message queue with the GSP.
-#[repr(transparent)]
-pub(crate) struct MsgqRxHeader(bindings::msgqRxHeader);
-
-/// Header for the message RX queue.
-impl MsgqRxHeader {
- /// Creates a new RX queue header.
- pub(crate) fn new() -> Self {
- Self(Default::default())
- }
-
- /// Returns the value of the read pointer for this queue.
- pub(crate) fn read_ptr(this: CoherentView<'_, Self>) -> u32 {
- io_read!(this, .0.readPtr)
- }
-
- /// Sets the value of the read pointer for this queue.
- pub(crate) fn set_read_ptr(this: CoherentView<'_, Self>, val: u32) {
- io_write!(this, .0.readPtr, val)
- }
-}
-
-// SAFETY: Padding is explicit and does not contain uninitialized data.
-unsafe impl AsBytes for MsgqRxHeader {}
-
bitfield! {
struct MsgHeaderVersion(u32) {
31:24 major;
@@ -862,12 +512,19 @@ fn init(sequence: u32, cmd_size: usize, function: MsgFunction) -> impl Init<Self
}
}
-/// GSP Message Element.
+/// GSP Message Element (r000 MCTP/NVDM format).
///
-/// This is essentially a message header expected to be followed by the message data.
-#[repr(transparent)]
+/// This is the transport-layer header for messages exchanged with GSP-RM.
+/// r000 firmware uses MCTP/NVDM framing instead of the r570 `GSP_MSG_QUEUE_ELEMENT`.
+#[repr(C)]
pub(crate) struct GspMsgElement {
- inner: bindings::GSP_MSG_QUEUE_ELEMENT,
+ mctp_magic: u32,
+ mctp_payload_size: u32,
+ mctp_header: MctpHeader,
+ nvdm_header: NvdmHeader,
+ nvdm_payload_size: u32,
+ reserved: u32,
+ rpc: bindings::rpc_message_header_v,
}
impl GspMsgElement {
@@ -875,81 +532,76 @@ impl GspMsgElement {
///
/// # Arguments
///
- /// * `elem_seq` - Transport sequence number of the queue element (`seqNum`).
- /// * `rpc_seq` - RPC sequence number, echoed by the GSP in the reply.
+ /// * `rpc_seq` - RPC sequence number, echoed by the GSP in the reply. Zero for an async
+ /// command, which expects none.
/// * `cmd_size` - Size of the command (not including the message element), in bytes.
/// * `function` - Function of the message.
pub(crate) fn init(
- elem_seq: u32,
rpc_seq: u32,
cmd_size: usize,
function: MsgFunction,
) -> impl Init<Self, Error> {
type RpcMessageHeader = bindings::rpc_message_header_v;
- type InnerGspMsgElement = bindings::GSP_MSG_QUEUE_ELEMENT;
- let init_inner = try_init!(InnerGspMsgElement {
- seqNum: elem_seq,
- elemCount: size_of::<Self>()
+
+ try_init!(GspMsgElement {
+ mctp_magic: MCTP_MAGIC,
+ // Despite the name, this counts the element header as well as the payload.
+ mctp_payload_size: size_of::<Self>()
+ .checked_add(cmd_size)
+ .ok_or(EOVERFLOW)?
+ .try_into()
+ .map_err(|_| EOVERFLOW)?,
+ mctp_header: MctpHeader::single_packet(),
+ nvdm_header: NvdmHeader::new(NvdmType::RmRpc),
+ nvdm_payload_size: size_of::<RpcMessageHeader>()
.checked_add(cmd_size)
.ok_or(EOVERFLOW)?
- .div_ceil(GSP_PAGE_SIZE)
.try_into()
.map_err(|_| EOVERFLOW)?,
+ reserved: 0,
rpc <- RpcMessageHeader::init(rpc_seq, cmd_size, function),
- ..Zeroable::init_zeroed()
- });
-
- try_init!(GspMsgElement {
- inner <- init_inner,
})
}
- /// Sets the checksum of this message.
- ///
- /// Since the header is also part of the checksum, this is usually called after the whole
- /// message has been written to the shared memory area.
- pub(crate) fn set_checksum(&mut self, checksum: u32) {
- self.inner.checkSum = checksum;
- }
-
- /// Returns the length of the message's payload.
+ /// Returns the length of the message's payload (command data after the RPC header).
pub(crate) fn payload_length(&self) -> usize {
- // `rpc.length` includes the length of the RPC message header.
- num::u32_as_usize(self.inner.rpc.length)
+ num::u32_as_usize(self.nvdm_payload_size)
.saturating_sub(size_of::<bindings::rpc_message_header_v>())
}
- /// Returns the total length of the message, message and RPC headers included.
+ /// Returns the total length of the message, transport and RPC headers included.
pub(crate) fn length(&self) -> usize {
- size_of::<Self>() + self.payload_length()
+ num::u32_as_usize(self.mctp_payload_size)
+ }
+
+ /// Returns `true` if the MCTP magic field contains the expected value.
+ pub(crate) fn has_valid_magic(&self) -> bool {
+ self.mctp_magic == MCTP_MAGIC
}
// Returns the sequence number of the message.
pub(crate) fn sequence(&self) -> u32 {
- self.inner.rpc.sequence
+ self.rpc.sequence
}
// Returns the function of the message, if it is valid, or the invalid function number as an
// error.
pub(crate) fn function(&self) -> Result<MsgFunction, u32> {
- self.inner
- .rpc
- .function
- .try_into()
- .map_err(|_| self.inner.rpc.function)
+ self.rpc.function.try_into().map_err(|_| self.rpc.function)
}
// Returns the number of elements (i.e. memory pages) used by this message.
pub(crate) fn element_count(&self) -> u32 {
- self.inner.elemCount
+ self.mctp_payload_size
+ .div_ceil(num::usize_into_u32::<GSP_PAGE_SIZE>())
}
}
-// SAFETY: Padding is explicit and does not contain uninitialized data.
+// SAFETY: All fields are integer types or contain only integer types, with no
+// uninitialized padding bytes.
unsafe impl AsBytes for GspMsgElement {}
-// SAFETY: This struct only contains integer types for which all bit patterns
-// are valid.
+// SAFETY: All fields are integer types for which all bit patterns are valid.
unsafe impl FromBytes for GspMsgElement {}
/// Magic value that opens every MCTP-framed queue element: `"MCTP"` in ASCII.
@@ -981,17 +633,17 @@ pub(crate) struct GmcApiHeader {
/// GMC command that hands GSP-RM its system information and registry keys and returns the static
/// GPU configuration. Its reply is also what signals that GSP-RM has finished starting.
-pub(crate) const GMCAPI_CMD_GSP_INIT: u32 = r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_GSP_INIT;
+pub(crate) const GMCAPI_CMD_GSP_INIT: u32 = bindings::GMCAPI_COMMANDS_GMCAPI_CMD_GSP_INIT;
/// GMC command asking the driver to run the generic falcon bootloader against a descriptor the
/// GSP supplies.
pub(crate) const GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER: u32 =
- r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER;
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER;
/// GMC command asking the driver to run a high-security binary the GSP has placed in the
/// framebuffer.
pub(crate) const GMCAPI_CMD_EXEC_HS_BINARY: u32 =
- r000_00::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_HS_BINARY;
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_EXEC_HS_BINARY;
static_assert!(size_of::<GmcApiHeader>() == 40);
@@ -1026,19 +678,19 @@ pub(crate) struct GspGmcMsgElement {
static_assert!(
core::mem::offset_of!(GspGmcMsgElement, mctp_magic)
- == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpMagic)
+ == core::mem::offset_of!(bindings::GSP_MSG_QUEUE_ELEMENT, mctpMagic)
);
static_assert!(
core::mem::offset_of!(GspGmcMsgElement, mctp_payload_size)
- == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpPayloadSize)
+ == core::mem::offset_of!(bindings::GSP_MSG_QUEUE_ELEMENT, mctpPayloadSize)
);
static_assert!(
core::mem::offset_of!(GspGmcMsgElement, mctp_header)
- == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, mctpHeader)
+ == core::mem::offset_of!(bindings::GSP_MSG_QUEUE_ELEMENT, mctpHeader)
);
static_assert!(
core::mem::offset_of!(GspGmcMsgElement, nvdm_header)
- == core::mem::offset_of!(r000_00::GSP_MSG_QUEUE_ELEMENT, nvdmHeader)
+ == core::mem::offset_of!(bindings::GSP_MSG_QUEUE_ELEMENT, nvdmHeader)
);
impl GspGmcMsgElement {
@@ -1117,6 +769,20 @@ unsafe impl AsBytes for GspGmcMsgElement {}
// SAFETY: All fields are integer types for which all bit patterns are valid.
unsafe impl FromBytes for GspGmcMsgElement {}
+/// Optional bindata (ucodes) firmware info for GSP startup arguments.
+pub(crate) struct BindataArgs {
+ /// DMA address of the radix3 level 0 page table for the bindata firmware.
+ pub(crate) radix3: u64,
+ /// Size in bytes of the bindata firmware.
+ pub(crate) size: u64,
+}
+
+/// Magic value for the `GSP_ARGUMENTS_CACHED` header.
+const GSP_ARGUMENTS_MAGIC_VALUE: u32 = 0x2050_5347;
+
+/// Flag indicating the GSP stack should be placed in DMEM.
+const GSP_ARGUMENTS_FLAG_STACK_IN_DMEM: u64 = 0x02;
+
/// Arguments for GSP startup.
#[repr(transparent)]
#[derive(Zeroable)]
@@ -1126,16 +792,32 @@ pub(crate) struct GspArgumentsCached {
impl GspArgumentsCached {
/// Creates the arguments for starting the GSP up using `cmdq` as its command queue.
- pub(crate) fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
- let init_inner = init!(bindings::GSP_ARGUMENTS_CACHED {
- messageQueueInitArguments <- MessageQueueInitArguments::new(cmdq),
- bDmemStack: 1,
- ..Zeroable::init_zeroed()
- });
+ ///
+ /// `bindata` names the ucodes firmware, if the driver found one.
+ ///
+ /// `state_monitor` is the buffer GSP-RM maps during init to report its own state.
+ pub(crate) fn new(
+ cmdq: &Cmdq,
+ bindata: Option<&BindataArgs>,
+ state_monitor: &Coherent<[u8; GSP_PAGE_SIZE]>,
+ ) -> Self {
+ let mut args = bindings::GSP_ARGUMENTS_CACHED {
+ magic: GSP_ARGUMENTS_MAGIC_VALUE,
+ size: num::usize_into_u16::<{ size_of::<bindings::GSP_ARGUMENTS_CACHED>() }>(),
+ flags: GSP_ARGUMENTS_FLAG_STACK_IN_DMEM,
+ messageQueueInitArguments: MessageQueueInitArguments::new(cmdq),
+ ..Default::default()
+ };
+
+ if let Some(bindata) = bindata {
+ args.bindataArgs.radix3 = bindata.radix3;
+ args.bindataArgs.size = bindata.size;
+ }
- init!(GspArgumentsCached {
- inner <- init_inner,
- })
+ args.rmStateMonitorBufferArgs.pa = state_monitor.dma_address();
+ args.rmStateMonitorBufferArgs.size = num::usize_as_u64(state_monitor.size());
+
+ Self { inner: args }
}
}
@@ -1153,12 +835,26 @@ pub(crate) struct GspArgumentsPadded {
}
impl GspArgumentsPadded {
- pub(crate) fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
+ pub(crate) fn new<'a>(
+ cmdq: &'a Cmdq,
+ bindata: Option<&'a BindataArgs>,
+ state_monitor: &'a Coherent<[u8; GSP_PAGE_SIZE]>,
+ ) -> impl Init<Self> + 'a {
init!(GspArgumentsPadded {
- inner <- GspArgumentsCached::new(cmdq),
+ inner: GspArgumentsCached::new(cmdq, bindata, state_monitor),
..Zeroable::init_zeroed()
})
}
+
+ /// Updates the optional bindata mapping before GSP-RM starts reading its arguments.
+ pub(crate) fn set_bindata(this: &Coherent<Self>, bindata: Option<&BindataArgs>) {
+ let (radix3, size) = bindata
+ .map(|bindata| (bindata.radix3, bindata.size))
+ .unwrap_or((0, 0));
+
+ io_write!(this, .inner.inner.bindataArgs.radix3, radix3);
+ io_write!(this, .inner.inner.bindataArgs.size, size);
+ }
}
// SAFETY: Padding is explicit and will not contain uninitialized data.
@@ -1173,14 +869,23 @@ unsafe impl FromBytes for GspArgumentsPadded {}
impl MessageQueueInitArguments {
/// Creates a new init arguments structure for `cmdq`.
- fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
- init!(MessageQueueInitArguments {
+ fn new(cmdq: &Cmdq) -> Self {
+ MessageQueueInitArguments {
sharedMemPhysAddr: cmdq.dma_addr,
pageTableEntryCount: num::usize_into_u32::<{ Cmdq::NUM_PTES }>(),
cmdQueueOffset: num::usize_as_u64(Cmdq::CMDQ_OFFSET),
statQueueOffset: num::usize_as_u64(Cmdq::STATQ_OFFSET),
- ..Zeroable::init_zeroed()
- })
+
+ queueElementHdrSize: num::usize_into_u32::<
+ { size_of::<GspMsgElement>() - size_of::<bindings::rpc_message_header_v>() },
+ >(),
+ queueElementSizeMin: num::usize_into_u32::<GSP_PAGE_SIZE>(),
+ queueElementSizeMax: num::usize_into_u32::<GSP_MSG_QUEUE_ELEMENT_SIZE_MAX>(),
+ queueHeaderAlign: 4,
+ queueElementAlign: num::usize_into_u32::<GSP_PAGE_SHIFT>(),
+
+ ..Default::default()
+ }
}
}
@@ -1203,7 +908,9 @@ fn new(target: GspDmaTarget, wpr_meta_addr: u64) -> impl Init<Self> {
bIsGspRmBoot: 1,
wprCarveoutOffset: 0,
wprCarveoutSize: 0,
- __bindgen_padding_0: Default::default(),
+ bInstInSysMode: 0,
+ bIcuEnabled: 0,
+ bScrubCbcSr: 0,
});
params
@@ -1216,8 +923,8 @@ impl GspRmParams {
fn new(target: GspDmaTarget, libos_addr: u64) -> impl Init<Self> {
let params = init!(Self {
target: target as u32,
+ reserved: 0,
bootArgsOffset: libos_addr,
- __bindgen_padding_0: Default::default(),
});
params
@@ -1226,6 +933,9 @@ fn new(target: GspDmaTarget, libos_addr: u64) -> impl Init<Self> {
pub(crate) type GspFmcBootParams = bindings::GSP_FMC_BOOT_PARAMS;
+/// Magic value opening the ABI-stable `GSP_FMC_BOOT_PARAMS` header: `"FMC "` in ASCII.
+const GSP_FMC_BOOT_PARAMS_MAGIC: u32 = 0x2043_4d46;
+
// SAFETY: Padding is explicit and will not contain uninitialized data.
unsafe impl AsBytes for GspFmcBootParams {}
// SAFETY: This struct only contains integer types for which all bit patterns are valid.
@@ -1234,6 +944,8 @@ unsafe impl FromBytes for GspFmcBootParams {}
impl GspFmcBootParams {
pub(crate) fn new(wpr_meta_addr: u64, libos_addr: u64) -> impl Init<Self> {
let init = init!(Self {
+ magic: GSP_FMC_BOOT_PARAMS_MAGIC,
+ size: num::usize_into_u16::<{ size_of::<Self>() }>(),
// Blackwell FSP obtains WPR info from other sources, so
// wprCarveoutOffset and wprCarveoutSize are left zero.
bootGspRmParams <- GspAcrBootGspRmParams::new(GspDmaTarget::CoherentSystem,
diff --git a/drivers/gpu/nova-core/gsp/hal.rs b/drivers/gpu/nova-core/gsp/hal.rs
index 5850fa0fe0e9..ba98347cbbef 100644
--- a/drivers/gpu/nova-core/gsp/hal.rs
+++ b/drivers/gpu/nova-core/gsp/hal.rs
@@ -41,19 +41,6 @@ fn boot(
ctx: &mut GspBootContext<'_, '_>,
gsp_fw: &GspFirmware,
) -> Result<Option<crate::gsp::UnloadBundle>>;
-
- /// Performs HAL-specific post-GSP boot tasks.
- ///
- /// This method is called by the GSP boot code after the GSP is confirmed to be running, and
- /// after the initialization commands have been pushed onto its queue.
- fn post_boot(
- &self,
- _gsp: &Gsp,
- _ctx: &mut GspBootContext<'_, '_>,
- _gsp_fw: &GspFirmware,
- ) -> Result {
- Ok(())
- }
}
/// Returns the names of the firmware files required to boot the GSP of `chipset`, in addition to
@@ -75,6 +62,15 @@ pub(crate) const fn boot_firmware_files(chipset: Chipset) -> &'static [&'static
}
}
+/// Returns `true` if GSP-RM on `chipset` loads its images through the generic falcon bootloader.
+///
+/// Turing and GA100 raise `GMCAPI_CMD_EXEC_GENERIC_BOOTLOADER` during boot and need the
+/// `gen_bootloader.tlv` image to service it. GA102 and later raise `GMCAPI_CMD_EXEC_HS_BINARY`
+/// instead, and [`boot_firmware_files`] ships them no such image.
+pub(super) const fn uses_generic_bootloader(chipset: Chipset) -> bool {
+ matches!(chipset.arch(), Architecture::Turing) || matches!(chipset, Chipset::GA100)
+}
+
/// Returns the GSP HAL to be used for `chipset`.
pub(super) fn gsp_hal(chipset: Chipset) -> &'static dyn GspHal {
match chipset.arch() {
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index 68a48c882c0f..7d685fa0eb30 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -40,7 +40,6 @@
UnloadBundle, //
},
regs,
- sequencer::GspSequencer,
Gsp,
GspBootContext,
GspFwWprMeta, //
@@ -316,17 +315,6 @@ fn boot(
Ok(unload_guard.dismiss())
}
-
- fn post_boot(
- &self,
- gsp: &Gsp,
- ctx: &mut GspBootContext<'_, '_>,
- gsp_fw: &GspFirmware,
- ) -> Result {
- GspSequencer::run(&gsp.cmdq, ctx, &gsp.libos, gsp_fw.bootloader.app_version)?;
-
- Ok(())
- }
}
/// The TU102 HAL requires the use of the FWSEC bootloader.
diff --git a/drivers/gpu/nova-core/gsp/sequencer.rs b/drivers/gpu/nova-core/gsp/sequencer.rs
deleted file mode 100644
index e2f1da129d8f..000000000000
--- a/drivers/gpu/nova-core/gsp/sequencer.rs
+++ /dev/null
@@ -1,379 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-//! GSP Sequencer implementation for Pre-hopper GSP boot sequence.
-
-use core::array;
-
-use kernel::{
- device,
- dma::Coherent,
- io::{
- poll::read_poll_timeout,
- Io, //
- },
- prelude::*,
- time::{
- delay::fsleep,
- Delta, //
- },
- transmute::FromBytes, //
-};
-
-use crate::{
- driver::Bar0,
- falcon::{
- gsp::Gsp,
- sec2::Sec2,
- Falcon, //
- },
- gsp::{
- cmdq::{
- Cmdq,
- MessageFromGsp, //
- },
- fw,
- GspBootContext,
- LibosMemoryRegionInitArgument, //
- },
- num::FromSafeCast,
- sbuffer::SBufferIter,
-};
-
-/// GSP Sequencer information containing the command sequence and data.
-struct GspSequence {
- /// Current command index for error reporting.
- cmd_index: u32,
- /// Command data buffer containing the sequence of commands.
- cmd_data: KVec<u8>,
-}
-
-impl MessageFromGsp for GspSequence {
- const FUNCTION: fw::MsgFunction = fw::MsgFunction::GspRunCpuSequencer;
- type InitError = Error;
- type Message = fw::RunCpuSequencer;
-
- fn read(
- msg: &Self::Message,
- sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- let cmd_data = sbuffer.flush_into_kvec(GFP_KERNEL)?;
- Ok(GspSequence {
- cmd_index: msg.cmd_index(),
- cmd_data,
- })
- }
-}
-
-const CMD_SIZE: usize = size_of::<fw::SequencerBufferCmd>();
-
-/// GSP Sequencer Command types with payload data.
-/// Commands have an opcode and an opcode-dependent struct.
-#[allow(clippy::enum_variant_names)]
-#[derive(Debug)]
-pub(crate) enum GspSeqCmd {
- RegWrite(fw::RegWritePayload),
- RegModify(fw::RegModifyPayload),
- RegPoll(fw::RegPollPayload),
- DelayUs(fw::DelayUsPayload),
- RegStore(fw::RegStorePayload),
- CoreReset,
- CoreStart,
- CoreWaitForHalt,
- CoreResume,
-}
-
-impl GspSeqCmd {
- /// Creates a new `GspSeqCmd` from raw data returning the command and its size in bytes.
- pub(crate) fn new(data: &[u8], dev: &device::Device) -> Result<(Self, usize)> {
- let fw_cmd = fw::SequencerBufferCmd::from_bytes(data).ok_or(EINVAL)?;
- let opcode_size = core::mem::size_of::<u32>();
-
- let (cmd, size) = match fw_cmd.opcode()? {
- fw::SeqBufOpcode::RegWrite => {
- let payload = fw_cmd.reg_write_payload()?;
- let size = opcode_size + size_of_val(&payload);
- (GspSeqCmd::RegWrite(payload), size)
- }
- fw::SeqBufOpcode::RegModify => {
- let payload = fw_cmd.reg_modify_payload()?;
- let size = opcode_size + size_of_val(&payload);
- (GspSeqCmd::RegModify(payload), size)
- }
- fw::SeqBufOpcode::RegPoll => {
- let payload = fw_cmd.reg_poll_payload()?;
- let size = opcode_size + size_of_val(&payload);
- (GspSeqCmd::RegPoll(payload), size)
- }
- fw::SeqBufOpcode::DelayUs => {
- let payload = fw_cmd.delay_us_payload()?;
- let size = opcode_size + size_of_val(&payload);
- (GspSeqCmd::DelayUs(payload), size)
- }
- fw::SeqBufOpcode::RegStore => {
- let payload = fw_cmd.reg_store_payload()?;
- let size = opcode_size + size_of_val(&payload);
- (GspSeqCmd::RegStore(payload), size)
- }
- fw::SeqBufOpcode::CoreReset => (GspSeqCmd::CoreReset, opcode_size),
- fw::SeqBufOpcode::CoreStart => (GspSeqCmd::CoreStart, opcode_size),
- fw::SeqBufOpcode::CoreWaitForHalt => (GspSeqCmd::CoreWaitForHalt, opcode_size),
- fw::SeqBufOpcode::CoreResume => (GspSeqCmd::CoreResume, opcode_size),
- };
-
- if data.len() < size {
- dev_err!(dev, "Data is not enough for command\n");
- return Err(EINVAL);
- }
-
- Ok((cmd, size))
- }
-}
-
-/// GSP Sequencer for executing firmware commands during boot.
-pub(crate) struct GspSequencer<'a> {
- /// `Bar0` for register access.
- bar: Bar0<'a>,
- /// SEC2 falcon for core operations.
- sec2_falcon: &'a Falcon<'a, Sec2>,
- /// GSP falcon for core operations.
- gsp_falcon: &'a Falcon<'a, Gsp>,
- /// LibOS memory region init arguments.
- libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
- /// Bootloader application version.
- bootloader_app_version: u32,
- /// Device for logging.
- dev: &'a device::Device,
-}
-
-impl fw::RegWritePayload {
- fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
- let addr = usize::from_safe_cast(self.addr());
-
- sequencer.bar.try_write32(self.val(), addr)
- }
-}
-
-impl fw::RegModifyPayload {
- fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
- let addr = usize::from_safe_cast(self.addr());
-
- sequencer.bar.try_read32(addr).and_then(|val| {
- sequencer
- .bar
- .try_write32((val & !self.mask()) | self.val(), addr)
- })
- }
-}
-
-impl fw::RegPollPayload {
- fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
- let addr = usize::from_safe_cast(self.addr());
-
- // Default timeout to 4 seconds.
- let timeout_us = if self.timeout() == 0 {
- 4_000_000
- } else {
- i64::from(self.timeout())
- };
-
- // First read.
- sequencer.bar.try_read32(addr)?;
-
- // Poll the requested register with requested timeout.
- read_poll_timeout(
- || sequencer.bar.try_read32(addr),
- |current| (current & self.mask()) == self.val(),
- Delta::ZERO,
- Delta::from_micros(timeout_us),
- )
- .map(|_| ())
- }
-}
-
-impl fw::DelayUsPayload {
- fn run(&self, _sequencer: &GspSequencer<'_>) -> Result {
- fsleep(Delta::from_micros(i64::from(self.val())));
- Ok(())
- }
-}
-
-impl fw::RegStorePayload {
- fn run(&self, sequencer: &GspSequencer<'_>) -> Result {
- let addr = usize::from_safe_cast(self.addr());
-
- sequencer.bar.try_read32(addr).map(|_| ())
- }
-}
-
-impl GspSeqCmd {
- fn run(&self, seq: &GspSequencer<'_>) -> Result {
- match self {
- GspSeqCmd::RegWrite(cmd) => cmd.run(seq),
- GspSeqCmd::RegModify(cmd) => cmd.run(seq),
- GspSeqCmd::RegPoll(cmd) => cmd.run(seq),
- GspSeqCmd::DelayUs(cmd) => cmd.run(seq),
- GspSeqCmd::RegStore(cmd) => cmd.run(seq),
- GspSeqCmd::CoreReset => {
- seq.gsp_falcon.reset()?;
- seq.gsp_falcon.dma_reset();
- Ok(())
- }
- GspSeqCmd::CoreStart => {
- seq.gsp_falcon.start()?;
- Ok(())
- }
- GspSeqCmd::CoreWaitForHalt => {
- seq.gsp_falcon.wait_till_halted()?;
- Ok(())
- }
- GspSeqCmd::CoreResume => {
- // At this point, 'SEC2-RTOS' has been loaded into SEC2 by the sequencer
- // but neither SEC2-RTOS nor GSP-RM is running yet. This part of the
- // sequencer will start both.
-
- // Reset the GSP to prepare it for resuming.
- seq.gsp_falcon.reset()?;
-
- let libos_dma_address = seq.libos.dma_address();
-
- // Write the libOS DMA address to GSP mailboxes.
- seq.gsp_falcon.write_mailboxes(
- Some(libos_dma_address as u32),
- Some((libos_dma_address >> 32) as u32),
- );
-
- // Start the SEC2 falcon which will trigger GSP-RM to resume on the GSP.
- seq.sec2_falcon.start()?;
-
- // Poll until GSP-RM reload/resume has completed (up to 2 seconds).
- seq.gsp_falcon.check_reload_completed(Delta::from_secs(2))?;
-
- // Verify SEC2 completed successfully by checking its mailbox for errors.
- let mbox0 = seq.sec2_falcon.read_mailbox0();
- if mbox0 != 0 {
- dev_err!(seq.dev, "Sequencer: sec2 errors: {:?}\n", mbox0);
- return Err(EIO);
- }
-
- // Configure GSP with the bootloader version.
- seq.gsp_falcon.write_os_version(seq.bootloader_app_version);
-
- // Verify the GSP's RISC-V core is active indicating successful GSP boot.
- if !seq.gsp_falcon.is_riscv_active() {
- dev_err!(seq.dev, "Sequencer: RISC-V core is not active\n");
- return Err(EIO);
- }
- Ok(())
- }
- }
- }
-}
-
-/// Iterator over GSP sequencer commands.
-struct GspSeqIter<'a> {
- /// Command data buffer.
- cmd_data: &'a [u8],
- /// Current position in the buffer.
- current_offset: usize,
- /// Total number of commands to process.
- total_cmds: u32,
- /// Number of commands processed so far.
- cmds_processed: u32,
- /// Device for logging.
- dev: &'a device::Device,
-}
-
-impl<'a> GspSeqIter<'a> {
- fn new(seq: &'a GspSequence, dev: &'a device::Device) -> Self {
- Self {
- cmd_data: &seq.cmd_data,
- current_offset: 0,
- total_cmds: seq.cmd_index,
- cmds_processed: 0,
- dev,
- }
- }
-}
-
-impl<'a> Iterator for GspSeqIter<'a> {
- type Item = Result<GspSeqCmd>;
-
- fn next(&mut self) -> Option<Self::Item> {
- // Stop if we've processed all commands or reached the end of data.
- if self.cmds_processed >= self.total_cmds || self.current_offset >= self.cmd_data.len() {
- return None;
- }
-
- // Check if we have enough data for opcode.
- if self.current_offset + core::mem::size_of::<u32>() > self.cmd_data.len() {
- return Some(Err(EIO));
- }
-
- let offset = self.current_offset;
-
- // Handle command creation based on available data,
- // zero-pad if necessary (since last command may not be full size).
- let mut buffer = [0u8; CMD_SIZE];
- let copy_len = if offset + CMD_SIZE <= self.cmd_data.len() {
- CMD_SIZE
- } else {
- self.cmd_data.len() - offset
- };
- buffer[..copy_len].copy_from_slice(&self.cmd_data[offset..offset + copy_len]);
- let cmd_result = GspSeqCmd::new(&buffer, self.dev);
-
- cmd_result.map_or_else(
- |_err| {
- dev_err!(self.dev, "Error parsing command at offset {}\n", offset);
- None
- },
- |(cmd, size)| {
- self.current_offset += size;
- self.cmds_processed += 1;
- Some(Ok(cmd))
- },
- )
- }
-}
-
-impl<'a> GspSequencer<'a> {
- pub(crate) fn run(
- cmdq: &Cmdq,
- ctx: &'a GspBootContext<'_, '_>,
- libos: &'a Coherent<[LibosMemoryRegionInitArgument]>,
- bootloader_app_version: u32,
- ) -> Result {
- let seq_info = cmdq.await_msg::<GspSequence>()?;
-
- let sequencer = GspSequencer {
- bar: ctx.bar,
- sec2_falcon: ctx.sec2_falcon,
- gsp_falcon: ctx.gsp_falcon,
- libos,
- bootloader_app_version,
- dev: ctx.dev(),
- };
-
- dev_dbg!(sequencer.dev, "Running CPU Sequencer commands\n");
-
- for cmd_result in GspSeqIter::new(&seq_info, sequencer.dev) {
- match cmd_result {
- Ok(cmd) => cmd.run(&sequencer)?,
- Err(e) => {
- dev_err!(
- sequencer.dev,
- "Error running command at index {}\n",
- seq_info.cmd_index
- );
- return Err(e);
- }
- }
- }
-
- dev_dbg!(
- sequencer.dev,
- "CPU Sequencer commands completed successfully\n"
- );
- Ok(())
- }
-}
diff --git a/drivers/gpu/nova-core/irq/gsp.rs b/drivers/gpu/nova-core/irq/gsp.rs
index ee7d13b14a88..adc4866bf490 100644
--- a/drivers/gpu/nova-core/irq/gsp.rs
+++ b/drivers/gpu/nova-core/irq/gsp.rs
@@ -178,7 +178,7 @@ fn handle(&self) -> irq::ThreadedIrqReturn {
/// IRQ thread: drains and dispatches the GSP-to-CPU message queue.
fn handle_threaded(&self) -> irq::IrqReturn {
- if let Err(e) = self.cmdq.drain() {
+ if let Err(e) = self.cmdq.drain(self.bar) {
// A queue that fails to drain cannot advance past the message that failed, so every
// later notification would repeat this failure. Disable the source instead.
self.tree
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index 0ae88bef2a05..3e94b7f3a6b4 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -28,6 +28,8 @@ pub(crate) enum NvdmType with TryFrom<Bounded<u32, 8>> {
Cot = 0x14,
/// FSP command response.
FspResponse = 0x15,
+ /// GSP-RM RPC message.
+ RmRpc = 0x25,
/// GMC API message (GSP command queue).
GmcApi = 0x26,
}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (24 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 25/27] gpu: nova-core: switch to the r000 GSP firmware John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
2026-08-19 3:52 ` [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings John Hubbard
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
The r000 boot path folds the system information and the static GPU
configuration into the GSP_INIT request, whose reply carries that
configuration back to the driver.
The set-system-info and get-static-info commands stayed behind after the
switch, unreachable and marked dead, along with the two payload
structures that encoded them.
Remove both commands and their payloads, and keep the decoded static
configuration that the GSP_INIT reply fills in. The two message-function
entries stay, because that table catalogs the wire protocol rather than
what the driver implements, and already names many functions nova-core
never sends.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/gsp.rs | 8 --
drivers/gpu/nova-core/gsp/commands.rs | 65 +------------
drivers/gpu/nova-core/gsp/fw/commands.rs | 115 +----------------------
3 files changed, 3 insertions(+), 185 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 8e912eaf8e2e..b128153fda86 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -407,14 +407,6 @@ pub(crate) fn new(
})
}
- /// Query the GSP for the static GPU information.
- ///
- /// The r000 boot path gets the same information from the `GSP_INIT` reply instead.
- #[expect(dead_code)]
- pub(crate) fn get_static_info(&self, bar: Bar0<'_>) -> Result<commands::GetGspStaticInfoReply> {
- self.cmdq.send_command(bar, commands::GetGspStaticInfo)
- }
-
/// Returns a shared handle to the GSP command queue.
pub(crate) fn cmdq(&self) -> Arc<Cmdq> {
self.cmdq.clone()
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 26ea07dc4a28..9079393da0b3 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -49,34 +49,6 @@
vgpu::VgpuState, //
};
-/// The `GspSetSystemInfo` command.
-///
-/// The r000 boot path folds this into the `GSP_INIT` payload instead.
-pub(crate) struct SetSystemInfo<'a> {
- pdev: &'a pci::Device<device::Bound>,
- chipset: Chipset,
-}
-
-#[expect(dead_code)]
-impl<'a> SetSystemInfo<'a> {
- /// Creates a new `GspSetSystemInfo` command using the parameters of `pdev`.
- pub(crate) fn new(pdev: &'a pci::Device<device::Bound>, chipset: Chipset) -> Self {
- Self { pdev, chipset }
- }
-}
-
-impl<'a> CommandToGsp for SetSystemInfo<'a> {
- const FUNCTION: MsgFunction = MsgFunction::GspSetSystemInfo;
- const IS_ASYNC: bool = true;
- type Command = fw::commands::GspSetSystemInfo;
- type Reply = NoReply;
- type InitError = Error;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- Self::Command::init(self.pdev, self.chipset)
- }
-}
-
struct RegistryEntry {
key: &'static str,
value: u32,
@@ -186,48 +158,13 @@ fn init_variable_payload(
}
}
-/// The `GetGspStaticInfo` command.
-pub(crate) struct GetGspStaticInfo;
-
-impl CommandToGsp for GetGspStaticInfo {
- const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Command = fw::commands::GspStaticConfigInfo;
- type Reply = GetGspStaticInfoReply;
- type InitError = Infallible;
-
- fn init(&self) -> impl Init<Self::Command, Self::InitError> {
- Self::Command::init_zeroed()
- }
-}
-
-/// The reply from the GSP to the [`GetGspStaticInfo`] command.
+/// The static GPU configuration, as decoded from the `GSP_INIT` reply.
pub(crate) struct GetGspStaticInfoReply {
gpu_name: [u8; 64],
/// Usable FB (VRAM) regions for driver memory allocation.
pub(crate) usable_fb_regions: KVec<Range<u64>>,
}
-impl MessageFromGsp for GetGspStaticInfoReply {
- const FUNCTION: MsgFunction = MsgFunction::GetGspStaticInfo;
- type Message = fw::commands::GspStaticConfigInfo;
- type InitError = Error;
-
- fn read(
- msg: &Self::Message,
- _sbuffer: &mut SBufferIter<array::IntoIter<&[u8], 2>>,
- ) -> Result<Self, Self::InitError> {
- let mut usable_fb_regions = KVec::new();
- for region in msg.usable_fb_regions() {
- usable_fb_regions.push(region, GFP_KERNEL)?;
- }
-
- Ok(GetGspStaticInfoReply {
- gpu_name: msg.gpu_name_str(),
- usable_fb_regions,
- })
- }
-}
-
/// Error type for [`GetGspStaticInfoReply::gpu_name`].
#[derive(Debug)]
pub(crate) enum GpuNameError {
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 00e40a435053..e49234f35d0f 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -14,11 +14,7 @@
}, //
};
-use crate::{
- gpu::Chipset,
- gsp::GSP_PAGE_SIZE,
- num::IntoSafeCast, //
-};
+use crate::gpu::Chipset;
use crate::gsp::nvkv::{
nvkv_decode,
@@ -37,56 +33,6 @@
use super::bindings;
-/// Payload of the `GspSetSystemInfo` command.
-#[repr(transparent)]
-pub(crate) struct GspSetSystemInfo {
- inner: bindings::GspSystemInfo,
-}
-static_assert!(size_of::<GspSetSystemInfo>() < GSP_PAGE_SIZE);
-
-impl GspSetSystemInfo {
- /// Returns an in-place initializer for the `GspSetSystemInfo` command.
- pub(crate) fn init<'a>(
- dev: &'a pci::Device<device::Bound>,
- chipset: Chipset,
- ) -> impl Init<Self, Error> + 'a {
- type InnerGspSystemInfo = bindings::GspSystemInfo;
- let pci_config_mirror_range = chipset.pci_config_mirror_range();
- let init_inner = try_init!(InnerGspSystemInfo {
- gpuPhysAddr: dev.resource_start(0)?,
- gpuPhysFbAddr: dev.resource_start(1)?,
- gpuPhysInstAddr: dev.resource_start(3)?,
- nvDomainBusDeviceFunc: u64::from(dev.dev_id()),
-
- // Using TASK_SIZE in r535_gsp_rpc_set_system_info() seems wrong because
- // TASK_SIZE is per-task. That's probably a design issue in GSP-RM though.
- maxUserVa: (1 << 47) - 4096,
- pciConfigMirrorBase: pci_config_mirror_range.start,
- pciConfigMirrorSize: pci_config_mirror_range.end - pci_config_mirror_range.start,
-
- PCIDeviceID: (u32::from(dev.device_id()) << 16) | u32::from(dev.vendor_id().as_raw()),
- PCISubDeviceID: (u32::from(dev.subsystem_device_id()) << 16)
- | u32::from(dev.subsystem_vendor_id()),
- PCIRevisionID: u32::from(dev.revision_id()),
- bIsPrimary: 0,
- bPreserveVideoMemoryAllocations: 0,
- ..Zeroable::init_zeroed()
- });
-
- try_init!(GspSetSystemInfo {
- inner <- init_inner,
- })
- }
-}
-
-// SAFETY: These structs don't meet the no-padding requirements of AsBytes but
-// that is not a problem because they are not used outside the kernel.
-unsafe impl AsBytes for GspSetSystemInfo {}
-
-// SAFETY: These structs don't meet the no-padding requirements of FromBytes but
-// that is not a problem because they are not used outside the kernel.
-unsafe impl FromBytes for GspSetSystemInfo {}
-
#[repr(transparent)]
pub(crate) struct PackedRegistryEntry(bindings::PACKED_REGISTRY_ENTRY);
@@ -136,60 +82,6 @@ unsafe impl AsBytes for PackedRegistryTable {}
// are valid.
unsafe impl FromBytes for PackedRegistryTable {}
-/// Payload of the `GetGspStaticInfo` command and message.
-#[repr(transparent)]
-#[derive(Zeroable)]
-pub(crate) struct GspStaticConfigInfo(bindings::GspStaticConfigInfo_t);
-
-impl GspStaticConfigInfo {
- /// Returns a bytes array containing the (hopefully) zero-terminated name of this GPU.
- pub(crate) fn gpu_name_str(&self) -> [u8; 64] {
- self.0.gpuNameString
- }
-
- /// Returns an iterator over valid FB regions from GSP firmware data.
- fn fb_regions(
- &self,
- ) -> impl Iterator<Item = &bindings::NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO> {
- let fb_info = &self.0.fbRegionInfoParams;
- fb_info
- .fbRegion
- .iter()
- .take(fb_info.numFBRegions.into_safe_cast())
- .filter(|reg| reg.limit >= reg.base)
- }
-
- /// Iterates over usable FB regions from GSP firmware data.
- ///
- /// Each yielded region is a [`Range<u64>`] suitable for driver memory allocation.
- /// Usable regions are those that satisfy all the following properties:
- /// - Are not reserved for firmware internal use.
- /// - Are not protected (hardware-enforced access restrictions).
- /// - Support compression (can use GPU memory compression for bandwidth).
- /// - Support ISO (isochronous memory for display requiring guaranteed bandwidth).
- pub(crate) fn usable_fb_regions(&self) -> impl Iterator<Item = Range<u64>> + '_ {
- self.fb_regions().filter_map(|reg| {
- // Filter: not reserved, not protected, supports compression and ISO.
- if reg.reserved == 0
- && reg.bProtected == 0
- && reg.supportCompressed != 0
- && reg.supportISO != 0
- {
- reg.limit.checked_add(1).map(|end| reg.base..end)
- } else {
- None
- }
- })
- }
-}
-
-// SAFETY: Padding is explicit and will not contain uninitialized data.
-unsafe impl AsBytes for GspStaticConfigInfo {}
-
-// SAFETY: This struct only contains integer types for which all bit patterns
-// are valid.
-unsafe impl FromBytes for GspStaticConfigInfo {}
-
/// Power level requested to the [`UnloadingGuestDriver`] command.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
@@ -369,8 +261,6 @@ impl GspInitRequest {
const NV_DOMAIN_BUS_DEVICE_FUNC_KEY: KeyId = 0x1020;
/// Describes `dev` to GSP-RM and asks it to apply `regkeys`.
- ///
- /// The same identifiers reach GSP-RM through [`GspSetSystemInfo::init`] on the RPC path.
pub(crate) fn new(
dev: &pci::Device<device::Bound>,
chipset: Chipset,
@@ -442,8 +332,7 @@ pub(crate) fn gpu_name(&self) -> &[u8] {
/// Iterates over the FB regions the driver may allocate from.
///
/// A region qualifies when it is untagged, unprotected, and supports both compression and
- /// isochronous access, which is the same set the RPC path selects from
- /// [`GspStaticConfigInfo::usable_fb_regions`].
+ /// isochronous access.
pub(crate) fn usable_fb_regions(&self) -> impl Iterator<Item = Range<u64>> + '_ {
self.fb_regions.iter().filter_map(|region| {
if region.limit >= region.base
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread* [PATCH 27/27] gpu: nova-core: firmware: delete the r570 bindings
2026-08-19 3:51 [PATCH 00/27] gpu: nova-core: boot on the r000 GSP firmware John Hubbard
` (25 preceding siblings ...)
2026-08-19 3:52 ` [PATCH 26/27] gpu: nova-core: gsp: remove the retired system-info and static-info RPCs John Hubbard
@ 2026-08-19 3:52 ` John Hubbard
26 siblings, 0 replies; 28+ messages in thread
From: John Hubbard @ 2026-08-19 3:52 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
Cc: Timur Tabi, Alistair Popple, Eliot Courtney, Zhi Wang,
David Airlie, Simona Vetter, Bjorn Helgaas, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
nova-gpu, LKML, John Hubbard
Nothing referenced the r570 firmware bindings once the driver started
compiling against the r000 headers, but the module stayed declared and
its generated definitions kept being built.
Delete the module and its bindings, along with the last two comments
that named r570.
Assisted-by: Cursor:claude-opus-5
Signed-off-by: John Hubbard <jhubbard@nvidia.com>
---
drivers/gpu/nova-core/fsp.rs | 3 +-
drivers/gpu/nova-core/gsp/fw.rs | 4 +-
drivers/gpu/nova-core/gsp/fw/r570_144.rs | 31 -
.../gpu/nova-core/gsp/fw/r570_144/bindings.rs | 1060 -----------------
4 files changed, 3 insertions(+), 1095 deletions(-)
delete mode 100644 drivers/gpu/nova-core/gsp/fw/r570_144.rs
delete mode 100644 drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index ab685fb4168f..668d64e8c8db 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -256,7 +256,8 @@ fn frts_vidmem_offset(hal: &dyn hal::FspHal, fb_info: &FbSizes) -> Result<u64> {
// As per OpenRM's `kfspPrepareBootCommands_GH100`.
if fb_info.pmu_reserved_size != 0 {
offset = (offset + u64::from(fb_info.pmu_reserved_size))
- // The 2 MiB alignment is r570-specific.
+ // Open RM aligns this to WPR granularity, which is 128 KiB. 2 MiB is a
+ // multiple of that and reserves at least as much.
.align_up(Alignment::new::<SZ_2M>())
.ok_or(EINVAL)?;
}
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index c24808e41941..55e032db649b 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -3,7 +3,6 @@
pub(crate) mod commands;
mod r000_00;
-mod r570_144;
// Alias to avoid repeating the version number with every use.
use r000_00 as bindings;
@@ -512,10 +511,9 @@ fn init(sequence: u32, cmd_size: usize, function: MsgFunction) -> impl Init<Self
}
}
-/// GSP Message Element (r000 MCTP/NVDM format).
+/// GSP Message Element, in the MCTP/NVDM format.
///
/// This is the transport-layer header for messages exchanged with GSP-RM.
-/// r000 firmware uses MCTP/NVDM framing instead of the r570 `GSP_MSG_QUEUE_ELEMENT`.
#[repr(C)]
pub(crate) struct GspMsgElement {
mctp_magic: u32,
diff --git a/drivers/gpu/nova-core/gsp/fw/r570_144.rs b/drivers/gpu/nova-core/gsp/fw/r570_144.rs
deleted file mode 100644
index 2e6f0d298756..000000000000
--- a/drivers/gpu/nova-core/gsp/fw/r570_144.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-//! Firmware bindings.
-//!
-//! Imports the generated bindings by `bindgen`.
-//!
-//! This module may not be directly used. Please abstract or re-export the needed symbols in the
-//! parent module instead.
-
-#![allow(
- dead_code,
- clippy::all,
- clippy::undocumented_unsafe_blocks,
- clippy::ptr_as_ptr,
- clippy::ref_as_ptr,
- missing_docs,
- non_camel_case_types,
- non_upper_case_globals,
- non_snake_case,
- improper_ctypes,
- unreachable_pub,
- unsafe_op_in_unsafe_fn
-)]
-use kernel::ffi;
-use pin_init::MaybeZeroable;
-
-include!("r570_144/bindings.rs");
-
-// SAFETY: This type has a size of zero, so its inclusion into another type should not affect their
-// ability to implement `Zeroable`.
-unsafe impl<T> kernel::prelude::Zeroable for __IncompleteArrayField<T> {}
diff --git a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs b/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
deleted file mode 100644
index afe3e007f088..000000000000
--- a/drivers/gpu/nova-core/gsp/fw/r570_144/bindings.rs
+++ /dev/null
@@ -1,1060 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-#[repr(C)]
-#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::core::marker::PhantomData<T>, [T; 0]);
-impl<T> __IncompleteArrayField<T> {
- #[inline]
- pub const fn new() -> Self {
- __IncompleteArrayField(::core::marker::PhantomData, [])
- }
- #[inline]
- pub fn as_ptr(&self) -> *const T {
- self as *const _ as *const T
- }
- #[inline]
- pub fn as_mut_ptr(&mut self) -> *mut T {
- self as *mut _ as *mut T
- }
- #[inline]
- pub unsafe fn as_slice(&self, len: usize) -> &[T] {
- ::core::slice::from_raw_parts(self.as_ptr(), len)
- }
- #[inline]
- pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
- ::core::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
- }
-}
-impl<T> ::core::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
- fmt.write_str("__IncompleteArrayField")
- }
-}
-pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_0: u32 = 0;
-pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_3: u32 = 3;
-pub const NV2080_CTRL_GPU_SET_POWER_STATE_GPU_LEVEL_7: u32 = 7;
-pub const NV_VGPU_MSG_SIGNATURE_VALID: u32 = 1129337430;
-pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS2: u32 = 0;
-pub const GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS3_BAREMETAL: u32 = 23068672;
-pub const GSP_FW_HEAP_PARAM_BASE_RM_SIZE_TU10X: u32 = 8388608;
-pub const GSP_FW_HEAP_PARAM_BASE_RM_SIZE_GH100: u32 = 14680064;
-pub const GSP_FW_HEAP_PARAM_SIZE_PER_GB_FB: 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_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;
-pub const GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MAX_MB: u32 = 280;
-pub const GSP_FW_WPR_META_REVISION: u32 = 1;
-pub const GSP_FW_WPR_META_MAGIC: i64 = -2577556379034558285;
-pub const REGISTRY_TABLE_ENTRY_TYPE_DWORD: u32 = 1;
-pub const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: u32 = 65536;
-pub type __u8 = ffi::c_uchar;
-pub type __u16 = ffi::c_ushort;
-pub type __u32 = ffi::c_uint;
-pub type __u64 = ffi::c_ulonglong;
-pub type u8_ = __u8;
-pub type u16_ = __u16;
-pub type u32_ = __u32;
-pub type u64_ = __u64;
-pub const NV_VGPU_MSG_FUNCTION_NOP: _bindgen_ty_2 = 0;
-pub const NV_VGPU_MSG_FUNCTION_SET_GUEST_SYSTEM_INFO: _bindgen_ty_2 = 1;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_ROOT: _bindgen_ty_2 = 2;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_DEVICE: _bindgen_ty_2 = 3;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_MEMORY: _bindgen_ty_2 = 4;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_CTX_DMA: _bindgen_ty_2 = 5;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_CHANNEL_DMA: _bindgen_ty_2 = 6;
-pub const NV_VGPU_MSG_FUNCTION_MAP_MEMORY: _bindgen_ty_2 = 7;
-pub const NV_VGPU_MSG_FUNCTION_BIND_CTX_DMA: _bindgen_ty_2 = 8;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_OBJECT: _bindgen_ty_2 = 9;
-pub const NV_VGPU_MSG_FUNCTION_FREE: _bindgen_ty_2 = 10;
-pub const NV_VGPU_MSG_FUNCTION_LOG: _bindgen_ty_2 = 11;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_VIDMEM: _bindgen_ty_2 = 12;
-pub const NV_VGPU_MSG_FUNCTION_UNMAP_MEMORY: _bindgen_ty_2 = 13;
-pub const NV_VGPU_MSG_FUNCTION_MAP_MEMORY_DMA: _bindgen_ty_2 = 14;
-pub const NV_VGPU_MSG_FUNCTION_UNMAP_MEMORY_DMA: _bindgen_ty_2 = 15;
-pub const NV_VGPU_MSG_FUNCTION_GET_EDID: _bindgen_ty_2 = 16;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_DISP_CHANNEL: _bindgen_ty_2 = 17;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_DISP_OBJECT: _bindgen_ty_2 = 18;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_SUBDEVICE: _bindgen_ty_2 = 19;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_DYNAMIC_MEMORY: _bindgen_ty_2 = 20;
-pub const NV_VGPU_MSG_FUNCTION_DUP_OBJECT: _bindgen_ty_2 = 21;
-pub const NV_VGPU_MSG_FUNCTION_IDLE_CHANNELS: _bindgen_ty_2 = 22;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_EVENT: _bindgen_ty_2 = 23;
-pub const NV_VGPU_MSG_FUNCTION_SEND_EVENT: _bindgen_ty_2 = 24;
-pub const NV_VGPU_MSG_FUNCTION_REMAPPER_CONTROL: _bindgen_ty_2 = 25;
-pub const NV_VGPU_MSG_FUNCTION_DMA_CONTROL: _bindgen_ty_2 = 26;
-pub const NV_VGPU_MSG_FUNCTION_DMA_FILL_PTE_MEM: _bindgen_ty_2 = 27;
-pub const NV_VGPU_MSG_FUNCTION_MANAGE_HW_RESOURCE: _bindgen_ty_2 = 28;
-pub const NV_VGPU_MSG_FUNCTION_BIND_ARBITRARY_CTX_DMA: _bindgen_ty_2 = 29;
-pub const NV_VGPU_MSG_FUNCTION_CREATE_FB_SEGMENT: _bindgen_ty_2 = 30;
-pub const NV_VGPU_MSG_FUNCTION_DESTROY_FB_SEGMENT: _bindgen_ty_2 = 31;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_SHARE_DEVICE: _bindgen_ty_2 = 32;
-pub const NV_VGPU_MSG_FUNCTION_DEFERRED_API_CONTROL: _bindgen_ty_2 = 33;
-pub const NV_VGPU_MSG_FUNCTION_REMOVE_DEFERRED_API: _bindgen_ty_2 = 34;
-pub const NV_VGPU_MSG_FUNCTION_SIM_ESCAPE_READ: _bindgen_ty_2 = 35;
-pub const NV_VGPU_MSG_FUNCTION_SIM_ESCAPE_WRITE: _bindgen_ty_2 = 36;
-pub const NV_VGPU_MSG_FUNCTION_SIM_MANAGE_DISPLAY_CONTEXT_DMA: _bindgen_ty_2 = 37;
-pub const NV_VGPU_MSG_FUNCTION_FREE_VIDMEM_VIRT: _bindgen_ty_2 = 38;
-pub const NV_VGPU_MSG_FUNCTION_PERF_GET_PSTATE_INFO: _bindgen_ty_2 = 39;
-pub const NV_VGPU_MSG_FUNCTION_PERF_GET_PERFMON_SAMPLE: _bindgen_ty_2 = 40;
-pub const NV_VGPU_MSG_FUNCTION_PERF_GET_VIRTUAL_PSTATE_INFO: _bindgen_ty_2 = 41;
-pub const NV_VGPU_MSG_FUNCTION_PERF_GET_LEVEL_INFO: _bindgen_ty_2 = 42;
-pub const NV_VGPU_MSG_FUNCTION_MAP_SEMA_MEMORY: _bindgen_ty_2 = 43;
-pub const NV_VGPU_MSG_FUNCTION_UNMAP_SEMA_MEMORY: _bindgen_ty_2 = 44;
-pub const NV_VGPU_MSG_FUNCTION_SET_SURFACE_PROPERTIES: _bindgen_ty_2 = 45;
-pub const NV_VGPU_MSG_FUNCTION_CLEANUP_SURFACE: _bindgen_ty_2 = 46;
-pub const NV_VGPU_MSG_FUNCTION_UNLOADING_GUEST_DRIVER: _bindgen_ty_2 = 47;
-pub const NV_VGPU_MSG_FUNCTION_TDR_SET_TIMEOUT_STATE: _bindgen_ty_2 = 48;
-pub const NV_VGPU_MSG_FUNCTION_SWITCH_TO_VGA: _bindgen_ty_2 = 49;
-pub const NV_VGPU_MSG_FUNCTION_GPU_EXEC_REG_OPS: _bindgen_ty_2 = 50;
-pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_INFO: _bindgen_ty_2 = 51;
-pub const NV_VGPU_MSG_FUNCTION_ALLOC_VIRTMEM: _bindgen_ty_2 = 52;
-pub const NV_VGPU_MSG_FUNCTION_UPDATE_PDE_2: _bindgen_ty_2 = 53;
-pub const NV_VGPU_MSG_FUNCTION_SET_PAGE_DIRECTORY: _bindgen_ty_2 = 54;
-pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_PSTATE_INFO: _bindgen_ty_2 = 55;
-pub const NV_VGPU_MSG_FUNCTION_TRANSLATE_GUEST_GPU_PTES: _bindgen_ty_2 = 56;
-pub const NV_VGPU_MSG_FUNCTION_RESERVED_57: _bindgen_ty_2 = 57;
-pub const NV_VGPU_MSG_FUNCTION_RESET_CURRENT_GR_CONTEXT: _bindgen_ty_2 = 58;
-pub const NV_VGPU_MSG_FUNCTION_SET_SEMA_MEM_VALIDATION_STATE: _bindgen_ty_2 = 59;
-pub const NV_VGPU_MSG_FUNCTION_GET_ENGINE_UTILIZATION: _bindgen_ty_2 = 60;
-pub const NV_VGPU_MSG_FUNCTION_UPDATE_GPU_PDES: _bindgen_ty_2 = 61;
-pub const NV_VGPU_MSG_FUNCTION_GET_ENCODER_CAPACITY: _bindgen_ty_2 = 62;
-pub const NV_VGPU_MSG_FUNCTION_VGPU_PF_REG_READ32: _bindgen_ty_2 = 63;
-pub const NV_VGPU_MSG_FUNCTION_SET_GUEST_SYSTEM_INFO_EXT: _bindgen_ty_2 = 64;
-pub const NV_VGPU_MSG_FUNCTION_GET_GSP_STATIC_INFO: _bindgen_ty_2 = 65;
-pub const NV_VGPU_MSG_FUNCTION_RMFS_INIT: _bindgen_ty_2 = 66;
-pub const NV_VGPU_MSG_FUNCTION_RMFS_CLOSE_QUEUE: _bindgen_ty_2 = 67;
-pub const NV_VGPU_MSG_FUNCTION_RMFS_CLEANUP: _bindgen_ty_2 = 68;
-pub const NV_VGPU_MSG_FUNCTION_RMFS_TEST: _bindgen_ty_2 = 69;
-pub const NV_VGPU_MSG_FUNCTION_UPDATE_BAR_PDE: _bindgen_ty_2 = 70;
-pub const NV_VGPU_MSG_FUNCTION_CONTINUATION_RECORD: _bindgen_ty_2 = 71;
-pub const NV_VGPU_MSG_FUNCTION_GSP_SET_SYSTEM_INFO: _bindgen_ty_2 = 72;
-pub const NV_VGPU_MSG_FUNCTION_SET_REGISTRY: _bindgen_ty_2 = 73;
-pub const NV_VGPU_MSG_FUNCTION_GSP_INIT_POST_OBJGPU: _bindgen_ty_2 = 74;
-pub const NV_VGPU_MSG_FUNCTION_SUBDEV_EVENT_SET_NOTIFICATION: _bindgen_ty_2 = 75;
-pub const NV_VGPU_MSG_FUNCTION_GSP_RM_CONTROL: _bindgen_ty_2 = 76;
-pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_INFO2: _bindgen_ty_2 = 77;
-pub const NV_VGPU_MSG_FUNCTION_DUMP_PROTOBUF_COMPONENT: _bindgen_ty_2 = 78;
-pub const NV_VGPU_MSG_FUNCTION_UNSET_PAGE_DIRECTORY: _bindgen_ty_2 = 79;
-pub const NV_VGPU_MSG_FUNCTION_GET_CONSOLIDATED_STATIC_INFO: _bindgen_ty_2 = 80;
-pub const NV_VGPU_MSG_FUNCTION_GMMU_REGISTER_FAULT_BUFFER: _bindgen_ty_2 = 81;
-pub const NV_VGPU_MSG_FUNCTION_GMMU_UNREGISTER_FAULT_BUFFER: _bindgen_ty_2 = 82;
-pub const NV_VGPU_MSG_FUNCTION_GMMU_REGISTER_CLIENT_SHADOW_FAULT_BUFFER: _bindgen_ty_2 = 83;
-pub const NV_VGPU_MSG_FUNCTION_GMMU_UNREGISTER_CLIENT_SHADOW_FAULT_BUFFER: _bindgen_ty_2 = 84;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_VGPU_FB_USAGE: _bindgen_ty_2 = 85;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_NVFBC_SW_SESSION_UPDATE_INFO: _bindgen_ty_2 = 86;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_NVENC_SW_SESSION_UPDATE_INFO: _bindgen_ty_2 = 87;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESET_CHANNEL: _bindgen_ty_2 = 88;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESET_ISOLATED_CHANNEL: _bindgen_ty_2 = 89;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_HANDLE_VF_PRI_FAULT: _bindgen_ty_2 = 90;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CLK_GET_EXTENDED_INFO: _bindgen_ty_2 = 91;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_BOOST: _bindgen_ty_2 = 92;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_VPSTATES_GET_CONTROL: _bindgen_ty_2 = 93;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_ZBC_CLEAR_TABLE: _bindgen_ty_2 = 94;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_COLOR_CLEAR: _bindgen_ty_2 = 95;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_DEPTH_CLEAR: _bindgen_ty_2 = 96;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_SCHEDULE: _bindgen_ty_2 = 97;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_TIMESLICE: _bindgen_ty_2 = 98;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PREEMPT: _bindgen_ty_2 = 99;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_DISABLE_CHANNELS: _bindgen_ty_2 = 100;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_TSG_INTERLEAVE_LEVEL: _bindgen_ty_2 = 101;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_CHANNEL_INTERLEAVE_LEVEL: _bindgen_ty_2 = 102;
-pub const NV_VGPU_MSG_FUNCTION_GSP_RM_ALLOC: _bindgen_ty_2 = 103;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS_V2: _bindgen_ty_2 = 104;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_AES_ENCRYPT: _bindgen_ty_2 = 105;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_SESSION_KEY: _bindgen_ty_2 = 106;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CIPHER_SESSION_KEY_STATUS: _bindgen_ty_2 = 107;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_CLEAR_ALL_SM_ERROR_STATES: _bindgen_ty_2 = 108;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_READ_ALL_SM_ERROR_STATES: _bindgen_ty_2 = 109;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_EXCEPTION_MASK: _bindgen_ty_2 = 110;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_PROMOTE_CTX: _bindgen_ty_2 = 111;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_CTXSW_PREEMPTION_BIND: _bindgen_ty_2 = 112;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_SET_CTXSW_PREEMPTION_MODE: _bindgen_ty_2 = 113;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_CTXSW_ZCULL_BIND: _bindgen_ty_2 = 114;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_INITIALIZE_CTX: _bindgen_ty_2 = 115;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_VASPACE_COPY_SERVER_RESERVED_PDES: _bindgen_ty_2 = 116;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_CLEAR_FAULTED_BIT: _bindgen_ty_2 = 117;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_LATEST_ECC_ADDRESSES: _bindgen_ty_2 = 118;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_MC_SERVICE_INTERRUPTS: _bindgen_ty_2 = 119;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DMA_SET_DEFAULT_VASPACE: _bindgen_ty_2 = 120;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_CE_PCE_MASK: _bindgen_ty_2 = 121;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_ZBC_CLEAR_TABLE_ENTRY: _bindgen_ty_2 = 122;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_NVLINK_PEER_ID_MASK: _bindgen_ty_2 = 123;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_NVLINK_STATUS: _bindgen_ty_2 = 124;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS: _bindgen_ty_2 = 125;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_P2P_CAPS_MATRIX: _bindgen_ty_2 = 126;
-pub const NV_VGPU_MSG_FUNCTION_RESERVED_0: _bindgen_ty_2 = 127;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_PM_AREA_SMPC: _bindgen_ty_2 = 128;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_HWPM_LEGACY: _bindgen_ty_2 = 129;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_B0CC_EXEC_REG_OPS: _bindgen_ty_2 = 130;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_BIND_PM_RESOURCES: _bindgen_ty_2 = 131;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SUSPEND_CONTEXT: _bindgen_ty_2 = 132;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_RESUME_CONTEXT: _bindgen_ty_2 = 133;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_EXEC_REG_OPS: _bindgen_ty_2 = 134;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_MMU_DEBUG: _bindgen_ty_2 = 135;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_READ_SINGLE_SM_ERROR_STATE: _bindgen_ty_2 = 136;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_CLEAR_SINGLE_SM_ERROR_STATE: _bindgen_ty_2 = 137;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_ERRBAR_DEBUG: _bindgen_ty_2 = 138;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_NEXT_STOP_TRIGGER_TYPE: _bindgen_ty_2 = 139;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_ALLOC_PMA_STREAM: _bindgen_ty_2 = 140;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PMA_STREAM_UPDATE_GET_PUT: _bindgen_ty_2 = 141;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FB_GET_INFO_V2: _bindgen_ty_2 = 142;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_SET_CHANNEL_PROPERTIES: _bindgen_ty_2 = 143;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_GET_CTX_BUFFER_INFO: _bindgen_ty_2 = 144;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_KGR_GET_CTX_BUFFER_PTES: _bindgen_ty_2 = 145;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_EVICT_CTX: _bindgen_ty_2 = 146;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FB_GET_FS_INFO: _bindgen_ty_2 = 147;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GRMGR_GET_GR_FS_INFO: _bindgen_ty_2 = 148;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_STOP_CHANNEL: _bindgen_ty_2 = 149;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_PC_SAMPLING_MODE: _bindgen_ty_2 = 150;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_RATED_TDP_GET_STATUS: _bindgen_ty_2 = 151;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_RATED_TDP_SET_CONTROL: _bindgen_ty_2 = 152;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FREE_PMA_STREAM: _bindgen_ty_2 = 153;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_TIMER_SET_GR_TICK_FREQ: _bindgen_ty_2 = 154;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FIFO_SETUP_VF_ZOMBIE_SUBCTX_PDB: _bindgen_ty_2 = 155;
-pub const NV_VGPU_MSG_FUNCTION_GET_CONSOLIDATED_GR_STATIC_INFO: _bindgen_ty_2 = 156;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_SINGLE_SM_SINGLE_STEP: _bindgen_ty_2 = 157;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_GET_TPC_PARTITION_MODE: _bindgen_ty_2 = 158;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GR_SET_TPC_PARTITION_MODE: _bindgen_ty_2 = 159;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_ALLOCATE: _bindgen_ty_2 = 160;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_DESTROY: _bindgen_ty_2 = 161;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_MAP: _bindgen_ty_2 = 162;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_UNMAP: _bindgen_ty_2 = 163;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_PUSH_STREAM: _bindgen_ty_2 = 164;
-pub const NV_VGPU_MSG_FUNCTION_UVM_PAGING_CHANNEL_SET_HANDLES: _bindgen_ty_2 = 165;
-pub const NV_VGPU_MSG_FUNCTION_UVM_METHOD_STREAM_GUEST_PAGES_OPERATION: _bindgen_ty_2 = 166;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_QUIESCE_PMA_CHANNEL: _bindgen_ty_2 = 167;
-pub const NV_VGPU_MSG_FUNCTION_DCE_RM_INIT: _bindgen_ty_2 = 168;
-pub const NV_VGPU_MSG_FUNCTION_REGISTER_VIRTUAL_EVENT_BUFFER: _bindgen_ty_2 = 169;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_EVENT_BUFFER_UPDATE_GET: _bindgen_ty_2 = 170;
-pub const NV_VGPU_MSG_FUNCTION_GET_PLCABLE_ADDRESS_KIND: _bindgen_ty_2 = 171;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PERF_LIMITS_SET_STATUS_V2: _bindgen_ty_2 = 172;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_SRIOV_PROMOTE_PMA_STREAM: _bindgen_ty_2 = 173;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_MMU_DEBUG_MODE: _bindgen_ty_2 = 174;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_PROMOTE_FAULT_METHOD_BUFFERS: _bindgen_ty_2 = 175;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FLCN_GET_CTX_BUFFER_SIZE: _bindgen_ty_2 = 176;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FLCN_GET_CTX_BUFFER_INFO: _bindgen_ty_2 = 177;
-pub const NV_VGPU_MSG_FUNCTION_DISABLE_CHANNELS: _bindgen_ty_2 = 178;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FABRIC_MEMORY_DESCRIBE: _bindgen_ty_2 = 179;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FABRIC_MEM_STATS: _bindgen_ty_2 = 180;
-pub const NV_VGPU_MSG_FUNCTION_SAVE_HIBERNATION_DATA: _bindgen_ty_2 = 181;
-pub const NV_VGPU_MSG_FUNCTION_RESTORE_HIBERNATION_DATA: _bindgen_ty_2 = 182;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_INTERNAL_MEMSYS_SET_ZBC_REFERENCED: _bindgen_ty_2 = 183;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_CREATE: _bindgen_ty_2 = 184;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_DELETE: _bindgen_ty_2 = 185;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_GET_WORK_SUBMIT_TOKEN: _bindgen_ty_2 = 186;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPFIFO_SET_WORK_SUBMIT_TOKEN_NOTIF_INDEX: _bindgen_ty_2 = 187;
-pub const NV_VGPU_MSG_FUNCTION_PMA_SCRUBBER_SHARED_BUFFER_GUEST_PAGES_OPERATION: _bindgen_ty_2 =
- 188;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_MASTER_GET_VIRTUAL_FUNCTION_ERROR_CONT_INTR_MASK:
- _bindgen_ty_2 = 189;
-pub const NV_VGPU_MSG_FUNCTION_SET_SYSMEM_DIRTY_PAGE_TRACKING_BUFFER: _bindgen_ty_2 = 190;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_P2P_CAPS: _bindgen_ty_2 = 191;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_BUS_SET_P2P_MAPPING: _bindgen_ty_2 = 192;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_BUS_UNSET_P2P_MAPPING: _bindgen_ty_2 = 193;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_FLA_SETUP_INSTANCE_MEM_BLOCK: _bindgen_ty_2 = 194;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_MIGRATABLE_OPS: _bindgen_ty_2 = 195;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_TOTAL_HS_CREDITS: _bindgen_ty_2 = 196;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GET_HS_CREDITS: _bindgen_ty_2 = 197;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_HS_CREDITS: _bindgen_ty_2 = 198;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_PM_AREA_PC_SAMPLER: _bindgen_ty_2 = 199;
-pub const NV_VGPU_MSG_FUNCTION_INVALIDATE_TLB: _bindgen_ty_2 = 200;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_QUERY_ECC_STATUS: _bindgen_ty_2 = 201;
-pub const NV_VGPU_MSG_FUNCTION_ECC_NOTIFIER_WRITE_ACK: _bindgen_ty_2 = 202;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_GET_MODE_MMU_DEBUG: _bindgen_ty_2 = 203;
-pub const NV_VGPU_MSG_FUNCTION_RM_API_CONTROL: _bindgen_ty_2 = 204;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_INTERNAL_GPU_START_FABRIC_PROBE: _bindgen_ty_2 = 205;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_NVLINK_GET_INBAND_RECEIVED_DATA: _bindgen_ty_2 = 206;
-pub const NV_VGPU_MSG_FUNCTION_GET_STATIC_DATA: _bindgen_ty_2 = 207;
-pub const NV_VGPU_MSG_FUNCTION_RESERVED_208: _bindgen_ty_2 = 208;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_GPU_GET_INFO_V2: _bindgen_ty_2 = 209;
-pub const NV_VGPU_MSG_FUNCTION_GET_BRAND_CAPS: _bindgen_ty_2 = 210;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_NVLINK_INBAND_SEND_DATA: _bindgen_ty_2 = 211;
-pub const NV_VGPU_MSG_FUNCTION_UPDATE_GPM_GUEST_BUFFER_INFO: _bindgen_ty_2 = 212;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_INTERNAL_CONTROL_GSP_TRACE: _bindgen_ty_2 = 213;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SET_ZBC_STENCIL_CLEAR: _bindgen_ty_2 = 214;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_VGPU_HEAP_STATS: _bindgen_ty_2 = 215;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_SUBDEVICE_GET_LIBOS_HEAP_STATS: _bindgen_ty_2 = 216;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_SET_MODE_MMU_GCC_DEBUG: _bindgen_ty_2 = 217;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_DBG_GET_MODE_MMU_GCC_DEBUG: _bindgen_ty_2 = 218;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_HES: _bindgen_ty_2 = 219;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RELEASE_HES: _bindgen_ty_2 = 220;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RESERVE_CCU_PROF: _bindgen_ty_2 = 221;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_RELEASE_CCU_PROF: _bindgen_ty_2 = 222;
-pub const NV_VGPU_MSG_FUNCTION_RESERVED: _bindgen_ty_2 = 223;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_GET_CHIPLET_HS_CREDIT_POOL: _bindgen_ty_2 = 224;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_CMD_GET_HS_CREDITS_MAPPING: _bindgen_ty_2 = 225;
-pub const NV_VGPU_MSG_FUNCTION_CTRL_EXEC_PARTITIONS_EXPORT: _bindgen_ty_2 = 226;
-pub const NV_VGPU_MSG_FUNCTION_NUM_FUNCTIONS: _bindgen_ty_2 = 227;
-pub type _bindgen_ty_2 = ffi::c_uint;
-pub const NV_VGPU_MSG_EVENT_FIRST_EVENT: _bindgen_ty_3 = 4096;
-pub const NV_VGPU_MSG_EVENT_GSP_INIT_DONE: _bindgen_ty_3 = 4097;
-pub const NV_VGPU_MSG_EVENT_GSP_RUN_CPU_SEQUENCER: _bindgen_ty_3 = 4098;
-pub const NV_VGPU_MSG_EVENT_POST_EVENT: _bindgen_ty_3 = 4099;
-pub const NV_VGPU_MSG_EVENT_RC_TRIGGERED: _bindgen_ty_3 = 4100;
-pub const NV_VGPU_MSG_EVENT_MMU_FAULT_QUEUED: _bindgen_ty_3 = 4101;
-pub const NV_VGPU_MSG_EVENT_OS_ERROR_LOG: _bindgen_ty_3 = 4102;
-pub const NV_VGPU_MSG_EVENT_RG_LINE_INTR: _bindgen_ty_3 = 4103;
-pub const NV_VGPU_MSG_EVENT_GPUACCT_PERFMON_UTIL_SAMPLES: _bindgen_ty_3 = 4104;
-pub const NV_VGPU_MSG_EVENT_SIM_READ: _bindgen_ty_3 = 4105;
-pub const NV_VGPU_MSG_EVENT_SIM_WRITE: _bindgen_ty_3 = 4106;
-pub const NV_VGPU_MSG_EVENT_SEMAPHORE_SCHEDULE_CALLBACK: _bindgen_ty_3 = 4107;
-pub const NV_VGPU_MSG_EVENT_UCODE_LIBOS_PRINT: _bindgen_ty_3 = 4108;
-pub const NV_VGPU_MSG_EVENT_VGPU_GSP_PLUGIN_TRIGGERED: _bindgen_ty_3 = 4109;
-pub const NV_VGPU_MSG_EVENT_PERF_GPU_BOOST_SYNC_LIMITS_CALLBACK: _bindgen_ty_3 = 4110;
-pub const NV_VGPU_MSG_EVENT_PERF_BRIDGELESS_INFO_UPDATE: _bindgen_ty_3 = 4111;
-pub const NV_VGPU_MSG_EVENT_VGPU_CONFIG: _bindgen_ty_3 = 4112;
-pub const NV_VGPU_MSG_EVENT_DISPLAY_MODESET: _bindgen_ty_3 = 4113;
-pub const NV_VGPU_MSG_EVENT_EXTDEV_INTR_SERVICE: _bindgen_ty_3 = 4114;
-pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_256: _bindgen_ty_3 = 4115;
-pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_512: _bindgen_ty_3 = 4116;
-pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_1024: _bindgen_ty_3 = 4117;
-pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_2048: _bindgen_ty_3 = 4118;
-pub const NV_VGPU_MSG_EVENT_NVLINK_INBAND_RECEIVED_DATA_4096: _bindgen_ty_3 = 4119;
-pub const NV_VGPU_MSG_EVENT_TIMED_SEMAPHORE_RELEASE: _bindgen_ty_3 = 4120;
-pub const NV_VGPU_MSG_EVENT_NVLINK_IS_GPU_DEGRADED: _bindgen_ty_3 = 4121;
-pub const NV_VGPU_MSG_EVENT_PFM_REQ_HNDLR_STATE_SYNC_CALLBACK: _bindgen_ty_3 = 4122;
-pub const NV_VGPU_MSG_EVENT_NVLINK_FAULT_UP: _bindgen_ty_3 = 4123;
-pub const NV_VGPU_MSG_EVENT_GSP_LOCKDOWN_NOTICE: _bindgen_ty_3 = 4124;
-pub const NV_VGPU_MSG_EVENT_MIG_CI_CONFIG_UPDATE: _bindgen_ty_3 = 4125;
-pub const NV_VGPU_MSG_EVENT_UPDATE_GSP_TRACE: _bindgen_ty_3 = 4126;
-pub const NV_VGPU_MSG_EVENT_NVLINK_FATAL_ERROR_RECOVERY: _bindgen_ty_3 = 4127;
-pub const NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD: _bindgen_ty_3 = 4128;
-pub const NV_VGPU_MSG_EVENT_FECS_ERROR: _bindgen_ty_3 = 4129;
-pub const NV_VGPU_MSG_EVENT_RECOVERY_ACTION: _bindgen_ty_3 = 4130;
-pub const NV_VGPU_MSG_EVENT_NUM_EVENTS: _bindgen_ty_3 = 4131;
-pub type _bindgen_ty_3 = ffi::c_uint;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct NV0080_CTRL_GPU_GET_SRIOV_CAPS_PARAMS {
- pub totalVFs: u32_,
- pub firstVfOffset: u32_,
- pub vfFeatureMask: u32_,
- pub __bindgen_padding_0: [u8; 4usize],
- pub FirstVFBar0Address: u64_,
- pub FirstVFBar1Address: u64_,
- pub FirstVFBar2Address: u64_,
- pub bar0Size: u64_,
- pub bar1Size: u64_,
- pub bar2Size: u64_,
- pub b64bitBar0: u8_,
- pub b64bitBar1: u8_,
- pub b64bitBar2: u8_,
- pub bSriovEnabled: u8_,
- pub bSriovHeavyEnabled: u8_,
- pub bEmulateVFBar0TlbInvalidationRegister: u8_,
- pub bClientRmAllocatedCtxBuffer: u8_,
- pub bNonPowerOf2ChannelCountSupported: u8_,
- pub bVfResizableBAR1Supported: u8_,
- pub __bindgen_padding_1: [u8; 7usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct NV2080_CTRL_BIOS_GET_SKU_INFO_PARAMS {
- pub BoardID: u32_,
- pub chipSKU: [ffi::c_char; 9usize],
- pub chipSKUMod: [ffi::c_char; 5usize],
- pub __bindgen_padding_0: [u8; 2usize],
- pub skuConfigVersion: u32_,
- pub project: [ffi::c_char; 5usize],
- pub projectSKU: [ffi::c_char; 5usize],
- pub CDP: [ffi::c_char; 6usize],
- pub projectSKUMod: [ffi::c_char; 2usize],
- pub __bindgen_padding_1: [u8; 2usize],
- pub businessCycle: u32_,
-}
-pub type NV2080_CTRL_CMD_FB_GET_FB_REGION_SURFACE_MEM_TYPE_FLAG = [u8_; 17usize];
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO {
- pub base: u64_,
- pub limit: u64_,
- pub reserved: u64_,
- pub performance: u32_,
- pub supportCompressed: u8_,
- pub supportISO: u8_,
- pub bProtected: u8_,
- pub blackList: NV2080_CTRL_CMD_FB_GET_FB_REGION_SURFACE_MEM_TYPE_FLAG,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS {
- pub numFBRegions: u32_,
- pub __bindgen_padding_0: [u8; 4usize],
- pub fbRegion: [NV2080_CTRL_CMD_FB_GET_FB_REGION_FB_REGION_INFO; 16usize],
-}
-#[repr(C)]
-#[derive(Debug, Copy, Clone, MaybeZeroable)]
-pub struct NV2080_CTRL_GPU_GET_GID_INFO_PARAMS {
- pub index: u32_,
- pub flags: u32_,
- pub length: u32_,
- pub data: [u8_; 256usize],
-}
-impl Default for NV2080_CTRL_GPU_GET_GID_INFO_PARAMS {
- 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(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct DOD_METHOD_DATA {
- pub status: u32_,
- pub acpiIdListLen: u32_,
- pub acpiIdList: [u32_; 16usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct JT_METHOD_DATA {
- pub status: u32_,
- pub jtCaps: u32_,
- pub jtRevId: u16_,
- pub bSBIOSCaps: u8_,
- pub __bindgen_padding_0: u8,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct MUX_METHOD_DATA_ELEMENT {
- pub acpiId: u32_,
- pub mode: u32_,
- pub status: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct MUX_METHOD_DATA {
- pub tableLen: u32_,
- pub acpiIdMuxModeTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
- pub acpiIdMuxPartTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
- pub acpiIdMuxStateTable: [MUX_METHOD_DATA_ELEMENT; 16usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct CAPS_METHOD_DATA {
- pub status: u32_,
- pub optimusCaps: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct ACPI_METHOD_DATA {
- pub bValid: u8_,
- pub __bindgen_padding_0: [u8; 3usize],
- pub dodMethodData: DOD_METHOD_DATA,
- pub jtMethodData: JT_METHOD_DATA,
- pub muxMethodData: MUX_METHOD_DATA,
- pub capsMethodData: CAPS_METHOD_DATA,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct VIRTUAL_DISPLAY_GET_MAX_RESOLUTION_PARAMS {
- pub headIndex: u32_,
- pub maxHResolution: u32_,
- pub maxVResolution: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct VIRTUAL_DISPLAY_GET_NUM_HEADS_PARAMS {
- pub numHeads: u32_,
- pub maxNumHeads: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct BUSINFO {
- pub deviceID: u16_,
- pub vendorID: u16_,
- pub subdeviceID: u16_,
- pub subvendorID: u16_,
- pub revisionID: u8_,
- pub __bindgen_padding_0: u8,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_VF_INFO {
- pub totalVFs: u32_,
- pub firstVFOffset: u32_,
- pub FirstVFBar0Address: u64_,
- pub FirstVFBar1Address: u64_,
- pub FirstVFBar2Address: u64_,
- pub b64bitBar0: u8_,
- pub b64bitBar1: u8_,
- pub b64bitBar2: u8_,
- pub __bindgen_padding_0: [u8; 5usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_PCIE_CONFIG_REG {
- pub linkCap: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct EcidManufacturingInfo {
- pub ecidLow: u32_,
- pub ecidHigh: u32_,
- pub ecidExtended: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct FW_WPR_LAYOUT_OFFSET {
- pub nonWprHeapOffset: u64_,
- pub frtsOffset: u64_,
-}
-#[repr(C)]
-#[derive(Debug, Copy, Clone, MaybeZeroable)]
-pub struct GspStaticConfigInfo_t {
- pub grCapsBits: [u8_; 23usize],
- pub __bindgen_padding_0: u8,
- pub gidInfo: NV2080_CTRL_GPU_GET_GID_INFO_PARAMS,
- pub SKUInfo: NV2080_CTRL_BIOS_GET_SKU_INFO_PARAMS,
- pub __bindgen_padding_1: [u8; 4usize],
- pub fbRegionInfoParams: NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS,
- pub sriovCaps: NV0080_CTRL_GPU_GET_SRIOV_CAPS_PARAMS,
- pub sriovMaxGfid: u32_,
- pub engineCaps: [u32_; 3usize],
- pub poisonFuseEnabled: u8_,
- pub __bindgen_padding_2: [u8; 7usize],
- pub fb_length: u64_,
- pub fbio_mask: u64_,
- pub fb_bus_width: u32_,
- pub fb_ram_type: u32_,
- pub fbp_mask: u64_,
- pub l2_cache_size: u32_,
- pub gpuNameString: [u8_; 64usize],
- pub gpuShortNameString: [u8_; 64usize],
- pub gpuNameString_Unicode: [u16_; 64usize],
- pub bGpuInternalSku: u8_,
- pub bIsQuadroGeneric: u8_,
- pub bIsQuadroAd: u8_,
- pub bIsNvidiaNvs: u8_,
- pub bIsVgx: u8_,
- pub bGeforceSmb: u8_,
- pub bIsTitan: u8_,
- pub bIsTesla: u8_,
- pub bIsMobile: u8_,
- pub bIsGc6Rtd3Allowed: u8_,
- pub bIsGc8Rtd3Allowed: u8_,
- pub bIsGcOffRtd3Allowed: u8_,
- pub bIsGcoffLegacyAllowed: u8_,
- pub bIsMigSupported: u8_,
- pub RTD3GC6TotalBoardPower: u16_,
- pub RTD3GC6PerstDelay: u16_,
- pub __bindgen_padding_3: [u8; 2usize],
- pub bar1PdeBase: u64_,
- pub bar2PdeBase: u64_,
- pub bVbiosValid: u8_,
- pub __bindgen_padding_4: [u8; 3usize],
- pub vbiosSubVendor: u32_,
- pub vbiosSubDevice: u32_,
- pub bPageRetirementSupported: u8_,
- pub bSplitVasBetweenServerClientRm: u8_,
- pub bClRootportNeedsNosnoopWAR: u8_,
- pub __bindgen_padding_5: u8,
- pub displaylessMaxHeads: VIRTUAL_DISPLAY_GET_NUM_HEADS_PARAMS,
- pub displaylessMaxResolution: VIRTUAL_DISPLAY_GET_MAX_RESOLUTION_PARAMS,
- pub __bindgen_padding_6: [u8; 4usize],
- pub displaylessMaxPixels: u64_,
- pub hInternalClient: u32_,
- pub hInternalDevice: u32_,
- pub hInternalSubdevice: u32_,
- pub bSelfHostedMode: u8_,
- pub bAtsSupported: u8_,
- pub bIsGpuUefi: u8_,
- pub bIsEfiInit: u8_,
- pub ecidInfo: [EcidManufacturingInfo; 2usize],
- pub fwWprLayoutOffset: FW_WPR_LAYOUT_OFFSET,
-}
-impl Default for GspStaticConfigInfo_t {
- 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(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GspSystemInfo {
- pub gpuPhysAddr: u64_,
- pub gpuPhysFbAddr: u64_,
- pub gpuPhysInstAddr: u64_,
- pub gpuPhysIoAddr: u64_,
- pub nvDomainBusDeviceFunc: u64_,
- pub simAccessBufPhysAddr: u64_,
- pub notifyOpSharedSurfacePhysAddr: u64_,
- pub pcieAtomicsOpMask: u64_,
- pub consoleMemSize: u64_,
- pub maxUserVa: u64_,
- pub pciConfigMirrorBase: u32_,
- pub pciConfigMirrorSize: u32_,
- pub PCIDeviceID: u32_,
- pub PCISubDeviceID: u32_,
- pub PCIRevisionID: u32_,
- pub pcieAtomicsCplDeviceCapMask: u32_,
- pub oorArch: u8_,
- pub __bindgen_padding_0: [u8; 7usize],
- pub clPdbProperties: u64_,
- pub Chipset: u32_,
- pub bGpuBehindBridge: u8_,
- pub bFlrSupported: u8_,
- pub b64bBar0Supported: u8_,
- pub bMnocAvailable: u8_,
- pub chipsetL1ssEnable: u32_,
- pub bUpstreamL0sUnsupported: u8_,
- pub bUpstreamL1Unsupported: u8_,
- pub bUpstreamL1PorSupported: u8_,
- pub bUpstreamL1PorMobileOnly: u8_,
- pub bSystemHasMux: u8_,
- pub upstreamAddressValid: u8_,
- pub FHBBusInfo: BUSINFO,
- pub chipsetIDInfo: BUSINFO,
- pub __bindgen_padding_1: [u8; 2usize],
- pub acpiMethodData: ACPI_METHOD_DATA,
- pub hypervisorType: u32_,
- pub bIsPassthru: u8_,
- pub __bindgen_padding_2: [u8; 7usize],
- pub sysTimerOffsetNs: u64_,
- pub gspVFInfo: GSP_VF_INFO,
- pub bIsPrimary: u8_,
- pub isGridBuild: u8_,
- pub __bindgen_padding_3: [u8; 2usize],
- pub pcieConfigReg: GSP_PCIE_CONFIG_REG,
- pub gridBuildCsp: u32_,
- pub bPreserveVideoMemoryAllocations: u8_,
- pub bTdrEventSupported: u8_,
- pub bFeatureStretchVblankCapable: u8_,
- pub bEnableDynamicGranularityPageArrays: u8_,
- pub bClockBoostSupported: u8_,
- pub bRouteDispIntrsToCPU: u8_,
- pub __bindgen_padding_4: [u8; 6usize],
- pub hostPageSize: u64_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct MESSAGE_QUEUE_INIT_ARGUMENTS {
- pub sharedMemPhysAddr: u64_,
- pub pageTableEntryCount: u32_,
- pub __bindgen_padding_0: [u8; 4usize],
- pub cmdQueueOffset: u64_,
- pub statQueueOffset: u64_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SR_INIT_ARGUMENTS {
- pub oldLevel: u32_,
- pub flags: u32_,
- pub bInPMTransition: u8_,
- pub __bindgen_padding_0: [u8; 3usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_ARGUMENTS_CACHED {
- pub messageQueueInitArguments: MESSAGE_QUEUE_INIT_ARGUMENTS,
- pub srInitArguments: GSP_SR_INIT_ARGUMENTS,
- pub gpuInstance: u32_,
- pub bDmemStack: u8_,
- pub __bindgen_padding_0: [u8; 7usize],
- pub profilerArgs: GSP_ARGUMENTS_CACHED__bindgen_ty_1,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_ARGUMENTS_CACHED__bindgen_ty_1 {
- pub pa: u64_,
- pub size: u64_,
-}
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub union rpc_message_rpc_union_field_v03_00 {
- pub spare: u32_,
- pub cpuRmGfid: u32_,
-}
-impl Default for rpc_message_rpc_union_field_v03_00 {
- 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 type rpc_message_rpc_union_field_v = rpc_message_rpc_union_field_v03_00;
-#[repr(C)]
-#[derive(MaybeZeroable)]
-pub struct rpc_message_header_v03_00 {
- pub header_version: u32_,
- pub signature: u32_,
- pub length: u32_,
- pub function: u32_,
- pub rpc_result: u32_,
- pub rpc_result_private: u32_,
- pub sequence: u32_,
- pub u: rpc_message_rpc_union_field_v,
- pub rpc_message_data: __IncompleteArrayField<u8_>,
-}
-impl Default for rpc_message_header_v03_00 {
- 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 type rpc_message_header_v = rpc_message_header_v03_00;
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub struct GspFwWprMeta {
- pub magic: u64_,
- pub revision: u64_,
- pub sysmemAddrOfRadix3Elf: u64_,
- pub sizeOfRadix3Elf: u64_,
- pub sysmemAddrOfBootloader: u64_,
- pub sizeOfBootloader: u64_,
- pub bootloaderCodeOffset: u64_,
- pub bootloaderDataOffset: u64_,
- pub bootloaderManifestOffset: u64_,
- pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_1,
- pub gspFwRsvdStart: u64_,
- pub nonWprHeapOffset: u64_,
- pub nonWprHeapSize: u64_,
- pub gspFwWprStart: u64_,
- pub gspFwHeapOffset: u64_,
- pub gspFwHeapSize: u64_,
- pub gspFwOffset: u64_,
- pub bootBinOffset: u64_,
- pub frtsOffset: u64_,
- pub frtsSize: u64_,
- pub gspFwWprEnd: u64_,
- pub fbSize: u64_,
- pub vgaWorkspaceOffset: u64_,
- pub vgaWorkspaceSize: u64_,
- pub bootCount: u64_,
- pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_2,
- pub gspFwHeapVfPartitionCount: u8_,
- pub flags: u8_,
- pub padding: [u8_; 2usize],
- pub pmuReservedSize: u32_,
- pub verified: u64_,
-}
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub union GspFwWprMeta__bindgen_ty_1 {
- pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_1__bindgen_ty_1,
- pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_1__bindgen_ty_2,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GspFwWprMeta__bindgen_ty_1__bindgen_ty_1 {
- pub sysmemAddrOfSignature: u64_,
- pub sizeOfSignature: u64_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GspFwWprMeta__bindgen_ty_1__bindgen_ty_2 {
- pub gspFwHeapFreeListWprOffset: u32_,
- pub unused0: u32_,
- pub unused1: u64_,
-}
-impl Default for GspFwWprMeta__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()
- }
- }
-}
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub union GspFwWprMeta__bindgen_ty_2 {
- pub __bindgen_anon_1: GspFwWprMeta__bindgen_ty_2__bindgen_ty_1,
- pub __bindgen_anon_2: GspFwWprMeta__bindgen_ty_2__bindgen_ty_2,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GspFwWprMeta__bindgen_ty_2__bindgen_ty_1 {
- pub partitionRpcAddr: u64_,
- pub partitionRpcRequestOffset: u16_,
- pub partitionRpcReplyOffset: u16_,
- pub elfCodeOffset: u32_,
- pub elfDataOffset: u32_,
- pub elfCodeSize: u32_,
- pub elfDataSize: u32_,
- pub lsUcodeVersion: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GspFwWprMeta__bindgen_ty_2__bindgen_ty_2 {
- pub partitionRpcPadding: [u32_; 4usize],
- pub sysmemAddrOfCrashReportQueue: u64_,
- pub sizeOfCrashReportQueue: u32_,
- pub lsUcodeVersionPadding: [u32_; 1usize],
-}
-impl Default for GspFwWprMeta__bindgen_ty_2 {
- 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 GspFwWprMeta {
- 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 type LibosAddress = u64_;
-pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_NONE: LibosMemoryRegionKind = 0;
-pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_CONTIGUOUS: LibosMemoryRegionKind = 1;
-pub const LibosMemoryRegionKind_LIBOS_MEMORY_REGION_RADIX3: LibosMemoryRegionKind = 2;
-pub type LibosMemoryRegionKind = ffi::c_uint;
-pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_NONE: LibosMemoryRegionLoc = 0;
-pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_SYSMEM: LibosMemoryRegionLoc = 1;
-pub const LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_FB: LibosMemoryRegionLoc = 2;
-pub type LibosMemoryRegionLoc = ffi::c_uint;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct LibosMemoryRegionInitArgument {
- pub id8: LibosAddress,
- pub pa: LibosAddress,
- pub size: LibosAddress,
- pub kind: u8_,
- pub loc: u8_,
- pub __bindgen_padding_0: [u8; 6usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct PACKED_REGISTRY_ENTRY {
- pub nameOffset: u32_,
- pub type_: u8_,
- pub __bindgen_padding_0: [u8; 3usize],
- pub data: u32_,
- pub length: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, MaybeZeroable)]
-pub struct PACKED_REGISTRY_TABLE {
- pub size: u32_,
- pub numEntries: u32_,
- pub entries: __IncompleteArrayField<PACKED_REGISTRY_ENTRY>,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct msgqTxHeader {
- pub version: u32_,
- pub size: u32_,
- pub msgSize: u32_,
- pub msgCount: u32_,
- pub writePtr: u32_,
- pub flags: u32_,
- pub rxHdrOff: u32_,
- pub entryOff: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct msgqRxHeader {
- pub readPtr: u32_,
-}
-#[repr(C)]
-#[repr(align(8))]
-#[derive(MaybeZeroable)]
-pub struct GSP_MSG_QUEUE_ELEMENT {
- pub authTagBuffer: [u8_; 16usize],
- pub aadBuffer: [u8_; 16usize],
- pub checkSum: u32_,
- pub seqNum: u32_,
- pub elemCount: u32_,
- pub __bindgen_padding_0: [u8; 4usize],
- pub rpc: rpc_message_header_v,
-}
-impl Default for GSP_MSG_QUEUE_ELEMENT {
- 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 GSP_DMA_TARGET_GSP_DMA_TARGET_LOCAL_FB: GSP_DMA_TARGET = 0;
-pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COHERENT_SYSTEM: GSP_DMA_TARGET = 1;
-pub const GSP_DMA_TARGET_GSP_DMA_TARGET_NONCOHERENT_SYSTEM: GSP_DMA_TARGET = 2;
-pub const GSP_DMA_TARGET_GSP_DMA_TARGET_COUNT: GSP_DMA_TARGET = 3;
-pub type GSP_DMA_TARGET = ffi::c_uint;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_FMC_INIT_PARAMS {
- pub regkeys: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Copy, Clone, MaybeZeroable)]
-pub struct GSP_ACR_BOOT_GSP_RM_PARAMS {
- pub target: GSP_DMA_TARGET,
- pub gspRmDescSize: u32_,
- pub gspRmDescOffset: u64_,
- pub wprCarveoutOffset: u64_,
- pub wprCarveoutSize: u32_,
- pub bIsGspRmBoot: u8_,
- pub __bindgen_padding_0: [u8; 3usize],
-}
-impl Default for GSP_ACR_BOOT_GSP_RM_PARAMS {
- 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(Debug, Copy, Clone, MaybeZeroable)]
-pub struct GSP_RM_PARAMS {
- pub target: GSP_DMA_TARGET,
- pub __bindgen_padding_0: [u8; 4usize],
- pub bootArgsOffset: u64_,
-}
-impl Default for GSP_RM_PARAMS {
- 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(Debug, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SPDM_PARAMS {
- pub target: GSP_DMA_TARGET,
- pub __bindgen_padding_0: [u8; 4usize],
- pub payloadBufferOffset: u64_,
- pub payloadBufferSize: u32_,
- pub __bindgen_padding_1: [u8; 4usize],
-}
-impl Default for GSP_SPDM_PARAMS {
- 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(Debug, Copy, Clone, MaybeZeroable)]
-pub struct GSP_FMC_BOOT_PARAMS {
- pub initParams: GSP_FMC_INIT_PARAMS,
- pub __bindgen_padding_0: [u8; 4usize],
- pub bootGspRmParams: GSP_ACR_BOOT_GSP_RM_PARAMS,
- pub gspRmParams: GSP_RM_PARAMS,
- pub gspSpdmParams: GSP_SPDM_PARAMS,
-}
-impl Default for GSP_FMC_BOOT_PARAMS {
- 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(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct rpc_unloading_guest_driver_v1F_07 {
- pub bInPMTransition: u8_,
- pub bGc6Entering: u8_,
- pub __bindgen_padding_0: [u8; 2usize],
- pub newLevel: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, MaybeZeroable)]
-pub struct rpc_run_cpu_sequencer_v17_00 {
- pub bufferSizeDWord: u32_,
- pub cmdIndex: u32_,
- pub regSaveArea: [u32_; 8usize],
- pub commandBuffer: __IncompleteArrayField<u32_>,
-}
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_WRITE: GSP_SEQ_BUF_OPCODE = 0;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_MODIFY: GSP_SEQ_BUF_OPCODE = 1;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_POLL: GSP_SEQ_BUF_OPCODE = 2;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_DELAY_US: GSP_SEQ_BUF_OPCODE = 3;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_REG_STORE: GSP_SEQ_BUF_OPCODE = 4;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESET: GSP_SEQ_BUF_OPCODE = 5;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_START: GSP_SEQ_BUF_OPCODE = 6;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_WAIT_FOR_HALT: GSP_SEQ_BUF_OPCODE = 7;
-pub const GSP_SEQ_BUF_OPCODE_GSP_SEQ_BUF_OPCODE_CORE_RESUME: GSP_SEQ_BUF_OPCODE = 8;
-pub type GSP_SEQ_BUF_OPCODE = ffi::c_uint;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQ_BUF_PAYLOAD_REG_WRITE {
- pub addr: u32_,
- pub val: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQ_BUF_PAYLOAD_REG_MODIFY {
- pub addr: u32_,
- pub mask: u32_,
- pub val: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQ_BUF_PAYLOAD_REG_POLL {
- pub addr: u32_,
- pub mask: u32_,
- pub val: u32_,
- pub timeout: u32_,
- pub error: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQ_BUF_PAYLOAD_DELAY_US {
- pub val: u32_,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQ_BUF_PAYLOAD_REG_STORE {
- pub addr: u32_,
- pub index: u32_,
-}
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub struct GSP_SEQUENCER_BUFFER_CMD {
- pub opCode: GSP_SEQ_BUF_OPCODE,
- pub payload: GSP_SEQUENCER_BUFFER_CMD__bindgen_ty_1,
-}
-#[repr(C)]
-#[derive(Copy, Clone, MaybeZeroable)]
-pub union GSP_SEQUENCER_BUFFER_CMD__bindgen_ty_1 {
- pub regWrite: GSP_SEQ_BUF_PAYLOAD_REG_WRITE,
- pub regModify: GSP_SEQ_BUF_PAYLOAD_REG_MODIFY,
- pub regPoll: GSP_SEQ_BUF_PAYLOAD_REG_POLL,
- pub delayUs: GSP_SEQ_BUF_PAYLOAD_DELAY_US,
- pub regStore: GSP_SEQ_BUF_PAYLOAD_REG_STORE,
-}
-impl Default for GSP_SEQUENCER_BUFFER_CMD__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 GSP_SEQUENCER_BUFFER_CMD {
- 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()
- }
- }
-}
--
2.55.0
^ permalink raw reply related [flat|nested] 28+ messages in thread