diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-12 01:29:13 -0500 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-12 01:29:13 -0500 |
| commit | fa985e759707ce011bd238bbbb73bc422fccb90f (patch) | |
| tree | 70c2b7956e3e5efaf001a02da173fc6df75ddfd6 | |
| parent | fc01d736909bfaa411643eddbe1593a78c83371a (diff) | |
| download | open-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.
| -rw-r--r-- | kernel/gsp_rm_emulation.c | 330 | ||||
| -rw-r--r-- | kernel/gsp_rm_emulation.h | 41 | ||||
| -rw-r--r-- | kernel/gsp_sema_service.c | 66 |
3 files changed, 433 insertions, 4 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); /* diff --git a/kernel/gsp_rm_emulation.h b/kernel/gsp_rm_emulation.h index 82ae493..17154b7 100644 --- a/kernel/gsp_rm_emulation.h +++ b/kernel/gsp_rm_emulation.h @@ -50,6 +50,35 @@ struct gsp_emu_context; #define GSPRM_RPC_FUNC_UPDATE_BAR_PDE 70 #define GSPRM_RPC_EVENT_POST_EVENT 0x1003 #define GSPRM_MAX_EVENTS 128 +/* + * CONT.438: bounded client memory-object table. GSP-client kernel channels + * pass a client-allocated USERD as hUserdMemory[subdev] (NV_CHANNEL_ALLOC_PARAMS + * @32), not as the userdMem memdesc (@168), so the channel alloc alone cannot + * name the USERD PA. Remember the memory-object ALLOCs by (hClient, hObject) + * and resolve the channel's handle later. Sized well above the handful of + * USERD objects live at once; retired slots are reused. + */ +#define GSPRM_MAX_MEM_OBJECTS 128 + +/* Address-space tag for a remembered memory object (NV_ADDR_* values). */ +enum gsp_rm_mem_as { + GSPRM_MEM_AS_UNKNOWN = 0, + GSPRM_MEM_AS_SYSMEM = 1, /* NV_ADDR_SYSMEM: base is a guest PA */ + GSPRM_MEM_AS_VIDMEM = 2, /* NV_ADDR_FBMEM: base is an FB offset */ + GSPRM_MEM_AS_VIRTUAL = 3, /* VA range only; never a physical base */ +}; + +struct gsp_rm_mem_object { + u32 h_object; + u32 h_client; + u32 h_parent; + u32 class_id; + u64 base; /* PA (sysmem), FB offset (vidmem), or VA */ + u64 size; + u8 address_space; /* enum gsp_rm_mem_as */ + bool valid; +}; + /* Max event records written between two replies (status-ring safety). */ #define GSPRM_EVENT_BURST_MAX 24 #define GSPRM_RPC_EVENT_GSP_INIT_DONE 0x1001 @@ -211,6 +240,18 @@ struct gsp_rm_emu { u64 events_dropped; u32 event_burst; /* event records written in the current window */ unsigned long event_burst_at; /* m171: burst window start (time-based reset) */ + + /* + * CONT.438: client memory objects observed through GSP_RM_ALLOC, + * keyed by (hClient, hObject), used to turn a channel's + * hUserdMemory[subdev] into a physical USERD base. All alloc/free + * handling is serialized on the emulation thread, so no lock. + */ + struct gsp_rm_mem_object mem_objects[GSPRM_MAX_MEM_OBJECTS]; + u32 mem_objects_n; + u32 mem_objects_next; /* eviction cursor when full */ + u32 mem_object_logs; /* bounded mem-object witness budget */ + u32 mem_object_evictions; /* bounded diagnostic counter */ }; /* diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 6da812d..146291c 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -26747,6 +26747,12 @@ static bool svc_ce_declared_userd(struct nv_vgpu_device *vgpu, return true; } +/* + * CONT.438: bounded witness for FB-sweep candidates skipped because another + * live channel already owns their page. + */ +static u32 svc_foreign_userd_skip_log; + static int svc_chan_userd(struct nv_vgpu_device *vgpu, struct svc_channel *ch, u64 ring_pa, int ring_ap, u32 *put, u32 *get, int rrc) { @@ -27214,11 +27220,46 @@ ce_fb_scan: bool wtry = false, wused = false; u32 woff = (u32)(ch->gpfifo_va & (SEMA_PAGE_SIZE - 1)); + /* + * CONT.438 (patch A): a candidate on a 4K page that another + * live channel already owns is not this channel's USERD. The + * live X/GLX channel 0x1000013 re-adopted the RC watchdog + * channel 0x31415900's block at fb 0x8f0000 every pass (its + * own USERD is client-allocated, so the wire userdMem is + * zero); the foreign pair then never matched the client's + * progress. Snapshot the other channels' USERD pages once per + * sweep and refuse their pages as candidates. Failing closed + * here is deliberate: a foreign block must not become the + * write target for GP_GET. + */ + u64 foreign_userd_pages[SVC_MAX_CHANNELS]; + unsigned long chflags; + u32 foreign_userd_n = 0; + u32 oi; + + spin_lock_irqsave(&svc.chan_lock, chflags); + for (oi = 0; + oi < SVC_MAX_CHANNELS && + foreign_userd_n < SVC_MAX_CHANNELS; oi++) { + struct svc_channel *other = &svc.chan[oi]; + + if (!other->valid || other->vgpu != vgpu || + !other->userd_pa) + continue; + if (other->h_object == ch->h_object && + other->vgpu == ch->vgpu) + continue; + foreign_userd_pages[foreign_userd_n++] = + other->userd_pa & ~0xfffULL; + } + spin_unlock_irqrestore(&svc.chan_lock, chflags); + ch->fb_userd_scan_at = jiffies; ch->fb_userd_scan_n++; ch->userd_scans++; ring_sweep_ran = true; - lim = min_t(u64, svc_fb_shadow_size(vgpu), 0x20000000ULL); + lim = min_t(u64, svc_fb_shadow_size(vgpu), + 0x20000000ULL); if (base && lim) { for (off = 0; off + 0xa0 <= lim; off += 0x200) { u32 hp, hg; @@ -27230,6 +27271,8 @@ ce_fb_scan: int rck = -1; u64 rkva = 0; u32 rklen = 0; + u64 page; + bool foreign = false; memcpy(&hp, base + off + SVC_USERD_GP_PUT, 4); memcpy(&hg, base + off + SVC_USERD_GP_GET, 4); @@ -27246,6 +27289,27 @@ ce_fb_scan: ch->gpfifo_entries <= 1024))) continue; gate_n++; + /* + * CONT.438: the page-ownership gate. A block + * on another live channel's USERD page cannot + * be this channel's control block. + */ + page = off & ~0xfffULL; + for (oi = 0; oi < foreign_userd_n; oi++) { + if (foreign_userd_pages[oi] == page) { + foreign = true; + break; + } + } + if (foreign) { + if (svc_foreign_userd_skip_log < 16) { + svc_foreign_userd_skip_log++; + pr_info("sema-chan: userdp h=0x%x skip fb 0x%llx (page owned by another live channel; put=%u get=%u)\n", + ch->h_object, off, hp, + hg); + } + continue; + } /* Validate: every claimed entry decodes to a * pushbuffer VA in the ring's window. */ for (k = 0; k < hp && k < 64; k++) { |
