summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_booter.c286
-rw-r--r--kernel/gsp_phys_gpu.c19
-rw-r--r--kernel/gsp_phys_gpu.h9
3 files changed, 276 insertions, 38 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index d5585e4..fae657c 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -318,6 +318,7 @@ bool gsp_phys_boot_requested(void)
#define GSP_PHYS_RMCALL_CTRL_FIFO_GET_ENGINE_CONTEXT_PROPERTIES 0x801707u
#define GSP_PHYS_RMCALL_CTRL_FIFO_GET_INFO 0x20801109u
#define GSP_PHYS_RMCALL_CTRL_GPU_PROMOTE_CTX 0x2080012bu
+#define GSP_PHYS_RMCALL_CTRL_GPU_EVICT_CTX 0x2080012cu
/*
* NV2080_CTRL_CMD_GPU_INITIALIZE_CTX (0x2080012d, ctrl2080gpu.h:1047-1063):
* the old-interface physical-context init. The server routes it to
@@ -365,6 +366,10 @@ bool gsp_phys_boot_requested(void)
#define GSP_PHYS_RMCALL_GR_CTX_PT_MAX \
(GSP_PHYS_RMCALL_GR_CTX_MAX_SIZE / (2u * 1024u * 1024u))
#define GSP_PHYS_RMCALL_VA_GR_CTX 0x02200000ULL
+/* PATCH is mapped in the same 2 MB window, 1 MB above MAIN (MAIN max is
+ * 32 MB but the live server MAIN is 0x133000; PATCH lives at +0x140000 to
+ * stay clear of MAIN's tail). */
+#define GSP_PHYS_RMCALL_VA_GR_PATCH (GSP_PHYS_RMCALL_VA_GR_CTX + 0x140000ULL)
/* GR context-promote bisection modes (gsp_phys_rmcall_gr_promote). */
#define GSP_PHYS_RMCALL_GR_PROMOTE_OFF 0u
@@ -1329,6 +1334,20 @@ static struct gsp_phys_boot_stage {
u64 rmcall_srv_main_size;
u32 rmcall_srv_main_aperture;
/*
+ * Server-VA commit variant: map the server-managed MAIN at the ladder
+ * GR ctx VA (FBMEM PTEs through the server's own MMU walker) and send
+ * one combined PA+VA MAIN promote entry (bInitialize=1, bNonmapped=0,
+ * the shape kgrobjPromoteContext produces for a mapped client buffer).
+ * The engine context pointer needs a VA; a PA-only entry books none.
+ */
+ bool rmcall_srv_ctxva;
+ /* Server-reported PATCH buffer (bufferType 2), mapped at VA_GR_PATCH
+ * on the server-VA path and promoted VA-only with MAIN. */
+ bool rmcall_srv_patch_valid;
+ u64 rmcall_srv_patch_pa;
+ u64 rmcall_srv_patch_size;
+ u32 rmcall_srv_patch_aperture;
+ /*
* Mode 13: the client PATCH context buffer (PA-only promote). Prepared
* lazily in the promote step from the engine-context geometry query
* (engineId 0x10); freed by gsp_phys_rmcall_fini().
@@ -1974,6 +1993,11 @@ static void gsp_phys_rmcall_reset(void)
boot_stage.rmcall_srv_main_pa = 0;
boot_stage.rmcall_srv_main_size = 0;
boot_stage.rmcall_srv_main_aperture = 0;
+ boot_stage.rmcall_srv_ctxva = false;
+ boot_stage.rmcall_srv_patch_valid = false;
+ boot_stage.rmcall_srv_patch_pa = 0;
+ boot_stage.rmcall_srv_patch_size = 0;
+ boot_stage.rmcall_srv_patch_aperture = 0;
boot_stage.rmcall_patch_prepared = false;
boot_stage.rmcall_patch_pa = 0;
boot_stage.rmcall_patch_size = 0;
@@ -6007,6 +6031,21 @@ struct gsp_phys_rmcall_promote_params {
};
/*
+ * NV2080_CTRL_GPU_EVICT_CTX_PARAMS (20 bytes,
+ * ctrl/ctrl2080/ctrl2080gpu.h:1000-1006): engineType/hClient/ChID/
+ * hChanClient/hObject. Evicts the channel's virtual engine context; on the
+ * server-managed path it is the step that releases the server's pre-booked
+ * engine-ctx vaList entry (managed) so a client VA promote can add ours.
+ */
+struct gsp_phys_rmcall_evict_ctx_params {
+ __le32 engine_type;
+ __le32 h_client;
+ __le32 ch_id;
+ __le32 h_chan_client;
+ __le32 h_object;
+};
+
+/*
* NV2080_CTRL_GPU_INITIALIZE_CTX_PARAMS (56 bytes, ctrl2080gpu.h:1051-1063):
* engineType/hClient/ChID/hChanClient/hObject/hVirtMemory, physAddress,
* physAttr, hDmaHandle, index, size. Mode 10 sends it before the promote to
@@ -6437,7 +6476,8 @@ static int gsp_phys_rmcall_map_pt_window(void __iomem *bar0)
* a client PDE install there risks clobbering the server's own
* context-window page table.
*/
- for (i = 0; !gsp_phys_srv_ctxbuf_value() &&
+ for (i = 0; (!gsp_phys_srv_ctxbuf_value() ||
+ boot_stage.rmcall_srv_ctxva) &&
i < boot_stage.rmcall_ctx_pt_pages; i++) {
u32 ctx_index = (u32)((GSP_PHYS_RMCALL_VA_GR_CTX >> 21) + i);
u64 ctx_pt_pa = (u64)gsp_phys_chunk_page_dma(
@@ -7502,7 +7542,8 @@ static void gsp_phys_rmcall_pdb_build(void)
* client PT/VA mapping of our private buffer must not overwrite the
* server's own PDE for its ctx window.
*/
- if (boot_stage.rmcall_ctx_prepared && !gsp_phys_srv_ctxbuf_value()) {
+ if (boot_stage.rmcall_ctx_prepared &&
+ (!gsp_phys_srv_ctxbuf_value() || boot_stage.rmcall_srv_ctxva)) {
for (i = 0; i < boot_stage.rmcall_ctx_pt_pages; i++) {
u32 pd0_idx = (u32)(((GSP_PHYS_RMCALL_VA_GR_CTX >> 21) +
i) & 0xffu);
@@ -8852,7 +8893,7 @@ static void gsp_phys_rmcall_gr_ctx_info(void __iomem *bar0)
}
count = le32_to_cpu(p->buffer_count);
- shown = min_t(u32, count, 4u);
+ shown = min_t(u32, count, 16u);
pr_info("vgsp-boot: rm-call: GR_GET_CTX_BUFFER_INFO: bufferCount=%u (showing %u)\n",
count, shown);
for (i = 0; i < count; i++) {
@@ -8883,6 +8924,21 @@ static void gsp_phys_rmcall_gr_ctx_info(void __iomem *bar0)
boot_stage.rmcall_srv_main_size,
boot_stage.rmcall_srv_main_aperture);
}
+ /* PATCH is bufferType 2; promoted VA-only with MAIN. */
+ if (le32_to_cpu(ci->buffer_type) == 2 &&
+ !boot_stage.rmcall_srv_patch_valid) {
+ boot_stage.rmcall_srv_patch_valid = true;
+ boot_stage.rmcall_srv_patch_pa =
+ le64_to_cpu(ci->phys_addr);
+ boot_stage.rmcall_srv_patch_size =
+ le64_to_cpu(ci->size);
+ boot_stage.rmcall_srv_patch_aperture =
+ le32_to_cpu(ci->aperture);
+ pr_info("vgsp-boot: rm-call: server PATCH ctx buffer: pa=0x%llx size=0x%llx aperture=%u\n",
+ boot_stage.rmcall_srv_patch_pa,
+ boot_stage.rmcall_srv_patch_size,
+ boot_stage.rmcall_srv_patch_aperture);
+ }
}
kvfree(p);
}
@@ -8910,11 +8966,86 @@ static int gsp_phys_rmcall_srv_ctx_commit(void __iomem *bar0)
if (boot_stage.rmcall_srv_main_valid) {
main_pa = boot_stage.rmcall_srv_main_pa;
main_size = boot_stage.rmcall_srv_main_size;
- pa_attr = (boot_stage.rmcall_srv_main_aperture == 0) ?
+ /*
+ * physAttr must match the reported aperture: FBMEM (2) needs the
+ * VIDMEM/GPU_CACHEABLE_NO attribute (0x4), SYSMEM/unknown use the
+ * NCOH_SYS attribute (0x6). The old aperture==0 test sent the
+ * sysmem attribute for the FBMEM server MAIN, which makes the RM
+ * materialize the wrong memdesc for the engine context.
+ */
+ pa_attr = (boot_stage.rmcall_srv_main_aperture == 2) ?
GSP_PHYS_RMCALL_GR_CTX_PHYS_ATTR_FB :
GSP_PHYS_RMCALL_GR_CTX_PHYS_ATTR;
- pr_info("vgsp-boot: rm-call: committing the SERVER-reported MAIN ctx buffer pa=0x%llx size=0x%llx\n",
- main_pa, main_size);
+ pr_info("vgsp-boot: rm-call: committing the SERVER-reported MAIN ctx buffer pa=0x%llx size=0x%llx aperture=%u physAttr=0x%x\n",
+ main_pa, main_size, boot_stage.rmcall_srv_main_aperture,
+ pa_attr);
+ /*
+ * The engine context pointer is a GPU VA; a PA-only promote
+ * books no VA (no context pointer appears in the instance/RAMFC,
+ * and GPCCS faults during ctxsw). Map the server MAIN at the
+ * ladder GR ctx VA with VIDMEM PTEs so the combined PA+VA promote
+ * below can book it, exactly like a mapped client buffer in the
+ * stock kgrobjPromoteContext shape.
+ */
+ if (boot_stage.rmcall_srv_main_aperture == 2 &&
+ main_size <= GSP_PHYS_RMCALL_GR_CTX_MAX_SIZE) {
+ u32 pages = (u32)DIV_ROUND_UP(main_size,
+ GSP_PHYS_PAGE_SIZE);
+ u32 pt_pages = DIV_ROUND_UP(pages,
+ GSP_PHYS_PAGE_SIZE / 8u);
+ u32 i;
+
+ for (i = 0; i < pt_pages; i++) {
+ if (boot_stage.rmcall_ctx_pt[i].pages)
+ continue;
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev,
+ &boot_stage.rmcall_ctx_pt[i], 1u);
+ if (ret) {
+ pr_err("vgsp-boot: rm-call: server MAIN PT page %u/%u allocation failed: %d; aborting\n",
+ i, pt_pages, ret);
+ return ret;
+ }
+ gsp_phys_chunk_zero(&boot_stage.rmcall_ctx_pt[i]);
+ }
+ boot_stage.rmcall_ctx_fbmem = true;
+ boot_stage.rmcall_ctx_pa = main_pa;
+ boot_stage.rmcall_ctx_size = (u32)main_size;
+ boot_stage.rmcall_ctx_pages = pages;
+ boot_stage.rmcall_ctx_pt_pages = pt_pages;
+ boot_stage.rmcall_ctx_va = GSP_PHYS_RMCALL_VA_GR_CTX;
+ boot_stage.rmcall_srv_ctxva = true;
+ gsp_phys_rmcall_pdb_build();
+ /*
+ * Map the server PATCH (FBMEM) into the same PT page at
+ * VA_GR_PATCH. pdb_build only writes MAIN slots, and its
+ * later refill leaves these entries untouched.
+ */
+ if (boot_stage.rmcall_srv_patch_valid &&
+ boot_stage.rmcall_srv_patch_aperture == 2) {
+ u32 pslot = (u32)((GSP_PHYS_RMCALL_VA_GR_PATCH >>
+ 12) & 0x1ffu);
+ u32 ppages = (u32)DIV_ROUND_UP(
+ boot_stage.rmcall_srv_patch_size,
+ GSP_PHYS_PAGE_SIZE);
+ u32 k;
+
+ for (k = 0; k < ppages; k++)
+ put_unaligned_le64(
+ gsp_phys_rmcall_pte_vid(
+ boot_stage.rmcall_srv_patch_pa +
+ (u64)k * GSP_PHYS_PAGE_SIZE),
+ (u8 *)gsp_phys_chunk_page_va(
+ &boot_stage.rmcall_ctx_pt[
+ (pslot + k) /
+ (GSP_PHYS_PAGE_SIZE / 8u)], 0) +
+ (size_t)((pslot + k) %
+ (GSP_PHYS_PAGE_SIZE / 8u)) * 8);
+ pr_info("vgsp-boot: rm-call: server PATCH mapped at VA 0x%llx (%u pages, FBMEM PTEs)\n",
+ GSP_PHYS_RMCALL_VA_GR_PATCH, ppages);
+ }
+ pr_info("vgsp-boot: rm-call: server MAIN mapped at VA 0x%llx (FBMEM PTEs, %u pages, %u PT page(s)); the promote books this VA\n",
+ boot_stage.rmcall_ctx_va, pages, pt_pages);
+ }
} else {
/*
* GR_GET_CTX_BUFFER_INFO still refused (0x1f even with a 64
@@ -8932,30 +9063,43 @@ static int gsp_phys_rmcall_srv_ctx_commit(void __iomem *bar0)
main_pa, main_size, GSP_PHYS_RMCALL_VA_GR_CTX);
}
- ic = kvzalloc(sizeof(*ic), GFP_KERNEL);
- if (!ic)
- return -ENOMEM;
- ic->engine_type = cpu_to_le32(GSP_PHYS_RMCALL_ENGINE_GR0);
- ic->h_client = cpu_to_le32(0);
- ic->ch_id = cpu_to_le32(0);
- ic->h_chan_client = cpu_to_le32(boot_stage.rmcall_client);
- ic->h_object = cpu_to_le32(boot_stage.rmcall_channel);
- ic->h_virt_memory = cpu_to_le32(0);
- ic->phys_address = cpu_to_le64(main_pa);
- ic->phys_attr = cpu_to_le32(pa_attr);
- ic->h_dma_handle = cpu_to_le32(0);
- ic->index = cpu_to_le32(0);
- ic->size = cpu_to_le64(main_size);
- ret = gsp_phys_rmcall_control(bar0, boot_stage.rmcall_subdevice,
- GSP_PHYS_RMCALL_CTRL_GPU_INITIALIZE_CTX,
- ic, sizeof(*ic), 24, NULL, 0,
- &rpc_result, &status,
- "GPU_INITIALIZE_CTX (server MAIN)");
- kvfree(ic);
- pr_info("vgsp-boot: rm-call: MAIN INITIALIZE_CTX: pa=0x%llx size=0x%llx physAttr=0x%x status=0x%x rpc_result=0x%x\n",
- main_pa, main_size, pa_attr, status, rpc_result);
- if (ret)
- return ret;
+ /*
+ * Server-VA path: the server already allocated and initialized MAIN
+ * during the GR object construct, so the old-interface
+ * GPU_INITIALIZE_CTX must NOT be sent (its PA entry re-initializes an
+ * already-initialized context and the promote then hits the 0x40
+ * already-initialized gate). Send only the UVM-shape VA-only promote
+ * entries (bInitialize=0, bNonmapped=0, gpuVirtAddr only), which take
+ * the per-buffer VA commit branch and program the engine context
+ * pointer. Otherwise: INITIALIZE_CTX + PATCH/MAIN PA-only.
+ */
+ if (!boot_stage.rmcall_srv_ctxva) {
+ ic = kvzalloc(sizeof(*ic), GFP_KERNEL);
+ if (!ic)
+ return -ENOMEM;
+ ic->engine_type = cpu_to_le32(GSP_PHYS_RMCALL_ENGINE_GR0);
+ ic->h_client = cpu_to_le32(0);
+ ic->ch_id = cpu_to_le32(0);
+ ic->h_chan_client = cpu_to_le32(boot_stage.rmcall_client);
+ ic->h_object = cpu_to_le32(boot_stage.rmcall_channel);
+ ic->h_virt_memory = cpu_to_le32(0);
+ ic->phys_address = cpu_to_le64(main_pa);
+ ic->phys_attr = cpu_to_le32(pa_attr);
+ ic->h_dma_handle = cpu_to_le32(0);
+ ic->index = cpu_to_le32(0);
+ ic->size = cpu_to_le64(main_size);
+ ret = gsp_phys_rmcall_control(bar0,
+ boot_stage.rmcall_subdevice,
+ GSP_PHYS_RMCALL_CTRL_GPU_INITIALIZE_CTX,
+ ic, sizeof(*ic), 24, NULL, 0,
+ &rpc_result, &status,
+ "GPU_INITIALIZE_CTX (server MAIN)");
+ kvfree(ic);
+ pr_info("vgsp-boot: rm-call: MAIN INITIALIZE_CTX: pa=0x%llx size=0x%llx physAttr=0x%x status=0x%x rpc_result=0x%x\n",
+ main_pa, main_size, pa_attr, status, rpc_result);
+ if (ret)
+ return ret;
+ }
p = kvzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
@@ -8969,11 +9113,50 @@ static int gsp_phys_rmcall_srv_ctx_commit(void __iomem *bar0)
p->virt_address = cpu_to_le64(0);
p->size = cpu_to_le64(0);
/*
- * The real client's promote set always contains PATCH; promote it
- * PA-only before MAIN (mode 13 order). Preparation is lazy and
- * bounded; a failure just drops the PATCH entry.
+ * Server-VA path: first evict the channel's engine context. On
+ * gsp_phys_srv_ctxbuf the server pre-booked MAIN into the engine-ctx
+ * vaList as managed (kgrctxMapCtxBuffers); the per-entry VA branch
+ * calls vaListSetManaged(FALSE) and returns NV_ERR_INVALID_STATE
+ * (0x40) while that booking exists. EVICT_CTX releases it so the
+ * following VA-only promote can add and commit our VA.
+ */
+ if (boot_stage.rmcall_srv_ctxva) {
+ struct gsp_phys_rmcall_evict_ctx_params ev;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.engine_type = cpu_to_le32(GSP_PHYS_RMCALL_ENGINE_GR0);
+ ev.h_client = cpu_to_le32(0);
+ ev.ch_id = cpu_to_le32(0);
+ ev.h_chan_client = cpu_to_le32(boot_stage.rmcall_client);
+ ev.h_object = cpu_to_le32(boot_stage.rmcall_channel);
+ rpc_result = 0;
+ status = 0;
+ ret = gsp_phys_rmcall_control(bar0, boot_stage.rmcall_subdevice,
+ GSP_PHYS_RMCALL_CTRL_GPU_EVICT_CTX,
+ &ev, sizeof(ev), 24, NULL, 0,
+ &rpc_result, &status, "GPU_EVICT_CTX");
+ pr_info("vgsp-boot: rm-call: EVICT_CTX: status=0x%x rpc_result=0x%x ret=%d\n",
+ status, rpc_result, ret);
+ if (ret)
+ return ret;
+ }
+ /*
+ * Server-VA commit shape (UVM kgrctxPreparePromoteCtxBuffer): VA-only
+ * entries, bInitialize=0, bNonmapped=0, all PA/size/attr zero; PATCH
+ * first, then MAIN. Otherwise: PATCH PA-only before MAIN PA-only
+ * (mode 13 order).
*/
- if (gsp_phys_rmcall_gr_patch_prepare(bar0) == 0 &&
+ if (boot_stage.rmcall_srv_ctxva) {
+ u32 n = 0;
+
+ if (boot_stage.rmcall_srv_patch_valid)
+ gsp_phys_rmcall_promote_entry_fill(p, n++, 2,
+ 0, GSP_PHYS_RMCALL_VA_GR_PATCH,
+ 0, 0, 0, 0);
+ gsp_phys_rmcall_promote_entry_fill(p, n++, 0,
+ 0, boot_stage.rmcall_ctx_va, 0, 0, 0, 0);
+ p->entry_count = cpu_to_le32(n);
+ } else if (gsp_phys_rmcall_gr_patch_prepare(bar0) == 0 &&
boot_stage.rmcall_patch_size != 0) {
gsp_phys_rmcall_promote_entry_fill(p, 0, 2,
boot_stage.rmcall_patch_pa, 0,
@@ -8993,9 +9176,14 @@ static int gsp_phys_rmcall_srv_ctx_commit(void __iomem *bar0)
GSP_PHYS_RMCALL_CTRL_GPU_PROMOTE_CTX,
p, sizeof(*p), 24, NULL, 0,
&rpc_result, &status,
- "GPU_PROMOTE_CTX (server MAIN PA-only)");
- pr_info("vgsp-boot: rm-call: MAIN PROMOTE_CTX: entries=%u entry{MAIN pa=0x%llx size=0x%llx physAttr=0x%x bInitialize=1 bNonmapped=1} status=0x%x rpc_result=0x%x ret=%d\n",
- le32_to_cpu(p->entry_count), main_pa, main_size, pa_attr,
+ boot_stage.rmcall_srv_ctxva ?
+ "GPU_PROMOTE_CTX (server VA-only)" :
+ "GPU_PROMOTE_CTX (server MAIN PA-only)");
+ pr_info("vgsp-boot: rm-call: MAIN PROMOTE_CTX: entries=%u%s status=0x%x rpc_result=0x%x ret=%d\n",
+ le32_to_cpu(p->entry_count),
+ boot_stage.rmcall_srv_ctxva ?
+ " entry{VA-only PATCH+MAIN bInitialize=0 bNonmapped=0}" :
+ " entry{PA-only MAIN bInitialize=1 bNonmapped=1}",
status, rpc_result, ret);
kvfree(p);
return ret;
@@ -9283,7 +9471,8 @@ static int gsp_phys_rmcall_stage3(void __iomem *bar0)
* tables, no server booking).
*/
if (gsp_phys_rmcall_vas_shared_value()) {
- if (gsp_phys_rmcall_engine_is_gr()) {
+ if (gsp_phys_rmcall_engine_is_gr() &&
+ gsp_phys_srv_ctxcommit_value()) {
ret = gsp_phys_rmcall_srv_ctx_commit(bar0);
if (ret) {
pr_err("vgsp-boot: rm-call: stage 3 aborted: shared-VAS server MAIN initialize+promote failed (%d); the GR context stays uninitialized and the ladder is not retried\n",
@@ -9297,6 +9486,25 @@ static int gsp_phys_rmcall_stage3(void __iomem *bar0)
ret);
return ret;
}
+ } else if (gsp_phys_rmcall_engine_is_gr() &&
+ gsp_phys_srv_ctxbuf_value() &&
+ gsp_phys_srv_ctxcommit_value()) {
+ /*
+ * External VAS + server-managed ctx buffers: the server
+ * allocated MAIN but did NOT map it into the (externally owned)
+ * VAS, so the engine-ctx vaList is empty and the UVM-shape
+ * VA-only promote can add our VA and reach the per-buffer
+ * commit (the shared path's vaListSetManaged(FALSE) gate does
+ * not apply here). The client-owned raw PDB maps the server
+ * MAIN at the ctx VA with FBMEM PTEs; no UPDATE_PDE_2 install
+ * is needed on an external root.
+ */
+ ret = gsp_phys_rmcall_srv_ctx_commit(bar0);
+ if (ret) {
+ pr_err("vgsp-boot: rm-call: stage 3 aborted: external-VAS server MAIN VA promote failed (%d); the GR context stays uninitialized and the ladder is not retried\n",
+ ret);
+ return ret;
+ }
}
if (gsp_phys_rmcall_engine_is_gr())
@@ -12741,6 +12949,8 @@ static_assert(offsetof(struct gsp_phys_rmcall_promote_params, entry_count) == 40
"NV2080_CTRL_GPU_PROMOTE_CTX_PARAMS.entryCount must be at offset 40");
static_assert(offsetof(struct gsp_phys_rmcall_promote_params, entry) == 48,
"NV2080_CTRL_GPU_PROMOTE_CTX_PARAMS.promoteEntry must be at offset 48 (8-byte aligned)");
+static_assert(sizeof(struct gsp_phys_rmcall_evict_ctx_params) == 20,
+ "NV2080_CTRL_GPU_EVICT_CTX_PARAMS must be 20 bytes");
static_assert(sizeof(struct gsp_phys_rmcall_ctx_buffer_info) == 80,
"NV2080_CTRL_GR_CTX_BUFFER_INFO must be 80 bytes (NvBool = NvU8)");
static_assert(offsetof(struct gsp_phys_rmcall_ctx_buffer_info, uuid) == 60,
diff --git a/kernel/gsp_phys_gpu.c b/kernel/gsp_phys_gpu.c
index 91f7a77..94d6652 100644
--- a/kernel/gsp_phys_gpu.c
+++ b/kernel/gsp_phys_gpu.c
@@ -182,6 +182,25 @@ bool gsp_phys_srv_ctxbuf_value(void)
EXPORT_SYMBOL_GPL(gsp_phys_srv_ctxbuf_value);
/*
+ * Server-managed GR context client commit (default 1). On the
+ * gsp_phys_srv_ctxbuf path the ladder sends GPU_INITIALIZE_CTX + a PA-only
+ * GPU_PROMOTE_CTX for the server-reported MAIN context buffer. Set 0 to test
+ * the pure server-managed shape: the GSP-RM allocated/mapped/committed the
+ * context itself and no client control is sent.
+ */
+static bool gsp_phys_srv_ctxcommit_param = true;
+module_param_named(gsp_phys_rmcall_srv_ctxcommit, gsp_phys_srv_ctxcommit_param,
+ bool, 0644);
+MODULE_PARM_DESC(gsp_phys_rmcall_srv_ctxcommit,
+ "On the server-managed GR ctx path send GPU_INITIALIZE_CTX + PA-only GPU_PROMOTE_CTX for the server MAIN (default 1); 0 = pure server-managed, no client promote");
+
+bool gsp_phys_srv_ctxcommit_value(void)
+{
+ return gsp_phys_srv_ctxcommit_param;
+}
+EXPORT_SYMBOL_GPL(gsp_phys_srv_ctxcommit_value);
+
+/*
* P5 wave-1 shadow work forwarding (default 0). When set, the sema service
* requests one real CE copy on the debugfs ladder's channel for each decoded
* guest CE push, using only the ladder's module-owned buffers; the software
diff --git a/kernel/gsp_phys_gpu.h b/kernel/gsp_phys_gpu.h
index 74e3103..a270df8 100644
--- a/kernel/gsp_phys_gpu.h
+++ b/kernel/gsp_phys_gpu.h
@@ -81,6 +81,15 @@ bool gsp_phys_ctrl_forward_enabled(void);
bool gsp_phys_srv_ctxbuf_value(void);
/*
+ * True when the gsp_phys_rmcall_srv_ctxcommit parameter is set (default 1).
+ * On the server-managed ctx-buffer path (gsp_phys_srv_ctxbuf=1) the ladder
+ * sends GPU_INITIALIZE_CTX + a PA-only GPU_PROMOTE_CTX for the server-reported
+ * MAIN buffer. Set 0 to run the pure server-managed shape (no client promote
+ * at all) for bisection.
+ */
+bool gsp_phys_srv_ctxcommit_value(void);
+
+/*
* True when the gsp_phys_work_forward module parameter is set (default 0: the
* guest's CE pushes stay software-emulated exactly as before). When set, the
* sema service shadows each decoded CE push as one copy on the debugfs ladder's