[PATCH 4/9] gpu: nova-core: read vGPU mode from FSP via PRC protocol
From: Zhi Wang
Date: Thu Jun 04 2026 - 07:49:33 EST
Add support for querying the vGPU mode configuration from FSP using
the PRC (Product Reconfiguration Control) protocol. PRC is an API
system exposed through FSP's Management Partition that allows querying
device configuration "knobs" without firmware updates.
Add a VgpuMode enum that validates the raw PRC response value,
returning an error for unexpected values.
Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
drivers/gpu/nova-core/fsp.rs | 145 ++++++++++++++++++++++++++++++++++
drivers/gpu/nova-core/mctp.rs | 3 +
2 files changed, 148 insertions(+)
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index 5fd2e9e277b1..ce11efeba37e 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -48,6 +48,44 @@
mod hal;
+/// PRC (Product Reconfiguration Control) protocol constants.
+///
+/// PRC is an API system exposed through FSP's Management Partition that allows
+/// querying and modifying device configuration "knobs" without firmware updates.
+/// Each knob is identified by a unique object ID and controls a specific device
+/// behavior (e.g., vGPU mode, ECC, confidential computing).
+mod prc {
+ /// Sub-command to read a PRC knob value.
+ pub(super) const SUBCMD_READ: u8 = 0x0c;
+
+ /// PRC object ID for vGPU mode configuration (knob ID 41).
+ pub(super) const OBJECT_VGPU_MODE: u8 = 0x29;
+
+ /// Request the active knob value (currently effective this boot).
+ pub(super) const FLAG_ACTIVE: u8 = 1 << 1;
+}
+
+/// vGPU operating mode as reported by FSP via the PRC protocol.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum VgpuMode {
+ /// vGPU support is disabled on this GPU.
+ Disabled = 0,
+ /// vGPU support is enabled on this GPU.
+ Enabled = 1,
+}
+
+impl TryFrom<u16> for VgpuMode {
+ type Error = kernel::error::Error;
+
+ fn try_from(value: u16) -> Result<Self> {
+ match value {
+ 0 => Ok(VgpuMode::Disabled),
+ 1 => Ok(VgpuMode::Enabled),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
/// FSP command response payload (`NVDM_PAYLOAD_COMMAND_RESPONSE`).
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -57,6 +95,39 @@ struct NvdmPayloadCommandResponse {
error_code: u32,
}
+// SAFETY: NvdmPayloadCommandResponse is a packed C struct with only integral fields.
+unsafe impl FromBytes for NvdmPayloadCommandResponse {}
+
+/// PRC message payload.
+///
+/// Sent to FSP to query or modify a device configuration knob.
+/// The response includes the common FSP response header followed by
+/// a [`NvdmPayloadPrcResponse`] with the knob's current state value.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct NvdmPayloadPrc {
+ sub_message_id: u8,
+ flags: u8,
+ object_id: u8,
+ reserved: u8,
+}
+
+// SAFETY: NvdmPayloadPrc is a packed C struct with only integral fields.
+unsafe impl AsBytes for NvdmPayloadPrc {}
+
+/// PRC response payload containing the knob state value.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct NvdmPayloadPrcResponse {
+ value_low: u8,
+ value_high: u8,
+ reserved1: u8,
+ reserved2: u8,
+}
+
+// SAFETY: NvdmPayloadPrcResponse is a packed C struct with only integral fields.
+unsafe impl FromBytes for NvdmPayloadPrcResponse {}
+
/// Common MCTP and NVDM headers shared by all FSP messages.
#[repr(C, packed)]
#[derive(Clone, Copy)]
@@ -92,6 +163,18 @@ struct FspResponse {
// SAFETY: FspResponse is a packed C struct with only integral fields.
unsafe impl FromBytes for FspResponse {}
+/// Complete FSP PRC response including the knob state payload.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct FspPrcResponse {
+ header: FspMessageHeader,
+ response: NvdmPayloadCommandResponse,
+ prc_data: NvdmPayloadPrcResponse,
+}
+
+// SAFETY: FspPrcResponse is a packed C struct with only integral fields.
+unsafe impl FromBytes for FspPrcResponse {}
+
/// Trait implemented by types representing a message to send to FSP.
///
/// This provides [`Fsp::send_sync_fsp`] with the information it needs to send
@@ -178,10 +261,25 @@ fn new<'a>(
// bytes are initialized.
unsafe impl AsBytes for FspCotMessage {}
+/// Complete FSP PRC message.
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct FspPrcMessage {
+ header: FspMessageHeader,
+ prc: NvdmPayloadPrc,
+}
+
+// SAFETY: FspPrcMessage is a packed C struct with only integral fields.
+unsafe impl AsBytes for FspPrcMessage {}
+
impl MessageToFsp for FspCotMessage {
const NVDM_TYPE: NvdmType = NvdmType::Cot;
}
+impl MessageToFsp for FspPrcMessage {
+ const NVDM_TYPE: NvdmType = NvdmType::Prc;
+}
+
/// Bundled arguments for FMC boot via FSP Chain of Trust.
pub(crate) struct FmcBootArgs {
chipset: Chipset,
@@ -226,6 +324,53 @@ pub(crate) struct Fsp {
}
impl Fsp {
+ /// Read vGPU mode from FSP using the PRC protocol.
+ ///
+ /// Queries FSP's Management Partition for the active vGPU mode knob value.
+ /// Returns [`VgpuMode::Enabled`] if vGPU support is active on this GPU,
+ /// [`VgpuMode::Disabled`] otherwise.
+ #[expect(dead_code)]
+ pub(crate) fn read_vgpu_mode(
+ &mut self,
+ dev: &device::Device<device::Bound>,
+ bar: Bar0<'_>,
+ ) -> Result<VgpuMode> {
+ let msg = KBox::new(
+ FspPrcMessage {
+ header: FspMessageHeader::new(NvdmType::Prc),
+ prc: NvdmPayloadPrc {
+ sub_message_id: prc::SUBCMD_READ,
+ flags: prc::FLAG_ACTIVE,
+ object_id: prc::OBJECT_VGPU_MODE,
+ reserved: 0,
+ },
+ },
+ GFP_KERNEL,
+ )?;
+
+ let response_buf = self.send_sync_fsp(dev, bar, &*msg)?;
+
+ let prc_resp_size = core::mem::size_of::<FspPrcResponse>();
+ if response_buf.len() < prc_resp_size {
+ dev_err!(
+ dev,
+ "PRC response too small: {} bytes (expected {})\n",
+ response_buf.len(),
+ prc_resp_size
+ );
+ return Err(EIO);
+ }
+
+ let (prc_response, _) = FspPrcResponse::from_bytes_prefix(&response_buf[..]).ok_or(EIO)?;
+
+ let raw_value = u16::from(prc_response.prc_data.value_low)
+ | (u16::from(prc_response.prc_data.value_high) << 8);
+
+ VgpuMode::try_from(raw_value).inspect_err(|_| {
+ dev_err!(dev, "unexpected vGPU mode value: {:#x}\n", raw_value);
+ })
+ }
+
/// Waits for FSP secure boot completion, then returns the [`Fsp`] interface.
///
/// Polls the thermal scratch register until FSP signals boot completion or the timeout
diff --git a/drivers/gpu/nova-core/mctp.rs b/drivers/gpu/nova-core/mctp.rs
index 482786e07bc7..b203c632bf20 100644
--- a/drivers/gpu/nova-core/mctp.rs
+++ b/drivers/gpu/nova-core/mctp.rs
@@ -13,6 +13,8 @@
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum NvdmType {
+ /// PRC (Product Reconfiguration Control) message.
+ Prc = 0x13,
#[default]
/// Chain of Trust boot message.
Cot = 0x14,
@@ -25,6 +27,7 @@ impl TryFrom<u8> for NvdmType {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
+ x if x == u8::from(Self::Prc) => Ok(Self::Prc),
x if x == u8::from(Self::Cot) => Ok(Self::Cot),
x if x == u8::from(Self::FspResponse) => Ok(Self::FspResponse),
_ => Err(value),
--
2.51.0