summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_rm_emulation.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-12 01:29:13 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-12 01:29:13 -0500
commitfa985e759707ce011bd238bbbb73bc422fccb90f (patch)
tree70c2b7956e3e5efaf001a02da173fc6df75ddfd6 /kernel/gsp_rm_emulation.c
parentfc01d736909bfaa411643eddbe1593a78c83371a (diff)
downloadopen-iov-fa985e759707ce011bd238bbbb73bc422fccb90f.tar.gz
open-iov-fa985e759707ce011bd238bbbb73bc422fccb90f.zip
vgsp: resolve GSP-client USERD from hUserdMemory and skip foreign USERD pages
GSP-client kernel channels carry their USERD as hUserdMemory[subdev] (NV_CHANNEL_ALLOC_PARAMS @32) plus userdOffset[subdev] (@64), leaving the userdMem memdesc (@168) zero. The service therefore adopted another live channel's USERD page (c23: h=0x1000013 took the RC watchdog h=0x31415900's block at fb 0x8f0000 every pass) and the Xorg PB progress wait never advanced. Remember client memory-object ALLOCs (class 0x3e sysmem, 0x40 vidmem; 0x70/0x50a0 are virtual ranges and are refused) in a bounded (hClient,hObject) table, alias them on DUP_OBJECT and retire them on FREE. At channel registration, if the wire userdMem base is zero, resolve hUserdMemory+userdOffset through that table and set userd_pa/userd_as only when the base is nonzero, page-aligned and in bounds; otherwise fail closed with a rate-limited reason. Also skip FB-sweep USERD candidates on a page already owned by another live channel. Validation pending a fresh V30 cycle: glxinfo -B exit 0, Xorg WAIT count 0, chan_dump h=0x1000013 userd!=0 ok=1 get advancing, sema-push have_rel=1, glxgears FPS unchanged, GZDoom -glversion 4.0 past V_Init. Evidence: handoff-meta-plan.md CONT.438; c23 X/GLX logs; ABI gsp_abi_check.c:1764, nvos.h:1631-1668, cl003e.h/cl0040.h.
Diffstat (limited to 'kernel/gsp_rm_emulation.c')
-rw-r--r--kernel/gsp_rm_emulation.c330
1 files changed, 327 insertions, 3 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index 308de9c..9ba17c8 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -3368,11 +3368,285 @@ static bool gsp_rm_is_channel_class(u32 class_id)
#define GSPRM_CHAN_USERD_MEM 168
#define GSPRM_CHAN_MEM_APERTURE 16 /* offset of addressSpace in the desc */
#define GSPRM_CHAN_NOTIFIER_MEM 248
+#define GSPRM_CHAN_HUSERD_MEMORY 32 /* NvHandle hUserdMemory[NV_MAX_SUBDEVICES] */
+#define GSPRM_CHAN_USERD_OFFSET 64 /* NvU64 userdOffset[NV_MAX_SUBDEVICES] */
-static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
- const u8 *params, u32 params_size)
+/*
+ * CONT.438: client memory-object ALLOCs - the objects hUserdMemory can name.
+ *
+ * NV_MEMORY_ALLOCATION_PARAMS (nvos.h:1631-1668; ABI asserted by
+ * gsp_phys_booter.c static_asserts): 128 bytes, u64 fields 8-byte aligned.
+ * attr @24 (u32) location bits 1:0 (NVOS32_ATTR_LOCATION_VIDMEM = 0,
+ * NVOS32_ATTR_LOCATION_PCI = 1)
+ * size @64 (u64) [IN/OUT]
+ * offset @80 (u64) [IN/OUT] - the server-returned GPU physical base for
+ * sysmem (class 0x3e NV01_MEMORY_SYSTEM) or the FB
+ * offset for vidmem (class 0x40 NV01_MEMORY_LOCAL_USER).
+ * Both classes share this layout (cl003e.h:33,
+ * cl0040.h:34; the allocation call is nv_gpu_ops.c:
+ * 4644).
+ *
+ * NV_MEMORY_VIRTUAL_ALLOCATION_PARAMS (cl0070.h:66-70) is only 24 bytes
+ * { offset @0, limit @8, hVASpace @16 } - a VA-range reservation, not a
+ * physical allocation, so it must never be turned into a PA. Class 0x50a0
+ * NV50_MEMORY_VIRTUAL is allocated with NV_MEMORY_ALLOCATION_PARAMS and also
+ * returns a VA in .offset (nv_gpu_ops.c:4727-4730); same rule.
+ */
+#define GSPRM_MEM_ALLOC_SIZE 64
+#define GSPRM_MEM_ALLOC_OFFSET 80
+#define GSPRM_MEM_VIRT_OFFSET 0
+#define GSPRM_MEM_VIRT_LIMIT 8
+
+static struct gsp_rm_mem_object *
+gsp_rm_mem_object_find(struct gsp_rm_emu *rm, u32 h_client, u32 h_object)
+{
+ u32 i;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *e = &rm->mem_objects[i];
+
+ if (e->valid && e->h_client == h_client &&
+ e->h_object == h_object)
+ return e;
+ }
+ return NULL;
+}
+
+/* Reuse a retired slot, append while the table has room, else evict. */
+static struct gsp_rm_mem_object *gsp_rm_mem_object_slot(struct gsp_rm_emu *rm)
+{
+ struct gsp_rm_mem_object *e;
+ u32 i, slot;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ if (!rm->mem_objects[i].valid)
+ return &rm->mem_objects[i];
+ }
+ if (rm->mem_objects_n < GSPRM_MAX_MEM_OBJECTS)
+ return &rm->mem_objects[rm->mem_objects_n++];
+
+ slot = rm->mem_objects_next++ % GSPRM_MAX_MEM_OBJECTS;
+ e = &rm->mem_objects[slot];
+ if (rm->mem_object_evictions < 8) {
+ rm->mem_object_evictions++;
+ pr_info("vgsp-rm: mem-object table full; evicting (hClient=0x%x hObject=0x%x class=0x%x) for a new allocation\n",
+ e->h_client, e->h_object, e->class_id);
+ }
+ return e;
+}
+
+static void gsp_rm_mem_object_note(struct gsp_rm_emu *rm, u32 h_client,
+ u32 h_parent, u32 h_object, u32 class_id,
+ const u8 *params, u32 params_size)
+{
+ struct gsp_rm_mem_object *e;
+ u64 base = 0, size = 0;
+ u32 address_space;
+
+ switch (class_id) {
+ case 0x3e: /* NV01_MEMORY_SYSTEM */
+ case 0x40: /* NV01_MEMORY_LOCAL_USER */
+ if (params_size < GSPRM_MEM_ALLOC_OFFSET + 8)
+ return;
+ memcpy(&base, params + GSPRM_MEM_ALLOC_OFFSET, 8);
+ if (params_size >= GSPRM_MEM_ALLOC_SIZE + 8)
+ memcpy(&size, params + GSPRM_MEM_ALLOC_SIZE, 8);
+ address_space = class_id == 0x3e ? GSPRM_MEM_AS_SYSMEM
+ : GSPRM_MEM_AS_VIDMEM;
+ break;
+ case 0x70: /* NV01_MEMORY_VIRTUAL (VA range) */
+ if (params_size < 16)
+ return;
+ memcpy(&base, params + GSPRM_MEM_VIRT_OFFSET, 8);
+ memcpy(&size, params + GSPRM_MEM_VIRT_LIMIT, 8);
+ address_space = GSPRM_MEM_AS_VIRTUAL;
+ break;
+ case 0x50a0: /* NV50_MEMORY_VIRTUAL (VA in .offset) */
+ if (params_size < GSPRM_MEM_ALLOC_OFFSET + 8)
+ return;
+ memcpy(&base, params + GSPRM_MEM_ALLOC_OFFSET, 8);
+ if (params_size >= GSPRM_MEM_ALLOC_SIZE + 8)
+ memcpy(&size, params + GSPRM_MEM_ALLOC_SIZE, 8);
+ address_space = GSPRM_MEM_AS_VIRTUAL;
+ break;
+ default:
+ return;
+ }
+
+ e = gsp_rm_mem_object_find(rm, h_client, h_object);
+ if (!e)
+ e = gsp_rm_mem_object_slot(rm);
+ e->h_object = h_object;
+ e->h_client = h_client;
+ e->h_parent = h_parent;
+ e->class_id = class_id;
+ e->base = base;
+ e->size = size;
+ e->address_space = (u8)address_space;
+ e->valid = true;
+
+ if ((class_id == 0x3e || class_id == 0x40) &&
+ rm->mem_object_logs < 64) {
+ rm->mem_object_logs++;
+ nv_vgpu_info(rm->vgpu,
+ "vgsp-rm: mem-object hClient=0x%x hParent=0x%x hObject=0x%x class=0x%x base=0x%llx size=0x%llx as=%u\n",
+ h_client, h_parent, h_object, class_id, base,
+ size, address_space);
+ }
+}
+
+static void gsp_rm_mem_object_retire(struct gsp_rm_emu *rm, u32 h_root,
+ u32 h_object)
+{
+ u32 i, retired = 0;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *e = &rm->mem_objects[i];
+
+ if (!e->valid)
+ continue;
+ /*
+ * A normal FREE names the object (hObjectOld) and the freeing
+ * client (hRoot); a root FREE names the client itself. Handles
+ * are recycled across sessions, so both must match.
+ */
+ if ((h_object && h_object != h_root &&
+ e->h_object == h_object &&
+ (!h_root || e->h_client == h_root)) ||
+ (h_object && h_object == h_root &&
+ e->h_client == h_root)) {
+ e->valid = false;
+ retired++;
+ }
+ }
+ if (retired && gsp_sema_dbg)
+ nv_vgpu_info(rm->vgpu,
+ "vgsp-rm: retired %u mem-object record(s) on FREE hObject=0x%x hRoot=0x%x\n",
+ retired, h_object, h_root);
+}
+
+static bool gsp_rm_mem_object_alias(struct gsp_rm_emu *rm, u32 h_dst_client,
+ u32 h_dst_object, u32 h_src_client,
+ u32 h_src_object)
+{
+ struct gsp_rm_mem_object *src, *dst;
+
+ src = gsp_rm_mem_object_find(rm, h_src_client, h_src_object);
+ if (!src)
+ return false;
+
+ dst = gsp_rm_mem_object_find(rm, h_dst_client, h_dst_object);
+ if (!dst)
+ dst = gsp_rm_mem_object_slot(rm);
+
+ *dst = *src;
+ dst->h_client = h_dst_client;
+ dst->h_object = h_dst_object;
+ dst->valid = true;
+ return true;
+}
+
+/*
+ * CONT.438: resolve a GSP-client channel's client-allocated USERD.
+ *
+ * On GSP, bClientAllocatedUserD channels carry hUserdMemory[subdev]
+ * (NV_CHANNEL_ALLOC_PARAMS @32; gsp_abi_check.c:1764) and
+ * userdOffset[subdev] (@64), while the userdMem memdesc (@168) stays zero.
+ * The real server resolves the handle to a memory object and uses
+ * memdescGetPhysAddr(..., userdOffset) (kernel_channel_gv100.c:204).
+ * We can do the same only when the object's ALLOC already taught us a
+ * physical base; otherwise leave USERD zero and say why. Never invent a PA:
+ * the service later writes GP_GET through the adopted block.
+ */
+static void gsp_rm_resolve_channel_userd(struct gsp_emu_context *ctx,
+ struct sema_channel_desc *desc,
+ u32 h_client, u32 h_object,
+ u32 h_userd, u64 userd_offset)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ struct gsp_rm_mem_object *e;
+ u64 pa;
+
+ e = gsp_rm_mem_object_find(rm, h_client, h_userd);
+ if (!e) {
+ struct gsp_rm_mem_object *alias = NULL;
+ u32 i;
+
+ for (i = 0; i < rm->mem_objects_n; i++) {
+ struct gsp_rm_mem_object *cand = &rm->mem_objects[i];
+
+ if (cand->valid && cand->h_object == h_userd) {
+ alias = cand;
+ break;
+ }
+ }
+ if (alias)
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx UNRESOLVED (handle exists under hClient=0x%x class=0x%x base=0x%llx, channel hClient=0x%x); leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ alias->h_client, alias->class_id,
+ (unsigned long long)alias->base,
+ h_client);
+ else
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx UNRESOLVED (no memory object in table, n=%u); leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ rm->mem_objects_n);
+ return;
+ }
+
+ if (e->address_space == GSPRM_MEM_AS_VIRTUAL) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x is a virtual range (base=0x%llx); refusing to guess a PA, leaving USERD zero\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base);
+ return;
+ }
+ if (!e->base) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x base=0 (server did not echo a physical base); leaving USERD zero\n",
+ h_object, h_userd, e->class_id);
+ return;
+ }
+ /* A physical base is page-granular; anything else is not a PA we know. */
+ if (e->base & 0xfff) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x unaligned base=0x%llx; leaving USERD zero\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base);
+ return;
+ }
+ /* GP_GET @0x88 / GP_PUT @0x8c must lie inside the object. */
+ if (e->size && userd_offset + 0x90 > e->size) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x off=0x%llx outside size=0x%llx; leaving USERD zero\n",
+ h_object, h_userd,
+ (unsigned long long)userd_offset,
+ (unsigned long long)e->size);
+ return;
+ }
+
+ pa = e->base + userd_offset;
+ if (e->address_space == GSPRM_MEM_AS_VIDMEM &&
+ pa + 0x90 > nv_vgpu_fb_emul_size(ctx->vgpu)) {
+ pr_info_ratelimited("vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x pa=0x%llx beyond emulated FB (0x%llx); leaving USERD zero\n",
+ h_object, h_userd, (unsigned long long)pa,
+ (unsigned long long)nv_vgpu_fb_emul_size(ctx->vgpu));
+ return;
+ }
+
+ desc->userd_pa = pa;
+ desc->userd_as = e->address_space == GSPRM_MEM_AS_VIDMEM ? 2 : 1;
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: chan USERD client-alloc hObject=0x%x hUserd=0x%x class=0x%x base=0x%llx off=0x%llx as=%u -> pa=0x%llx (memory-object table)\n",
+ h_object, h_userd, e->class_id,
+ (unsigned long long)e->base,
+ (unsigned long long)userd_offset,
+ desc->userd_as, (unsigned long long)pa);
+}
+
+static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_client,
+ u32 h_object, const u8 *params,
+ u32 params_size)
{
struct sema_channel_desc desc;
+ u64 userd_wire_base = 0;
/*
* CONT.405: name the registration attempt for the direct-GL client's
@@ -3442,6 +3716,7 @@ static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
memcpy(&desc.cid, params + GSPRM_CHAN_CID, 4);
memcpy(&desc.instance_pa, params + GSPRM_CHAN_INSTANCE_MEM, 8);
memcpy(&desc.userd_pa, params + GSPRM_CHAN_USERD_MEM, 8);
+ userd_wire_base = desc.userd_pa;
/*
* SESSION 153-CONTINUED-38: the USERD control page sits at
* userdMem.address + userdOffset[subDeviceId] (NV_CHANNEL_ALLOC_PARAMS
@@ -3509,6 +3784,36 @@ static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
flags, idx, pg, base, desc.userd_pa,
desc.userd_as, h_object);
}
+ /*
+ * CONT.438: a GSP-client kernel channel carries its USERD as
+ * hUserdMemory[subdev] (@32), not in the userdMem memdesc
+ * (@168): the wire memdesc base is zero while userdOffset[]
+ * may be nonzero. Do not let base 0 + offset/flags masquerade
+ * as a small PA - resolve the real memory object, or leave
+ * USERD zero (the service must never write a guessed block).
+ */
+ if (userd_wire_base == 0) {
+ u32 h_userd = 0;
+
+ desc.userd_pa = 0;
+ desc.userd_as = 0;
+ if (params_size >=
+ GSPRM_CHAN_USERD_OFFSET + (sd + 1) * 8 &&
+ params_size >=
+ GSPRM_CHAN_HUSERD_MEMORY +
+ (sd + 1) * 4) {
+ memcpy(&h_userd,
+ params + GSPRM_CHAN_HUSERD_MEMORY +
+ (u64)sd * 4, 4);
+ if (h_userd)
+ gsp_rm_resolve_channel_userd(ctx,
+ &desc,
+ h_client,
+ h_object,
+ h_userd,
+ uo);
+ }
+ }
}
memcpy(&desc.notifier_pa, params + GSPRM_CHAN_NOTIFIER_MEM, 8);
memcpy(&desc.notifier_as, params + GSPRM_CHAN_NOTIFIER_MEM +
@@ -3739,10 +4044,17 @@ static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
}
}
+ /*
+ * CONT.438: remember client memory objects (sysmem/vidmem/virtual)
+ * so a GSP-client channel's hUserdMemory[subdev] can be resolved to
+ * a physical USERD base later in this same serialized RPC stream.
+ */
+ gsp_rm_mem_object_note(rm, hc, hp, ho, class_id, params, params_size);
+
/* Channels: hand the progress service everything it needs to run the
* work submitted on this channel (GPFIFO ring + USERD). */
if (gsp_rm_is_channel_class(class_id))
- gsp_rm_register_channel(ctx, ho, params, params_size);
+ gsp_rm_register_channel(ctx, hc, ho, params, params_size);
/*
* r1621: display channel objects (core 0xc57d, window 0xc57e, cursor
@@ -5522,6 +5834,17 @@ static void gsp_rm_handle_dup_object(struct gsp_emu_context *ctx,
"vgsp-rm: DUP_OBJECT VAS alias h=0x%x -> src h=0x%x (gpfifo VA now resolves through the source directory)\n",
hDstObject, hSrcObject);
+ /*
+ * CONT.438: a duplicated memory object keeps the same physical base
+ * under the destination client's handle (the client may pass the dup
+ * as hUserdMemory).
+ */
+ if (gsp_rm_mem_object_alias(rm, hDstClient, hDstObject,
+ hSrcClient, hSrcObject))
+ nv_vgpu_info(ctx->vgpu,
+ "vgsp-rm: DUP_OBJECT mem-object alias h=0x%x (client 0x%x) -> src h=0x%x (client 0x%x)\n",
+ hDstObject, hDstClient, hSrcObject, hSrcClient);
+
/* Small delay before the reply: same reply-matching race mitigation as
* FREE/UNLOADING. */
udelay(2000);
@@ -5561,6 +5884,7 @@ static void gsp_rm_handle_free(struct gsp_emu_context *ctx, const u8 *elem,
memcpy(&hObjOld, params + 8, 4);
memset(params + 12, 0, 4); /* status = NV_OK */
+ gsp_rm_mem_object_retire(rm, hRoot, hObjOld);
gsp_sema_service_channel_del(ctx->vgpu, hObjOld);
/*