[PATCH 01/13] gpu: nova-core: vgpu: add post-GSP-boot vGPU initialization

From: Zhi Wang

Date: Sat Sep 05 2026 - 04:12:37 EST


GSP-RM does not expose the parameters needed to divide resources among
vGPU instances until GSP_INIT completes. Before this point VgpuManager
only knows whether vGPU mode is enabled, so it cannot provide the engine
topology, VMMU alignment, or channel capacity required by instance
management.

Decode the VMMU segment size and ordered FIFO engine table from the typed
GSP_INIT NVKV response. Retain only host-driven engines while preserving
hardware FIFO order. After a successful GSP_INIT, initialize the manager
with these values and the 2048-channel capacity, but retain them only when
vGPU mode is enabled.

Move VgpuManager into Gpu and let it borrow the pinned ChannelIdPool.
Create that pool with the same channel capacity, order the Gpu fields so
the manager is dropped before the memory manager, GSP resources, and its
channel pool, and pass it separately to GSP boot so unload resources do
not retain a manager reference. Keep a copy of the detected mode in the
GPU-owned GSP runtime data instead of passing it through the HAL calls.

Co-developed-by: Alok Kumar <alkumar@xxxxxxxxxx>
Signed-off-by: Alok Kumar <alkumar@xxxxxxxxxx>
Signed-off-by: Zhi Wang <zhiw@xxxxxxxxxx>
---
drivers/gpu/nova-core/gpu.rs | 53 ++++++---
drivers/gpu/nova-core/gpu/channel.rs | 3 +
drivers/gpu/nova-core/gsp.rs | 12 +-
drivers/gpu/nova-core/gsp/boot.rs | 13 ++-
drivers/gpu/nova-core/gsp/commands.rs | 33 ++++++
drivers/gpu/nova-core/gsp/fw/commands.rs | 36 ++++++
drivers/gpu/nova-core/gsp/hal/gh100.rs | 2 +-
drivers/gpu/nova-core/gsp/hal/tu102.rs | 2 +-
drivers/gpu/nova-core/vgpu.rs | 91 ---------------
drivers/gpu/nova-core/vgpu/mod.rs | 139 +++++++++++++++++++++++
10 files changed, 274 insertions(+), 110 deletions(-)
delete mode 100644 drivers/gpu/nova-core/vgpu.rs
create mode 100644 drivers/gpu/nova-core/vgpu/mod.rs

diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 2105e993ee60..a2189589422a 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -53,6 +53,11 @@
mod channel;
mod hal;

