* [PATCH v2 1/1] gpu: nova-core: add fwctl driver for firmware control interface
2026-11-06 6:09 [PATCH v2 0/1] gpu: nova-core: add fwctl driver for firmware control interface Alok Kumar
@ 2026-11-06 6:09 ` Alok Kumar
0 siblings, 0 replies; 2+ messages in thread
From: Alok Kumar @ 2026-11-06 6:09 UTC (permalink / raw)
To: dakr, acourbot, kvm, rust-for-linux, nova-gpu, linux-pci
Cc: alex, jgg, yishaih, skolothumtho, kevin.tian, airlied, simona,
ojeda, alex.gaynor, boqun.feng, gary, bjorn3_gh, lossin,
a.hindborg, aliceryhl, tmgross, markus.probst, jhubbard, apopple,
joelagnelf, ecourtney, cjia, smitra, kjaju, alkumar, ankita,
aniketa, kwankhede, targupta, linux-kernel, zhiwang, zhiw, boqun,
daniel.almeida, tamird, work, jgg, bhelgaas, kwilczynski,
dri-devel, peter, xinmengs
From: Zhi Wang <zhiw@nvidia.com>
Add a fwctl driver that provides an interface for userspace to issue
GMC API commands to the GSP. Userspace supplies the command ID and
payload while the transport headers remain internal to the kernel.
Allow the seven vGPU management commands in the configuration scope.
Reject all other commands and scopes.
zhiw: Adapt to the GPU-owned command queue and its GMC transaction API.
Use the existing vGPU firmware module for command constants and register
fwctl after GPU and auxiliary-device initialization.
Signed-off-by: Zhi Wang <zhiw@nvidia.com>
Signed-off-by: Alok Kumar <alkumar@nvidia.com>
---
drivers/gpu/nova-core/Kconfig | 2 +
drivers/gpu/nova-core/driver.rs | 27 +++++++
drivers/gpu/nova-core/fwctl.rs | 119 +++++++++++++++++++++++++++++
drivers/gpu/nova-core/nova_core.rs | 2 +
drivers/gpu/nova-core/vgpu.rs | 10 +++
drivers/gpu/nova-core/vgpu/fw.rs | 18 ++++-
include/uapi/fwctl/fwctl.h | 1 +
include/uapi/fwctl/nova-core.h | 34 +++++++++
rust/kernel/fwctl.rs | 2 +
rust/uapi/uapi_helper.h | 2 +
10 files changed, 215 insertions(+), 2 deletions(-)
create mode 100644 drivers/gpu/nova-core/fwctl.rs
create mode 100644 include/uapi/fwctl/nova-core.h
diff --git a/drivers/gpu/nova-core/Kconfig b/drivers/gpu/nova-core/Kconfig
index 2e11e46c99c7..3431cac7f3ba 100644
--- a/drivers/gpu/nova-core/Kconfig
+++ b/drivers/gpu/nova-core/Kconfig
@@ -4,8 +4,10 @@ config NOVA_CORE
depends on PCI
depends on RUST
depends on !CPU_BIG_ENDIAN
+ depends on FWCTL=y
select AUXILIARY_BUS
select GPU_BUDDY
+ select RUST_FWCTL_ABSTRACTIONS
select RUST_FW_LOADER_ABSTRACTIONS
default n
help
diff --git a/drivers/gpu/nova-core/driver.rs b/drivers/gpu/nova-core/driver.rs
index 560a70c4f5a7..7ad93eca485b 100644
--- a/drivers/gpu/nova-core/driver.rs
+++ b/drivers/gpu/nova-core/driver.rs
@@ -6,6 +6,7 @@
Bound,
Core, //
},
+ fwctl,
io::resource,
pci,
pci::{
@@ -24,6 +25,10 @@
use crate::{
api::NovaCoreApi,
+ fwctl::{
+ NovaCoreFwCtl,
+ NovaCoreFwCtlData, //
+ },
gpu::Gpu, //
};
@@ -34,6 +39,8 @@
pub(crate) struct NovaCore<'bound> {
#[allow(clippy::type_complexity)]
_reg: auxiliary::Registration<'bound, ForLt!(NovaCoreApi<'_>)>,
+ /// Unregisters fwctl and drains its callbacks before GPU resources are dropped.
+ _fwctl: fwctl::Registration<'bound, NovaCoreFwCtl>,
#[pin]
pub(crate) gpu: Gpu<'bound>,
bar: pci::Bar<'bound, BAR0_SIZE>,
@@ -168,6 +175,26 @@ fn probe<'bound>(
)?
}
},
+
+ _fwctl: {
+ // SAFETY: `gpu` is initialized at its final pinned address. `_fwctl` is
+ // declared before `gpu`, so unregistering fwctl drains callbacks before the
+ // GPU is dropped. Initializer cleanup also unregisters it first, since this
+ // registration is initialized last. The private registration is never leaked.
+ let gpu = unsafe { &*core::ptr::from_ref(gpu.as_ref().get_ref()) };
+ let fwctl_dev = fwctl::Device::<NovaCoreFwCtl>::new(pdev.as_ref())?;
+
+ // SAFETY: The device is newly allocated, unregistered, and parented to `pdev`.
+ // The registration borrows the pinned GPU as described above, and its Drop
+ // runs before the parent device unbinds.
+ unsafe {
+ fwctl::Registration::new(
+ pdev.as_ref(),
+ &fwctl_dev,
+ NovaCoreFwCtlData::new(gpu.cmdq()),
+ )?
+ }
+ },
})
}
}
diff --git a/drivers/gpu/nova-core/fwctl.rs b/drivers/gpu/nova-core/fwctl.rs
new file mode 100644
index 000000000000..f1f750e86e4f
--- /dev/null
+++ b/drivers/gpu/nova-core/fwctl.rs
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! fwctl driver for nova-core GMC API pass-through.
+
+use kernel::{
+ fwctl,
+ fwctl::{
+ DeviceType,
+ FwRpcResponse,
+ RpcScope, //
+ },
+ prelude::*, //
+};
+
+use crate::{
+ gsp::cmdq::Cmdq,
+ vgpu::{
+ GMCAPI_CMD_ADD_VGPU_TYPE,
+ GMCAPI_CMD_ASSIGN_VGPU_TYPE,
+ GMCAPI_CMD_DEASSIGN_VGPU_TYPE,
+ GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE,
+ GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES,
+ GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES,
+ GMCAPI_CMD_QUERY_VGPU_PROPERTIES, //
+ },
+};
+
+/// Resources used by fwctl callbacks while the GPU is bound.
+#[pin_data]
+pub(crate) struct NovaCoreFwCtlData<'a> {
+ cmdq: &'a Cmdq<'a>,
+}
+
+impl<'a> NovaCoreFwCtlData<'a> {
+ pub(crate) fn new(cmdq: &'a Cmdq<'a>) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self { cmdq })
+ }
+}
+
+/// Per-file fwctl context for nova-core.
+#[pin_data]
+pub(crate) struct NovaCoreFwCtl {}
+
+impl fwctl::Operations for NovaCoreFwCtl {
+ type RegistrationData<'a> = NovaCoreFwCtlData<'a>;
+
+ const DEVICE_TYPE: DeviceType = DeviceType::NovaCore;
+
+ fn open<'a>(
+ _device: &fwctl::Device<Self>,
+ _reg_data: &Self::RegistrationData<'a>,
+ ) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {})
+ }
+
+ fn fw_rpc<'a>(
+ _this: Pin<&Self>,
+ _device: &fwctl::Device<Self>,
+ reg_data: &Self::RegistrationData<'a>,
+ scope: RpcScope,
+ rpc_buf: &mut [u8],
+ _max_output_len: usize,
+ ) -> Result<FwRpcResponse, Error> {
+ if rpc_buf.len() < RPC_HEADER_SIZE {
+ return Err(EINVAL);
+ }
+
+ let (header, payload) = rpc_buf.split_at(RPC_HEADER_SIZE);
+ let command_id = u32::from_le_bytes(header[..4].try_into().map_err(|_| EINVAL)?);
+ let reserved = u32::from_le_bytes(header[4..].try_into().map_err(|_| EINVAL)?);
+
+ if reserved != 0 {
+ return Err(EINVAL);
+ }
+ if !is_command_permitted(scope, command_id) {
+ return Err(EPERM);
+ }
+
+ let response =
+ reg_data
+ .cmdq
+ .send_gmc_and_receive(command_id, payload, GSP_GMC_MAX_RESPONSE_SIZE)?;
+ if response.status != 0 {
+ return Err(EIO);
+ }
+
+ let response_len = RPC_HEADER_SIZE
+ .checked_add(response.payload.len())
+ .ok_or(EOVERFLOW)?;
+ let mut out = KVVec::with_capacity(response_len, GFP_KERNEL)?;
+ out.extend_from_slice(&command_id.to_le_bytes(), GFP_KERNEL)?;
+ out.extend_from_slice(&0u32.to_le_bytes(), GFP_KERNEL)?;
+ out.extend_from_slice(&response.payload, GFP_KERNEL)?;
+
+ Ok(FwRpcResponse::NewBuffer(out))
+ }
+}
+
+/// Size of `fwctl_rpc_nova_core` (`command_id` plus `reserved`).
+const RPC_HEADER_SIZE: usize = 8;
+
+const GSP_GMC_MAX_RESPONSE_SIZE: u32 = 16_384;
+
+/// Returns whether `command_id` may be issued under `scope`.
+fn is_command_permitted(scope: RpcScope, command_id: u32) -> bool {
+ match scope {
+ RpcScope::Configuration => matches!(
+ command_id,
+ GMCAPI_CMD_ADD_VGPU_TYPE
+ | GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES
+ | GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES
+ | GMCAPI_CMD_ASSIGN_VGPU_TYPE
+ | GMCAPI_CMD_DEASSIGN_VGPU_TYPE
+ | GMCAPI_CMD_QUERY_VGPU_PROPERTIES
+ | GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE
+ ),
+ _ => false,
+ }
+}
diff --git a/drivers/gpu/nova-core/nova_core.rs b/drivers/gpu/nova-core/nova_core.rs
index f7753d63c432..5f09b69f3b54 100644
--- a/drivers/gpu/nova-core/nova_core.rs
+++ b/drivers/gpu/nova-core/nova_core.rs
@@ -16,6 +16,7 @@
mod fb;
mod firmware;
mod fsp;
+mod fwctl;
mod gpu;
mod gsp;
mod irq;
@@ -83,6 +84,7 @@ fn init(module: &'static kernel::ThisModule) -> impl PinInit<Self, Error> {
description: "Nova Core GPU driver",
license: "GPL v2",
firmware: [],
+ imports_ns: ["FWCTL"],
}
kernel::module_firmware!(firmware::ModInfoBuilder);
diff --git a/drivers/gpu/nova-core/vgpu.rs b/drivers/gpu/nova-core/vgpu.rs
index 47a27236a086..f198fa218297 100644
--- a/drivers/gpu/nova-core/vgpu.rs
+++ b/drivers/gpu/nova-core/vgpu.rs
@@ -46,6 +46,16 @@
pub(crate) mod vgpu_api;
mod vram;
+pub(crate) use fw::{
+ GMCAPI_CMD_ADD_VGPU_TYPE,
+ GMCAPI_CMD_ASSIGN_VGPU_TYPE,
+ GMCAPI_CMD_DEASSIGN_VGPU_TYPE,
+ GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE,
+ GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES,
+ GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES,
+ GMCAPI_CMD_QUERY_VGPU_PROPERTIES, //
+};
+
/// vGPU state detected during GPU construction.
#[derive(Debug, Clone, Copy)]
pub(crate) enum VgpuState {
diff --git a/drivers/gpu/nova-core/vgpu/fw.rs b/drivers/gpu/nova-core/vgpu/fw.rs
index 795ce4855586..8f0fb26da86b 100644
--- a/drivers/gpu/nova-core/vgpu/fw.rs
+++ b/drivers/gpu/nova-core/vgpu/fw.rs
@@ -31,10 +31,24 @@
pub(super) use commands::RpcMessage;
-pub(super) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
+pub(crate) const GMCAPI_CMD_ADD_VGPU_TYPE: u32 = bindings::GMCAPI_COMMANDS_GMCAPI_CMD_ADD_VGPU_TYPE;
+
+pub(crate) const GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES: u32 =
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_SUPPORTED_VGPU_TYPES;
+
+pub(crate) const GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES: u32 =
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_CREATABLE_VGPU_TYPES;
+
+pub(crate) const GMCAPI_CMD_ASSIGN_VGPU_TYPE: u32 =
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_ASSIGN_VGPU_TYPE;
+
+pub(crate) const GMCAPI_CMD_DEASSIGN_VGPU_TYPE: u32 =
+ bindings::GMCAPI_COMMANDS_GMCAPI_CMD_DEASSIGN_VGPU_TYPE;
+
+pub(crate) const GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE: u32 =
bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_ASSIGNED_VF_VGPU_TYPE;
-pub(super) const GMCAPI_CMD_QUERY_VGPU_PROPERTIES: u32 =
+pub(crate) const GMCAPI_CMD_QUERY_VGPU_PROPERTIES: u32 =
bindings::GMCAPI_COMMANDS_GMCAPI_CMD_QUERY_VGPU_PROPERTIES;
pub(super) const GMCAPI_CMD_BOOTLOAD_GSP_VGPU_PLUGIN_TASK: u32 =
diff --git a/include/uapi/fwctl/fwctl.h b/include/uapi/fwctl/fwctl.h
index 2d6d4049c205..0e336cbbeb6b 100644
--- a/include/uapi/fwctl/fwctl.h
+++ b/include/uapi/fwctl/fwctl.h
@@ -46,6 +46,7 @@ enum fwctl_device_type {
FWCTL_DEVICE_TYPE_CXL = 2,
FWCTL_DEVICE_TYPE_BNXT = 3,
FWCTL_DEVICE_TYPE_PDS = 4,
+ FWCTL_DEVICE_TYPE_NOVA_CORE = 8,
};
/**
diff --git a/include/uapi/fwctl/nova-core.h b/include/uapi/fwctl/nova-core.h
new file mode 100644
index 000000000000..c11619660058
--- /dev/null
+++ b/include/uapi/fwctl/nova-core.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES
+ *
+ * NVIDIA nova-core fwctl interface. All fwctl traffic uses the GMC API
+ * transport. User-space supplies a command_id and optional payload; the
+ * kernel constructs the GMC transport headers, validates the command_id
+ * against the permitted set for the requested scope, and returns only the
+ * response payload.
+ *
+ * The device_type for this file is FWCTL_DEVICE_TYPE_NOVA_CORE.
+ */
+#ifndef _UAPI_FWCTL_NOVA_CORE_H
+#define _UAPI_FWCTL_NOVA_CORE_H
+
+#include <linux/types.h>
+
+/**
+ * struct fwctl_rpc_nova_core - ioctl(FWCTL_RPC) in/out buffer format
+ * @command_id: GMC API command identifier (from GMCAPI_COMMANDS).
+ * @reserved: Must be zero.
+ *
+ * The request buffer passed to FWCTL_RPC begins with this header,
+ * followed by the command-specific payload bytes (if any).
+ *
+ * The response buffer returned by FWCTL_RPC begins with this same
+ * header (echoing the command_id), followed by the response payload.
+ */
+struct fwctl_rpc_nova_core {
+ __u32 command_id;
+ __u32 reserved;
+};
+
+#endif
diff --git a/rust/kernel/fwctl.rs b/rust/kernel/fwctl.rs
index b7778e4d5ee0..92e1cdf6da2d 100644
--- a/rust/kernel/fwctl.rs
+++ b/rust/kernel/fwctl.rs
@@ -44,6 +44,8 @@ pub enum DeviceType {
Pds = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_PDS,
/// Broadcom NetXtreme (bnxt) device.
Bnxt = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_BNXT,
+ /// NVIDIA nova-core GPU device.
+ NovaCore = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_NOVA_CORE,
}
/// Scope of access for an RPC request.
diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h
index 44fb6203c0ad..428a756a9b5c 100644
--- a/rust/uapi/uapi_helper.h
+++ b/rust/uapi/uapi_helper.h
@@ -11,6 +11,8 @@
#include <uapi/drm/drm.h>
#include <uapi/drm/nova_drm.h>
#include <uapi/drm/panthor_drm.h>
+#include <uapi/fwctl/fwctl.h>
+#include <uapi/fwctl/nova-core.h>
#include <uapi/linux/android/binder.h>
#include <uapi/linux/android/binder_netlink.h>
#include <uapi/linux/ioctl.h>
--
2.53.0
^ permalink raw reply [flat|nested] 2+ messages in thread