summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 15:45:55 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 15:45:55 -0400
commitf3598fc8115bfa8afe5833520419e5819db0ef35 (patch)
treec0be6df14d41af448b7b09022a73a5df0bbfac55 /kernel
parent2745906f5cc359dff8eb13178eca6226d9f41a14 (diff)
downloadopen-iov-f3598fc8115bfa8afe5833520419e5819db0ef35.tar.gz
open-iov-f3598fc8115bfa8afe5833520419e5819db0ef35.zip
kernel: capture the guest's PROMOTE_CTX entries; add promote mode 19 (guest-faithful 9-entry set)
- gsp_rm_emulation: dump every guest GPU_PROMOTE_CTX (0x2080012b) params and entries so the real client's promote shape is observable (captured on the emulation path: MAIN 2 MB PA+VA, PATCH, BUNDLE_CB/PAGEPOOL/ATTRIBUTE_CB/ RTV_CB_GLOBAL VA-only, FECS, PRIV, UNRESTRICTED) - gsp_phys_booter: promote mode 19 replays that exact 9-entry shape on the V5 channel with host buffers (MAIN 2 MB + PDB map, small buffers, booking-only VAs); clamp 0..19 - docs/eng-ops: mode-19 result recorded - the server never replied (fn76 -110) and the release fail-safe held WPR2/GPU until an approved reboot; mode 19 is known-wedging
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_booter.c215
-rw-r--r--kernel/gsp_phys_gpu.c6
-rw-r--r--kernel/gsp_rm_emulation.c61
3 files changed, 276 insertions, 6 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index afbfe28..3d1ccea 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -340,6 +340,13 @@ bool gsp_phys_boot_requested(void)
#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_MAIN 0u
#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PM 1u
#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PATCH 2u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_BUNDLE_CB 3u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PAGEPOOL 4u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_ATTRIBUTE_CB 5u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_RTV_CB_GLOBAL 6u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_FECS_EVENT 9u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PRIV_MAP 10u
+#define GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_UNR_PRIV_MAP 11u
/*
* GR_PATCH engine id in NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES
* (ctrl/ctrl0080/ctrl0080fifo.h:137 -> 0x10), the size source for the
@@ -420,8 +427,21 @@ bool gsp_phys_boot_requested(void)
* (VEID 0) whose absence is the leading candidate for the promote's 0x40.
*/
#define GSP_PHYS_RMCALL_GR_PROMOTE_CTXSHARE 18u
+/*
+ * Mode 19 (2026-10-02): the exact promote shape the stock guest driver sends
+ * (captured from the emulation path). One control, entryCount=9:
+ * MAIN bInit=1 bNonmapped=0 attr=0x6 PA+VA size=0x200000,
+ * PATCH bInit=1 bNonmapped=0 attr=0x6 PA+VA size=0x2000,
+ * BUNDLE_CB/PAGEPOOL/ATTRIBUTE_CB/RTV_CB_GLOBAL VA-only,
+ * FECS_EVENT bInit=1 bNonmapped=0 attr=0x6 PA+VA size=0x1000,
+ * PRIV_ACCESS_MAP bInit=1 bNonmapped=1 attr=0x6 PA size=0x1000,
+ * UNRESTRICTED_PRIV_ACCESS_MAP bInit=1 bNonmapped=0 attr=0x6 PA+VA size=0x1000.
+ * MAIN is 2 MB (the guest's size, larger than our server-query total) and
+ * carries both PA and VA in one entry, on the non-KMD V5 channel.
+ */
+#define GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET 19u
#define GSP_PHYS_RMCALL_GR_PROMOTE_MAX \
- (GSP_PHYS_RMCALL_GR_PROMOTE_CTXSHARE)
+ (GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET)
/*
* Work-submit token notifier slot. The default index is
@@ -1282,6 +1302,18 @@ static struct gsp_phys_boot_stage {
u32 rmcall_pm_size; /* queried PM save size */
struct gsp_phys_boot_chunk rmcall_pm;
/*
+ * Mode 19: the guest-faithful full promote set's small buffers
+ * (FECS event, private access map, unrestricted private access map).
+ * PATCH uses rmcall_patch with the guest's 0x2000 size.
+ */
+ bool rmcall_guest_set_prepared;
+ struct gsp_phys_boot_chunk rmcall_fecs;
+ struct gsp_phys_boot_chunk rmcall_priv;
+ struct gsp_phys_boot_chunk rmcall_unrestricted;
+ u64 rmcall_fecs_pa;
+ u64 rmcall_priv_pa;
+ u64 rmcall_unrestricted_pa;
+ /*
* GR context-promote bisection snapshot (stage 3, GR0 only): the 0..3
* gsp_phys_rmcall_gr_promote value, the number of accepted promote
* controls (0 = none, 1 = PA/init accepted, 2 = VA accepted; mode 2
@@ -1900,6 +1932,10 @@ static void gsp_phys_rmcall_reset(void)
boot_stage.rmcall_pm_prepared = false;
boot_stage.rmcall_pm_pa = 0;
boot_stage.rmcall_pm_size = 0;
+ boot_stage.rmcall_guest_set_prepared = false;
+ boot_stage.rmcall_fecs_pa = 0;
+ boot_stage.rmcall_priv_pa = 0;
+ boot_stage.rmcall_unrestricted_pa = 0;
boot_stage.rmcall_gr_promote = 0;
boot_stage.rmcall_gr_promote_valid = false;
boot_stage.rmcall_gr_promote_change_logged = false;
@@ -6694,6 +6730,8 @@ static const char *gsp_phys_rmcall_gr_promote_name(void)
return "no client promote; server-managed ctx buffers (gsp_phys_srv_ctxbuf=1)";
case GSP_PHYS_RMCALL_GR_PROMOTE_CTXSHARE:
return "mode 8 shape plus a client FERMI_CONTEXT_SHARE_A (hContextShare) that allocates the GraphicsSubcontext";
+ case GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET:
+ return "stock guest's 9-entry promote set (MAIN 2 MB PA+VA, PATCH/global/FECS/priv maps) on V5";
default:
return "off (PROMOTE_CTX never sent)";
}
@@ -6884,7 +6922,9 @@ static int gsp_phys_rmcall_alloc_channel(void __iomem *bar0)
(boot_stage.rmcall_gr_promote ==
GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY ||
boot_stage.rmcall_gr_promote ==
- GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY_PATCH)) {
+ GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY_PATCH ||
+ boot_stage.rmcall_gr_promote ==
+ GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET)) {
vars = &gsp_phys_rmcall_channel_variants[0];
attempts = 1;
}
@@ -7353,6 +7393,14 @@ static int gsp_phys_rmcall_gr_ctx_prepare(void __iomem *bar0)
total = ALIGN((u64)raw, GSP_PHYS_PAGE_SIZE) +
(u64)max_subctx * GSP_PHYS_PAGE_SIZE;
+ /*
+ * Mode 19 replays the stock guest's promote: its MAIN entry is 2 MB
+ * (the guest-side context buffer size), so allocate and promote that
+ * exact size instead of the server-query total.
+ */
+ if (boot_stage.rmcall_gr_promote ==
+ GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET)
+ total = 2u * 1024u * 1024u;
if (!total || total > GSP_PHYS_RMCALL_GR_CTX_MAX_SIZE) {
pr_err("vgsp-boot: rm-call: GR ctx MAIN size 0x%llx (raw=0x%x maxSubctx=%u) is outside the 1..0x%x sanity bound; aborting the ladder with no retry\n",
total, raw, max_subctx, GSP_PHYS_RMCALL_GR_CTX_MAX_SIZE);
@@ -7450,6 +7498,27 @@ err_free:
}
/*
+ * Fill one promote entry explicitly (mode 19). All fields as the stock
+ * guest's captured promote: bufferId, PA, VA, size, physAttr, and the
+ * initialize/nonmapped flags.
+ */
+static void gsp_phys_rmcall_promote_entry_fill(
+ struct gsp_phys_rmcall_promote_params *p, unsigned int idx, u16 bid,
+ u64 pa, u64 va, u64 size, u32 attr, u8 init, u8 nonmapped)
+{
+ struct gsp_phys_rmcall_promote_entry *e = &p->entry[idx];
+
+ memset(e, 0, sizeof(*e));
+ e->gpu_phys_addr = cpu_to_le64(pa);
+ e->gpu_virt_addr = cpu_to_le64(va);
+ e->size = cpu_to_le64(size);
+ e->phys_attr = cpu_to_le32(attr);
+ e->buffer_id = cpu_to_le16(bid);
+ e->b_initialize = init;
+ e->b_nonmapped = nonmapped;
+}
+
+/*
* Auxiliary GR context-buffer prepare (PATCH/PM): query the save size for
* `engine_id` (GET_ENGINE_CONTEXT_PROPERTIES), allocate a zeroed physically
* contiguous sysmem buffer and record its PA/size. The real client allocates
@@ -7537,6 +7606,70 @@ static int gsp_phys_rmcall_gr_pm_prepare(void __iomem *bar0)
}
/*
+ * Mode-19 helper: allocate the small buffers of the guest-faithful promote
+ * set with the guest's sizes (PATCH 0x2000, FECS event 0x1000, private access
+ * map 0x1000, unrestricted private access map 0x1000). All sysmem,
+ * contiguous, zeroed; a failure aborts the ladder with no retry.
+ */
+static int gsp_phys_rmcall_gr_guest_set_prepare(void __iomem *bar0)
+{
+ struct gsp_phys_boot_chunk *patch = &boot_stage.rmcall_patch;
+ struct gsp_phys_boot_chunk *fecs = &boot_stage.rmcall_fecs;
+ struct gsp_phys_boot_chunk *priv = &boot_stage.rmcall_priv;
+ struct gsp_phys_boot_chunk *unr = &boot_stage.rmcall_unrestricted;
+ int ret;
+
+ (void)bar0;
+ if (!boot_stage.pdev)
+ return -EINVAL;
+ if (boot_stage.rmcall_guest_set_prepared)
+ return 0;
+
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, patch, 2u);
+ if (ret || patch->page_dma)
+ goto fail_patch;
+ gsp_phys_chunk_zero(patch);
+ boot_stage.rmcall_patch_pa = (u64)patch->dma;
+ boot_stage.rmcall_patch_size = 0x2000u;
+ boot_stage.rmcall_patch_prepared = true;
+
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, fecs, 1u);
+ if (ret || fecs->page_dma)
+ goto fail_fecs;
+ gsp_phys_chunk_zero(fecs);
+ boot_stage.rmcall_fecs_pa = (u64)fecs->dma;
+
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, priv, 1u);
+ if (ret || priv->page_dma)
+ goto fail_priv;
+ gsp_phys_chunk_zero(priv);
+ boot_stage.rmcall_priv_pa = (u64)priv->dma;
+
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, unr, 1u);
+ if (ret || unr->page_dma)
+ goto fail_unr;
+ gsp_phys_chunk_zero(unr);
+ boot_stage.rmcall_unrestricted_pa = (u64)unr->dma;
+
+ boot_stage.rmcall_guest_set_prepared = true;
+ pr_info("vgsp-boot: rm-call: GR guest-set buffers prepared: patch pa=0x%llx size=0x2000, fecs pa=0x%llx, priv pa=0x%llx, unrestricted pa=0x%llx (all sysmem contiguous zeroed)\n",
+ boot_stage.rmcall_patch_pa, boot_stage.rmcall_fecs_pa,
+ boot_stage.rmcall_priv_pa, boot_stage.rmcall_unrestricted_pa);
+ return 0;
+
+fail_unr:
+ gsp_phys_chunk_free(boot_stage.pdev, priv);
+fail_priv:
+ gsp_phys_chunk_free(boot_stage.pdev, fecs);
+fail_fecs:
+ gsp_phys_chunk_free(boot_stage.pdev, patch);
+fail_patch:
+ pr_err("vgsp-boot: rm-call: GR guest-set buffer allocation failed (%d); aborting the ladder with no retry\n",
+ ret);
+ return ret ? ret : -ENOMEM;
+}
+
+/*
* Fill one MAIN promote entry with the prepared PA/VA and the snapshot's
* entry size. initialize/nonmapped carry the bisection shape: modes 1/3/5
* entry 0 and mode 2 step 1 use a full field set; mode 2 step 2 repeats the
@@ -7726,7 +7859,8 @@ static int gsp_phys_rmcall_gr_ctx_promote(void __iomem *bar0)
* modes 8-11).
*/
if ((mode == GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY ||
- mode == GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY_PATCH) &&
+ mode == GSP_PHYS_RMCALL_GR_PROMOTE_PA_ONLY_PATCH ||
+ mode == GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET) &&
boot_stage.rmcall_channel_variant !=
gsp_phys_rmcall_channel_variants[0].id) {
pr_err("vgsp-boot: rm-call: mode %u requires the V5 channel (V%u %s); winning variant is V%u; no PROMOTE_CTX is sent and the ladder aborts with no retry\n",
@@ -8280,6 +8414,78 @@ static int gsp_phys_rmcall_gr_ctx_promote(void __iomem *bar0)
*/
pr_info("vgsp-boot: rm-call: gr_promote_param=17: no client PROMOTE_CTX is sent; the context buffers are server-managed (gsp_phys_srv_ctxbuf registry profile) and the server commits the engine context itself; continuing to BIND/SCHEDULE/submit\n");
boot_stage.rmcall_gr_promote_step = 0;
+ } else if (mode == GSP_PHYS_RMCALL_GR_PROMOTE_GUEST_SET) {
+ /*
+ * Mode 19: the stock guest's exact multi-entry promote. One
+ * control, entryCount=9; MAIN carries both PA and VA (2 MB),
+ * the global context buffers are VA-only, PATCH/FECS/priv maps
+ * are PA-initialized. VAs other than MAIN's are booking-only
+ * (the engine does not touch those buffers for the semaphore
+ * push); they are placed above the ctx window so they cannot
+ * collide with the PDB-mapped regions.
+ */
+ u32 attr = GSP_PHYS_RMCALL_GR_CTX_PHYS_ATTR;
+ u64 bk = GSP_PHYS_RMCALL_VA_GR_CTX + 0x400000ULL;
+ u32 aux_rpc = 0, aux_status = 0;
+
+ ret = gsp_phys_rmcall_gr_guest_set_prepare(bar0);
+ if (ret) {
+ kvfree(p);
+ return ret;
+ }
+
+ p->entry_count = cpu_to_le32(9);
+ gsp_phys_rmcall_promote_entry_fill(p, 0,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_MAIN,
+ boot_stage.rmcall_ctx_pa, boot_stage.rmcall_ctx_va,
+ boot_stage.rmcall_ctx_size, attr, 1, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 1,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PATCH,
+ boot_stage.rmcall_patch_pa, bk + 0x1000,
+ boot_stage.rmcall_patch_size, attr, 1, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 2,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_BUNDLE_CB,
+ 0, bk + 0x2000, 0, 0, 0, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 3,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PAGEPOOL,
+ 0, bk + 0x3000, 0, 0, 0, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 4,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_ATTRIBUTE_CB,
+ 0, bk + 0x4000, 0, 0, 0, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 5,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_RTV_CB_GLOBAL,
+ 0, bk + 0x5000, 0, 0, 0, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 6,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_FECS_EVENT,
+ boot_stage.rmcall_fecs_pa, bk + 0x6000, 0x1000, attr,
+ 1, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, 7,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_PRIV_MAP,
+ boot_stage.rmcall_priv_pa, 0, 0x1000, attr, 1, 1);
+ gsp_phys_rmcall_promote_entry_fill(p, 8,
+ GSP_PHYS_RMCALL_GR_CTX_BUFFER_ID_UNR_PRIV_MAP,
+ boot_stage.rmcall_unrestricted_pa, bk + 0x7000,
+ 0x1000, attr, 1, 0);
+
+ ret = gsp_phys_rmcall_control(bar0,
+ boot_stage.rmcall_subdevice,
+ GSP_PHYS_RMCALL_CTRL_GPU_PROMOTE_CTX,
+ p, sizeof(*p), 24, NULL, 0,
+ &aux_rpc, &aux_status,
+ "GPU_PROMOTE_CTX (guest set, 9 entries)");
+ pr_info("vgsp-boot: rm-call: PROMOTE_CTX guest-set: entryCount=9 status=0x%x rpc_result=0x%x (hObject=0x%x hChanClient=0x%x engineType=0x%x MAIN pa=0x%llx va=0x%llx size=0x%x; gr_promote_param=%u)\n",
+ aux_status, aux_rpc, boot_stage.rmcall_channel,
+ boot_stage.rmcall_client, GSP_PHYS_RMCALL_ENGINE_GR0,
+ boot_stage.rmcall_ctx_pa, boot_stage.rmcall_ctx_va,
+ boot_stage.rmcall_ctx_size, mode);
+ if (ret) {
+ pr_err("vgsp-boot: rm-call: PROMOTE_CTX guest-set failed (status=0x%x rpc_result=0x%x, %s, ret=%d); the ladder aborts before BIND with no retry\n",
+ aux_status, aux_rpc,
+ gsp_phys_rmcall_status_str(aux_status), ret);
+ kvfree(p);
+ return ret;
+ }
+ boot_stage.rmcall_gr_promote_step = 1;
} else {
pr_err("vgsp-boot: rm-call: unknown gr_promote_param=%u reached the promote step; no PROMOTE_CTX is sent and the ladder aborts with no retry\n",
mode);
@@ -10545,6 +10751,9 @@ static void gsp_phys_rmcall_fini(struct pci_dev *pdev)
gsp_phys_chunk_free(pdev, &boot_stage.rmcall_ctx_pt[i]);
gsp_phys_chunk_free(pdev, &boot_stage.rmcall_patch);
gsp_phys_chunk_free(pdev, &boot_stage.rmcall_pm);
+ gsp_phys_chunk_free(pdev, &boot_stage.rmcall_fecs);
+ gsp_phys_chunk_free(pdev, &boot_stage.rmcall_priv);
+ gsp_phys_chunk_free(pdev, &boot_stage.rmcall_unrestricted);
gsp_phys_rmcall_reset();
}
diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c
index c352056..1fa1670 100644
--- a/kernel/gsp_phys_gpu.c
+++ b/kernel/gsp_phys_gpu.c
@@ -424,8 +424,8 @@ static int gsp_phys_rmcall_gr_promote_set(const char *val,
return ret;
if (gsp_phys_rmcall_gr_promote_param < 0 ||
- gsp_phys_rmcall_gr_promote_param > 18) {
- pr_warn("vgsp: gsp_phys_rmcall_gr_promote=%d is outside 0..18; clamping to 0 (no PROMOTE_CTX)\n",
+ gsp_phys_rmcall_gr_promote_param > 19) {
+ pr_warn("vgsp: gsp_phys_rmcall_gr_promote=%d is outside 0..19; clamping to 0 (no PROMOTE_CTX)\n",
gsp_phys_rmcall_gr_promote_param);
gsp_phys_rmcall_gr_promote_param = 0;
}
@@ -444,7 +444,7 @@ module_param_cb(gsp_phys_rmcall_gr_promote,
&gsp_phys_rmcall_gr_promote_ops,
&gsp_phys_rmcall_gr_promote_param, 0644);
MODULE_PARM_DESC(gsp_phys_rmcall_gr_promote,
- "GR context-promote bisection (default 0: never send PROMOTE_CTX; 1: single entry init+nonmapped=0 size=raw+subctx, the p6h1b shape; 2: two-phase PA-only nonmapped then VA-only with the same fields; 3: single entry size=raw; 4: two-phase with a zero-field VA-only step; 5: batched entryCount=2 PA-only init + zero-field VA-only; 6: no-entry hVirtMemory, NV01_MEMORY_VIRTUAL (class 0x70) on the VAS at the context window + PROMOTE_CTX entryCount=0; 7: no-entry bare virtAddress+size, entryCount=0; 8: single MAIN entry like 1 on the KMD-managed channel (V6 SKIP_CTXBUFFER_ALLOC), server creates the virtual context memdesc itself; 9: same V6 channel with the stock client's zero-field VA-only MAIN entry, bInitialize=0; 10: same V6 channel, GPU_INITIALIZE_CTX (0x2080012d) installs the physical context memdesc then PROMOTE_CTX entryCount=0 bare virtAddress+size; 11: mode 10 with a server FBMEM context buffer, VIDMEM physAttr 0x4; 12: V5 UVM/vGPU-faithful PA-only MAIN entry (bInitialize=1 bNonmapped=1, no VA) and continue to BIND/SCHEDULE/submit; 13: mode 12 plus a PATCH PA-only promote first, sized from GET_ENGINE_CONTEXT_PROPERTIES engineId 0x10; 14: KMD channel (V6) + PATCH PA-only then the single-entry MAIN init+VA (the flow that books the context VA and commits ENG_CTX_VALID); 15: mode 14 with a server FBMEM MAIN context buffer and VIDMEM physAttr 0x4; 16: mode 14 plus a PM ctxsw PA-only promote first; 17: no client PROMOTE_CTX, paired with gsp_phys_srv_ctxbuf=1 so the GSP-RM server-manages and commits the context; 18: mode 8 shape plus a client FERMI_CONTEXT_SHARE_A on the TSG (hContextShare) whose construct allocates the GraphicsSubcontext); values outside 0..18 clamp to 0 with a warning");
+ "GR context-promote bisection (default 0: never send PROMOTE_CTX; 1: single entry init+nonmapped=0 size=raw+subctx, the p6h1b shape; 2: two-phase PA-only nonmapped then VA-only with the same fields; 3: single entry size=raw; 4: two-phase with a zero-field VA-only step; 5: batched entryCount=2 PA-only init + zero-field VA-only; 6: no-entry hVirtMemory, NV01_MEMORY_VIRTUAL (class 0x70) on the VAS at the context window + PROMOTE_CTX entryCount=0; 7: no-entry bare virtAddress+size, entryCount=0; 8: single MAIN entry like 1 on the KMD-managed channel (V6 SKIP_CTXBUFFER_ALLOC), server creates the virtual context memdesc itself; 9: same V6 channel with the stock client's zero-field VA-only MAIN entry, bInitialize=0; 10: same V6 channel, GPU_INITIALIZE_CTX (0x2080012d) installs the physical context memdesc then PROMOTE_CTX entryCount=0 bare virtAddress+size; 11: mode 10 with a server FBMEM context buffer, VIDMEM physAttr 0x4; 12: V5 UVM/vGPU-faithful PA-only MAIN entry (bInitialize=1 bNonmapped=1, no VA) and continue to BIND/SCHEDULE/submit; 13: mode 12 plus a PATCH PA-only promote first, sized from GET_ENGINE_CONTEXT_PROPERTIES engineId 0x10; 14: KMD channel (V6) + PATCH PA-only then the single-entry MAIN init+VA (the flow that books the context VA and commits ENG_CTX_VALID); 15: mode 14 with a server FBMEM MAIN context buffer and VIDMEM physAttr 0x4; 16: mode 14 plus a PM ctxsw PA-only promote first; 17: no client PROMOTE_CTX, paired with gsp_phys_srv_ctxbuf=1 so the GSP-RM server-manages and commits the context; 18: mode 8 shape plus a client FERMI_CONTEXT_SHARE_A on the TSG (hContextShare) whose construct allocates the GraphicsSubcontext; 19: the stock guest's exact 9-entry promote set (MAIN 2 MB PA+VA, PATCH, global buffers, FECS event, private access maps) on V5); values outside 0..19 clamp to 0 with a warning");
int gsp_phys_rmcall_gr_promote_value(void)
{
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index ad0b4f5..1f636de 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -3027,6 +3027,67 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
&out_status, reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
break;
+ case 0x2080012b: { /* NV2080_CTRL_CMD_GPU_PROMOTE_CTX */
+ /*
+ * The stock guest promotes its GR virtual contexts through this
+ * control during channel setup. The emulation answers SUCCESS
+ * (the physical backend owns execution), but the guest's entry
+ * shapes are the ground truth for what a real client sends:
+ * capture them (all fields, all entries) so the physical ladder
+ * can be driven with the exact same promote shape.
+ */
+ const u8 *pp = req_data + GSP_RM_CTRL_HDR_SIZE;
+ u32 engine_type = 0, h_cli = 0, ch_id = 0, h_chan_cli = 0;
+ u32 h_obj = 0, h_virt = 0, entry_count = 0, i, ecap;
+
+ if (req.paramsSize >= 48) {
+ memcpy(&engine_type, pp + 0, 4);
+ memcpy(&h_cli, pp + 4, 4);
+ memcpy(&ch_id, pp + 8, 4);
+ memcpy(&h_chan_cli, pp + 12, 4);
+ memcpy(&h_obj, pp + 16, 4);
+ memcpy(&h_virt, pp + 20, 4);
+ {
+ u64 va = 0, sz = 0;
+
+ memcpy(&va, pp + 24, 8);
+ memcpy(&sz, pp + 32, 8);
+ memcpy(&entry_count, pp + 40, 4);
+ ecap = min_t(u32, entry_count, 16u);
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: PROMOTE_CTX engine=0x%x hClient=0x%x chId=%u hChanClient=0x%x hObject=0x%x hVirtMemory=0x%x va=0x%llx size=0x%llx entryCount=%u",
+ engine_type, h_cli, ch_id,
+ h_chan_cli, h_obj, h_virt, va,
+ sz, entry_count);
+ }
+ for (i = 0; i < ecap &&
+ 48u + (i + 1u) * 32u <= req.paramsSize; i++) {
+ const u8 *e = pp + 48 + i * 32;
+ u64 pa = 0, eva = 0, esz = 0;
+ u32 attr = 0;
+ u16 bid = 0;
+
+ memcpy(&pa, e + 0, 8);
+ memcpy(&eva, e + 8, 8);
+ memcpy(&esz, e + 16, 8);
+ memcpy(&attr, e + 24, 4);
+ memcpy(&bid, e + 28, 2);
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: promote[%u] bufferId=%u bInitialize=%u bNonmapped=%u physAttr=0x%x pa=0x%llx va=0x%llx size=0x%llx",
+ i, bid, e[30], e[31], attr, pa,
+ eva, esz);
+ }
+ }
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ if (req.paramsSize &&
+ req.paramsSize + GSP_RM_CTRL_HDR_SIZE <=
+ GSPRM_MAX_SINGLE_ELEM_DATA)
+ memcpy(reply + GSP_RM_CTRL_HDR_SIZE, pp,
+ req.paramsSize);
+ break;
+ }
+
default:
/* Unknown control command - return success with zeroed params.
* Do NOT echo the request: pure queries pass uninitialized