+pub(crate) use self::channel::{
+ ChannelIdPool,
+ TOTAL_CHANNELS, //
+};
+
macro_rules! define_chipset {
({ $($variant:ident = $value:expr),* $(,)* }) =>
{
@@ -291,8 +296,6 @@ struct GspResources<'gpu> {
// TODO: use different resource types for each boot method, and make the relevant Gsp methods
// generic against them.
fsp: Option<Fsp<'gpu>>,
- /// vGPU state detected before GSP boot.
- vgpu: VgpuManager,
/// GSP runtime data.
#[pin]
gsp: Gsp,
@@ -304,6 +307,10 @@ struct GspResources<'gpu> {
#[pin_data]
pub(crate) struct Gpu<'gpu> {
spec: Spec,
+ /// vGPU state and firmware parameters.
+ ///
+ /// Declared before MM and BAR1 so live instances are torn down before their resources.
+ vgpu: VgpuManager<'gpu>,
/// GPU memory manager owning memory management resources.
///
/// Must be kept declared *before* `gsp_resources`, so that its components are dropped while
@@ -314,6 +321,11 @@ pub(crate) struct Gpu<'gpu> {
/// GSP and its resources.
#[pin]
gsp_resources: GspResources<'gpu>,
+ /// Channel ID pool borrowed by the vGPU manager and its live instances.
+ ///
+ /// Declared after `vgpu` so the manager is dropped before the pool.
+ #[pin]
+ chid_pool: ChannelIdPool,
/// System memory page required for flushing all pending GPU-side memory writes done through
/// PCIE into system memory, via sysmembar (A GPU-initiated HW memory-barrier operation).
///
@@ -342,7 +354,6 @@ fn drop(self: Pin<&mut Self>) {
gsp_falcon: &*this.gsp_falcon,
sec2_falcon: &*this.sec2_falcon,
fsp: this.fsp.as_mut(),
- vgpu: &*this.vgpu,
},
bundle,
)
@@ -402,6 +413,18 @@ pub(crate) fn new(
// Initialize this early because `gsp_resources` depends on it.
sysmem_flush: SysmemFlush::register(dev, bar, spec.chipset)?,

+ chid_pool <- ChannelIdPool::new(cv!(TOTAL_CHANNELS as usize)),
+
+ // TODO: Use `&chid_pool` self-referential pin-init syntax once available.
+ //
+ // SAFETY: `chid_pool` is initialized before this expression and lives at a pinned
+ // stable address. Field order drops `vgpu` before `chid_pool`, including unwind of
+ // an incomplete initializer.
+ vgpu: VgpuManager::new(
+ // SAFETY: The lifetime and drop-order rationale above covers this borrow.
+ unsafe { &*core::ptr::from_ref(chid_pool.as_ref().get_ref()) },
+ ),
+
gsp_resources <- try_pin_init!(GspResources {
device: pdev,

@@ -420,22 +443,26 @@ pub(crate) fn new(

fsp: Fsp::try_new(dev, bar, spec.chipset)?,

- vgpu: VgpuManager::new(pdev, spec.chipset, fsp.as_mut()),
+ _: {
+ vgpu.detect_state(pdev, spec.chipset, fsp.as_mut());
+ },

- gsp <- Gsp::new(pdev, spec.chipset),
+ gsp <- Gsp::new(pdev, spec.chipset, vgpu.state()),

// 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.
- boot_result: gsp.boot(GspBootContext {
- pdev,
- bar,
- chipset: spec.chipset,
- gsp_falcon,
- sec2_falcon,
- fsp: fsp.as_mut(),
+ boot_result: gsp.boot(
+ GspBootContext {
+ pdev,
+ bar,
+ chipset: spec.chipset,
+ gsp_falcon,
+ sec2_falcon,
+ fsp: fsp.as_mut(),
+ },
vgpu,
- })?,
+ )?,
}),

_: {
diff --git a/drivers/gpu/nova-core/gpu/channel.rs b/drivers/gpu/nova-core/gpu/channel.rs
index 485efaba059d..9bf2edddca03 100644
--- a/drivers/gpu/nova-core/gpu/channel.rs
+++ b/drivers/gpu/nova-core/gpu/channel.rs
@@ -21,6 +21,9 @@
}, //
};

+/// Total channel ID capacity available to vGPU instances.
+pub(crate) const TOTAL_CHANNELS: u32 = 2048;
+
/// Pool for tracking reservations of channel IDs.
#[pin_data]
pub(crate) struct ChannelIdPool {
diff --git a/drivers/gpu/nova-core/gsp.rs b/drivers/gpu/nova-core/gsp.rs
index 700842240c22..deaae033a88f 100644
--- a/drivers/gpu/nova-core/gsp.rs
+++ b/drivers/gpu/nova-core/gsp.rs
@@ -60,7 +60,7 @@
fw::GspArgumentsPadded, //
},
num,
- vgpu::VgpuManager, //
+ vgpu::VgpuState, //
};

pub(crate) const GSP_PAGE_SHIFT: usize = 12;
@@ -79,7 +79,6 @@ pub(crate) struct GspBootContext<'ctx, 'gpu> {
pub(crate) gsp_falcon: &'ctx Falcon<'gpu, GspFalcon>,
pub(crate) sec2_falcon: &'ctx Falcon<'gpu, Sec2Falcon>,
pub(crate) fsp: Option<&'ctx mut Fsp<'gpu>>,
- pub(crate) vgpu: &'ctx VgpuManager,
}

impl<'ctx, 'gpu> GspBootContext<'ctx, 'gpu> {
@@ -282,6 +281,8 @@ struct LogBuffers {
pub(crate) struct Gsp {
/// Preloaded GSP firmware TLV metadata used during boot.
gsp_tlv: kernel::firmware::Firmware,
+ /// vGPU mode detected before GSP boot.
+ vgpu_state: VgpuState,
/// Libos arguments.
pub(crate) libos: Coherent<[LibosMemoryRegionInitArgument]>,
/// Log buffers for all LIBOS3 tasks, exposed via debugfs.
@@ -300,6 +301,7 @@ impl Gsp {
pub(crate) fn new(
pdev: &pci::Device<device::Bound>,
chipset: Chipset,
+ vgpu_state: VgpuState,
) -> impl PinInit<Self, Error> + '_ {
pin_init::pin_init_scope(move || {
let dev = pdev.as_ref();
@@ -323,6 +325,7 @@ pub(crate) fn new(

Ok(try_pin_init!(Self {
gsp_tlv,
+ vgpu_state,
cmdq: Arc::pin_init(Cmdq::new(dev), GFP_KERNEL)?,
rm_state_monitor: Coherent::zeroed(dev, GFP_KERNEL)?,
rmargs: Coherent::init(
@@ -398,6 +401,11 @@ pub(crate) fn new(
})
}

+ /// Returns the vGPU mode detected for this boot.
+ pub(crate) const fn vgpu_state(&self) -> VgpuState {
+ self.vgpu_state
+ }
+
/// 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/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 9c4d25f609de..2d027d2a9a35 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -37,6 +37,7 @@
gsp::GspFirmware,
radix3::Radix3, //
},
+ gpu::TOTAL_CHANNELS,
gsp::{
cmdq::{
Cmdq,
@@ -52,7 +53,8 @@
}, //
},
num,
- regs, //
+ regs,
+ vgpu::VgpuManager, //
};

impl super::Gsp {
@@ -72,6 +74,7 @@ impl super::Gsp {
pub(crate) fn boot(
self: Pin<&mut Self>,
mut ctx: super::GspBootContext<'_, '_>,
+ vgpu: &mut VgpuManager<'_>,
) -> Result<super::BootResult> {
let pdev = ctx.pdev;
let bar = ctx.bar;
@@ -128,7 +131,7 @@ pub(crate) fn boot(
// 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())?;
+ let init_payload = commands::build_gsp_init_payload(pdev, chipset, self.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) {
@@ -155,6 +158,12 @@ pub(crate) fn boot(
)
})?;

+ vgpu.init(
+ &static_info.fifo_engine_list,
+ static_info.vmmu_segment_size,
+ TOTAL_CHANNELS,
+ );
+
Ok(super::BootResult::new(
unload_guard.dismiss().1,
static_info,
diff --git a/drivers/gpu/nova-core/gsp/commands.rs b/drivers/gpu/nova-core/gsp/commands.rs
index 02bb9d653253..89369ff23b85 100644
--- a/drivers/gpu/nova-core/gsp/commands.rs
+++ b/drivers/gpu/nova-core/gsp/commands.rs
@@ -49,6 +49,19 @@
vgpu::VgpuState, //
};

+/// Upper bound on entries in the hardware FIFO engine table.
+pub(crate) const MAX_FIFO_ENGINES: usize = 64;
+
+/// Bit mask for `NVGMC_SC_ENGINE_FLAGS_IS_HOST_DRIVEN` (bits 0:0).
+const ENGINE_FLAGS_IS_HOST_DRIVEN: u32 = 1 << 0;
+
+/// Ordered list of host-driven GMC engine IDs from the hardware FIFO engine table.
+#[derive(Copy, Clone)]
+pub(crate) struct FifoEngineList {
+ pub(crate) gmc_ids: [u32; MAX_FIFO_ENGINES],
+ pub(crate) count: usize,
+}
+
/// The static GPU configuration, as decoded from the `GSP_INIT` reply.
pub(crate) struct GetGspStaticInfoReply {
gpu_name: [u8; 64],
@@ -58,6 +71,10 @@ pub(crate) struct GetGspStaticInfoReply {
pub(crate) usable_fb_regions: KVec<Range<u64>>,
/// Exclusive end of the FB physical address space.
pub(crate) total_fb_end: u64,
+ /// VMMU segment size reported by GSP-RM, in bytes.
+ pub(crate) vmmu_segment_size: u64,
+ /// Ordered host-driven FIFO engine GMC IDs.
+ pub(crate) fifo_engine_list: FifoEngineList,
}

/// Error type for [`GetGspStaticInfoReply::gpu_name`].
@@ -289,12 +306,28 @@ fn decode_gsp_info(words: &[u64]) -> Result<GetGspStaticInfoReply> {
usable_fb_regions.push(region, GFP_KERNEL)?;
}
let total_fb_end = decoded.total_fb_end().ok_or(EINVAL)?;
+ let vmmu_segment_size = decoded.vmmu_segment_size();
+ let fifo_count = decoded.fifo_engine_count();
+ let raw_ids = decoded.fifo_engine_gmc_ids();
+ let raw_flags = decoded.fifo_engine_flags();
+ let mut fifo_engine_list = FifoEngineList {
+ gmc_ids: [0; MAX_FIFO_ENGINES],
+ count: 0,
+ };
+ for index in 0..fifo_count {
+ if raw_flags[index] & ENGINE_FLAGS_IS_HOST_DRIVEN != 0 {
+ fifo_engine_list.gmc_ids[fifo_engine_list.count] = raw_ids[index];
+ fifo_engine_list.count += 1;
+ }
+ }

Ok(GetGspStaticInfoReply {
gpu_name,
bar1_pde_base: decoded.bar1_pde_base(),
usable_fb_regions,
total_fb_end,
+ vmmu_segment_size,
+ fifo_engine_list,
})
}

diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 22e4546142f0..1610b005199f 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -26,6 +26,7 @@
Encodeable,
Encoder,
Index,
+ Indexed,
Key,
KeyId,
Required, //
@@ -295,6 +296,14 @@ pub(crate) struct GspInitResponseSchema => GspInitResponse {
fb_regions: Accumulated<FbRegionSchema>,
bar1_pde_base: Required<u64, { Self::BAR1_PDE_BASE_KEY }>,
vmmu_segment_size: Key<u64, { Self::VMMU_SEGMENT_SIZE_KEY }>,
+ fifo_engine_count: Key<u32, { Self::FIFO_ENGINE_COUNT_KEY }>,
+ fifo_engine_gmc_ids: Indexed<
+ u32,
+ { GspInitResponse::MAX_FIFO_ENGINES },
+ { Self::FIFO_ENGINE_GMC_ENGINE_ID_KEY },
+ >,
+ fifo_engine_flags:
+ Indexed<u32, { GspInitResponse::MAX_FIFO_ENGINES }, { Self::FIFO_ENGINE_FLAGS_KEY }>,
}
}

@@ -303,6 +312,9 @@ impl GspInitResponseSchema {
const GPU_NAME_STRING_KEY: KeyId = 0x2000;
const BAR1_PDE_BASE_KEY: KeyId = 0x1020;
const VMMU_SEGMENT_SIZE_KEY: KeyId = 0x1050;
+ const FIFO_ENGINE_COUNT_KEY: KeyId = 0x0500;
+ const FIFO_ENGINE_GMC_ENGINE_ID_KEY: KeyId = 0x0501;
+ const FIFO_ENGINE_FLAGS_KEY: KeyId = 0x0502;
}

/// Payload of the `GSP_INIT` response.
@@ -312,10 +324,14 @@ pub(crate) struct GspInitResponse {
fb_regions: KVVec<FbRegion>,
bar1_pde_base: u64,
vmmu_segment_size: u64,
+ fifo_engine_count: u32,
+ fifo_engine_gmc_ids: [u32; Self::MAX_FIFO_ENGINES],
+ fifo_engine_flags: [u32; Self::MAX_FIFO_ENGINES],
}

impl GspInitResponse {
pub(crate) const MAX_GPU_NAME_LEN: usize = 64;
+ const MAX_FIFO_ENGINES: 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.
@@ -358,6 +374,26 @@ pub(crate) fn total_fb_end(&self) -> Option<u64> {
.max()?
.checked_add(1)
}
+
+ /// Returns the VMMU segment size reported by GSP-RM.
+ pub(crate) const fn vmmu_segment_size(&self) -> u64 {
+ self.vmmu_segment_size
+ }
+
+ /// Returns the count of FIFO engines reported by GSP-RM.
+ pub(crate) fn fifo_engine_count(&self) -> usize {
+ (self.fifo_engine_count as usize).min(Self::MAX_FIFO_ENGINES)
+ }
+
+ /// Returns the raw array of GMC engine IDs from the FIFO engine table.
+ pub(crate) fn fifo_engine_gmc_ids(&self) -> &[u32; Self::MAX_FIFO_ENGINES] {
+ &self.fifo_engine_gmc_ids
+ }
+
+ /// Returns the raw array of per-engine flags from the FIFO engine table.
+ pub(crate) fn fifo_engine_flags(&self) -> &[u32; Self::MAX_FIFO_ENGINES] {
+ &self.fifo_engine_flags
+ }
}

nvkv_decode! {
diff --git a/drivers/gpu/nova-core/gsp/hal/gh100.rs b/drivers/gpu/nova-core/gsp/hal/gh100.rs
index e283429a95dd..41c18d623b7a 100644
--- a/drivers/gpu/nova-core/gsp/hal/gh100.rs
+++ b/drivers/gpu/nova-core/gsp/hal/gh100.rs
@@ -151,7 +151,7 @@ fn boot(
let chipset = ctx.chipset;
let gsp_falcon = ctx.gsp_falcon;

- let fb_sizes = FbSizes::new(chipset, ctx.bar, ctx.vgpu.state())?;
+ let fb_sizes = FbSizes::new(chipset, ctx.bar, gsp.vgpu_state())?;
dev_dbg!(dev, "{:#x?}\n", fb_sizes);

let wpr_meta =
diff --git a/drivers/gpu/nova-core/gsp/hal/tu102.rs b/drivers/gpu/nova-core/gsp/hal/tu102.rs
index 74ea172726cb..bd47787348c8 100644
--- a/drivers/gpu/nova-core/gsp/hal/tu102.rs
+++ b/drivers/gpu/nova-core/gsp/hal/tu102.rs
@@ -259,7 +259,7 @@ fn boot(
let gsp_falcon = ctx.gsp_falcon;
let sec2_falcon = ctx.sec2_falcon;

- let fb_ranges = FbRanges::new(chipset, bar, gsp_fw, ctx.vgpu.state())?;
+ let fb_ranges = FbRanges::new(chipset, bar, gsp_fw, gsp.vgpu_state())?;
dev_dbg!(dev, "{:#x?}\n", fb_ranges);

// Declared before the unload guard so that if Booter fails while running, SEC2 is reset
diff --git a/drivers/gpu/nova-core/vgpu.rs b/drivers/gpu/nova-core/vgpu.rs
deleted file mode 100644
index 6b7e045acea8..000000000000
--- a/drivers/gpu/nova-core/vgpu.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-use core::num::NonZero;
-
-use kernel::{
- device,
- pci,
- prelude::*, //
-};
-
-use crate::{
- fsp::{
- Fsp,
- VgpuMode, //
- },
- gpu::Chipset, //
-};
-
-mod hal;
-
-/// vGPU state detected during GPU construction.
-#[derive(Debug, Clone, Copy)]
-pub(crate) enum VgpuState {
- /// vGPU mode is not enabled for this boot.
- Disabled,
- /// vGPU mode is enabled for this boot.
- Enabled {
- /// Total number of SR-IOV VFs supported by this device.
- total_vfs: NonZero<u16>,
- },
-}
-
-/// vGPU state manager.
-pub(crate) struct VgpuManager {
- state: VgpuState,
-}
-
-impl VgpuManager {
- /// Creates a vGPU manager by querying SR-IOV and the FSP PRC vGPU knob.
- pub(crate) fn new(
- pdev: &pci::Device<device::Core<'_>>,
- chipset: Chipset,
- fsp: Option<&mut Fsp<'_>>,
- ) -> Self {
- let state = Self::detect_state(pdev, chipset, fsp).unwrap_or_else(|e| {
- dev_warn!(
- pdev,
- "vGPU state detection failed: {:?}; disabling vGPU\n",
- e
- );
- VgpuState::Disabled
- });
- dev_dbg!(pdev, "vGPU state: {:?}\n", state);
-
- Self { state }
- }
-
- /// Detects the vGPU state from the chipset, SR-IOV capability and FSP PRC knob.
- fn detect_state(
- pdev: &pci::Device<device::Core<'_>>,
- chipset: Chipset,
- fsp: Option<&mut Fsp<'_>>,
- ) -> Result<VgpuState> {
- if !hal::vgpu_hal(chipset).supports_vgpu() {
- return Ok(VgpuState::Disabled);
- }
-
- let Some(total_vfs) = pdev.sriov_get_totalvfs() else {
- return Ok(VgpuState::Disabled);
- };
-
- if total_vfs.get() < 2 {
- // The current vGPU path does not support single-VF SR-IOV devices yet.
- // Treat one total VF as vGPU-disabled for now; single-VF support can relax
- // this gate once the manager handles that topology.
- return Ok(VgpuState::Disabled);
- }
-
- let fsp = fsp.ok_or(ENODEV)?;
-
- match fsp.read_vgpu_mode(pdev.as_ref())? {
- VgpuMode::Enabled => Ok(VgpuState::Enabled { total_vfs }),
- VgpuMode::Disabled => Ok(VgpuState::Disabled),
- }
- }
-
- /// Returns the detected vGPU state for this boot.
- pub(crate) fn state(&self) -> VgpuState {
- self.state
- }
-}
diff --git a/drivers/gpu/nova-core/vgpu/mod.rs b/drivers/gpu/nova-core/vgpu/mod.rs
new file mode 100644
index 000000000000..1354c662a507
--- /dev/null
+++ b/drivers/gpu/nova-core/vgpu/mod.rs
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+use core::num::NonZero;
+
+use kernel::{
+ device,
+ pci,
+ prelude::*, //
+};
+
+use crate::{
+ fsp::{
+ Fsp,
+ VgpuMode, //
+ },
+ gpu::{
+ ChannelIdPool,
+ Chipset, //
+ },
+ gsp::commands::FifoEngineList, //
+};
+
+mod hal;
+
+/// vGPU state detected during GPU construction.
+#[derive(Debug, Clone, Copy)]
+pub(crate) enum VgpuState {
+ /// vGPU mode is not enabled for this boot.
+ Disabled,
+ /// vGPU mode is enabled for this boot.
+ Enabled {
+ /// Total number of SR-IOV VFs supported by this device.
+ total_vfs: NonZero<u16>,
+ },
+}
+
+/// vGPU state manager.
+pub(crate) struct VgpuManager<'gpu> {
+ /// Channel ID pool the per-VF areas are reserved from.
+ #[expect(dead_code)]
+ pub(crate) chid_pool: &'gpu ChannelIdPool,
+ state: VgpuState,
+ vmmu_segment_size: Option<u64>,
+ total_channels: Option<u32>,
+ fifo_engine_list: Option<FifoEngineList>,
+}
+
+impl<'gpu> VgpuManager<'gpu> {
+ /// Creates an empty vGPU manager for initialization during GPU construction.
+ pub(crate) const fn new(chid_pool: &'gpu ChannelIdPool) -> Self {
+ Self {
+ chid_pool,
+ state: VgpuState::Disabled,
+ vmmu_segment_size: None,
+ total_channels: None,
+ fifo_engine_list: None,
+ }
+ }
+
+ /// Detects and stores vGPU state before GSP boot.
+ pub(crate) fn detect_state(
+ &mut self,
+ pdev: &pci::Device<device::Core<'_>>,
+ chipset: Chipset,
+ fsp: Option<&mut Fsp<'_>>,
+ ) {
+ let state: Result<VgpuState> = (|| {
+ if !hal::vgpu_hal(chipset).supports_vgpu() {
+ return Ok(VgpuState::Disabled);
+ }
+
+ let Some(total_vfs) = pdev.sriov_get_totalvfs() else {
+ return Ok(VgpuState::Disabled);
+ };
+
+ if total_vfs.get() < 2 {
+ // The current vGPU path does not support single-VF SR-IOV devices yet.
+ // Treat one total VF as vGPU-disabled for now; single-VF support can relax
+ // this gate once the manager handles that topology.
+ return Ok(VgpuState::Disabled);
+ }
+
+ let fsp = fsp.ok_or(ENODEV)?;
+
+ match fsp.read_vgpu_mode(pdev.as_ref())? {
+ VgpuMode::Enabled => Ok(VgpuState::Enabled { total_vfs }),
+ VgpuMode::Disabled => Ok(VgpuState::Disabled),
+ }
+ })();
+
+ self.state = state.unwrap_or_else(|e| {
+ dev_warn!(
+ pdev,
+ "vGPU state detection failed: {:?}; disabling vGPU\n",
+ e
+ );
+ VgpuState::Disabled
+ });
+ dev_dbg!(pdev, "vGPU state: {:?}\n", self.state);
+ }
+
+ /// Returns the detected vGPU state for this boot.
+ pub(crate) fn state(&self) -> VgpuState {
+ self.state
+ }
+
+ /// Initializes the runtime parameters returned by GSP_INIT.
+ pub(crate) fn init(
+ &mut self,
+ fifo_engine_list: &FifoEngineList,
+ vmmu_segment_size: u64,
+ total_channels: u32,
+ ) {
+ if matches!(self.state, VgpuState::Enabled { .. }) {
+ self.vmmu_segment_size = Some(vmmu_segment_size);
+ self.total_channels = Some(total_channels);
+ self.fifo_engine_list = Some(*fifo_engine_list);
+ }
+ }
+
+ /// Returns the firmware-reported VMMU segment size when vGPU is enabled.
+ #[expect(dead_code)]
+ pub(crate) const fn vmmu_segment_size(&self) -> Option<u64> {
+ self.vmmu_segment_size
+ }
+
+ /// Returns the number of channel IDs available to vGPU instances.
+ #[expect(dead_code)]
+ pub(crate) const fn total_channels(&self) -> Option<u32> {
+ self.total_channels
+ }
+
+ /// Returns the ordered FIFO engine list provided by GSP_INIT.
+ #[expect(dead_code)]
+ pub(crate) fn fifo_engine_list(&self) -> Result<&FifoEngineList> {
+ self.fifo_engine_list.as_ref().ok_or(ENODEV)
+ }
+}
--
2.53.0