diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-21 20:43:39 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-21 20:43:39 -0400 |
| commit | 92fa88178956b94439857b8ec97da7f29f42bf9f (patch) | |
| tree | 5ba65db30dac154b73790af24bfb2d4d07599027 /kernel | |
| parent | 1e73a2e00c76e25e4ce2980e5d04a533b0c6158d (diff) | |
| download | open-iov-92fa88178956b94439857b8ec97da7f29f42bf9f.tar.gz open-iov-92fa88178956b94439857b8ec97da7f29f42bf9f.zip | |
kernel: Fix guest memory corruption from non-page-aware queue I/O + fn-10 logging
ROOT CAUSE (guest userspace segfaults): the GSP-RM msgq buffer is
NV_MEMORY_NONCONTIGUOUS (129 independent 4K pages). gsp_rm_send wrote
multi-element records CONTIGUOUSLY from a translated start GPA - a record
crossing a page boundary scribbled into arbitrary guest RAM (pages are
unrelated GPAs). Observed as pgrep/ls segfaulting in the guest.
- gsp_rm_write_buf/gsp_rm_read_buf: split every shared-buffer access at
page boundaries and translate each page via the buffer page table
(gsp_rm_off_to_gpa). All queue record/element/header accesses converted.
- fn-10 (FREE) / fn-47 (UNLOADING) handler now logs hClient/hObject +
cmd_read for unload-cycle debugging.
Diagnosis captured: driver sends UNLOADING(47) then final FREEs during
teardown; queue-death detector can strand the last frees (fn-10 timeout
blocks close -> nvidia-smi hangs in exit).
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/gsp_rm_emulation.c | 122 |
1 files changed, 94 insertions, 28 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c index 8f8f9c2..0a040d5 100644 --- a/kernel/gsp_rm_emulation.c +++ b/kernel/gsp_rm_emulation.c @@ -51,6 +51,60 @@ static u64 gsp_rm_off_to_gpa(struct gsp_rm_emu *rm, u64 off) return rm->ptes[idx] + (off & 0xfff); } +/* + * Page-aware shared-buffer I/O. The queue buffer is NV_MEMORY_NONCONTIGUOUS: + * its 4K pages are independent guest allocations, so a range crossing a page + * boundary is NOT contiguous in guest physical memory. Translating the start + * and blasting a multi-page range would scribble into arbitrary guest RAM + * (observed as guest userspace segfaults). Split at page boundaries and + * translate each page separately. + */ +static int gsp_rm_write_buf(struct gsp_emu_context *ctx, u64 off, + const void *buf, size_t len) +{ + struct gsp_rm_emu *rm = ctx->rm_emu; + const u8 *p = buf; + int ret; + + while (len) { + u64 gpa = gsp_rm_off_to_gpa(rm, off); + size_t chunk = min_t(size_t, len, 0x1000 - (off & 0xfff)); + + if (!gpa) + return -EINVAL; + ret = gsp_rm_write(ctx, gpa, (void *)p, chunk); + if (ret) + return ret; + p += chunk; + off += chunk; + len -= chunk; + } + return 0; +} + +static int gsp_rm_read_buf(struct gsp_emu_context *ctx, u64 off, + void *buf, size_t len) +{ + struct gsp_rm_emu *rm = ctx->rm_emu; + u8 *p = buf; + int ret; + + while (len) { + u64 gpa = gsp_rm_off_to_gpa(rm, off); + size_t chunk = min_t(size_t, len, 0x1000 - (off & 0xfff)); + + if (!gpa) + return -EINVAL; + ret = gsp_rm_read(ctx, gpa, p, chunk); + if (ret) + return ret; + p += chunk; + off += chunk; + len -= chunk; + } + return 0; +} + /* ============================================================ * Status queue element construction * ============================================================ */ @@ -177,7 +231,6 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, { struct gsp_rm_emu *rm = ctx->rm_emu; u8 *buf; - u64 elem_gpa; u32 slot; u32 new_wp; u32 elem_count; @@ -213,33 +266,31 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, return -EOVERFLOW; } - /* Write all elements to consecutive queue slots */ + /* Write all elements to consecutive queue slots. Page-aware: the + * queue buffer is non-contiguous, so translate per page. */ slot = rm->stat_write % rm->stat_msgcount; - elem_gpa = gsp_rm_off_to_gpa(rm, rm->stat_off + rm->entry_off + - (u64)slot * GSPRM_QUEUE_ELEMENT_SIZE); /* Handle wrap-around: write may span the end of the circular queue */ { u32 elems_to_end = rm->stat_msgcount - slot; u32 first_chunk = min(elem_count, elems_to_end); u32 second_chunk = elem_count - first_chunk; + u64 rec_off = rm->stat_off + rm->entry_off + + (u64)slot * GSPRM_QUEUE_ELEMENT_SIZE; - ret = gsp_rm_write(ctx, elem_gpa, buf, - first_chunk * GSPRM_QUEUE_ELEMENT_SIZE); + ret = gsp_rm_write_buf(ctx, rec_off, buf, + first_chunk * GSPRM_QUEUE_ELEMENT_SIZE); if (ret) { - nv_vgpu_err(ctx->vgpu, "GSPRM: status write failed gpa=0x%llx ret=%d", - elem_gpa, ret); + nv_vgpu_err(ctx->vgpu, "GSPRM: status write failed off=0x%llx ret=%d", + rec_off, ret); kfree(buf); return ret; } if (second_chunk > 0) { - u64 wrap_gpa = gsp_rm_off_to_gpa(rm, - rm->stat_off + rm->entry_off); - - ret = gsp_rm_write(ctx, wrap_gpa, - buf + first_chunk * GSPRM_QUEUE_ELEMENT_SIZE, - second_chunk * GSPRM_QUEUE_ELEMENT_SIZE); + ret = gsp_rm_write_buf(ctx, rm->stat_off + rm->entry_off, + buf + first_chunk * GSPRM_QUEUE_ELEMENT_SIZE, + second_chunk * GSPRM_QUEUE_ELEMENT_SIZE); if (ret) { nv_vgpu_err(ctx->vgpu, "GSPRM: status wrap write failed ret=%d", ret); @@ -260,8 +311,8 @@ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, /* bump status TX header writePtr (offset 0x10) */ new_wp = rm->stat_write; - ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + 0x10, - &new_wp, sizeof(new_wp)); + ret = gsp_rm_write_buf(ctx, rm->stat_off + 0x10, + &new_wp, sizeof(new_wp)); if (ret) { nv_vgpu_err(ctx->vgpu, "GSPRM: status writePtr update failed ret=%d", ret); return ret; @@ -406,15 +457,15 @@ static int gsp_rm_link_status_queue(struct gsp_emu_context *ctx) tx.rxHdrOff = 0x20; /* ALIGN_UP(sizeof(msgqTxHeader)=32, 1<<hdrAlign(4)=16) */ tx.entryOff = rm->entry_off; - ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off), &tx, sizeof(tx)); + ret = gsp_rm_write_buf(ctx, rm->stat_off, &tx, sizeof(tx)); if (ret) { nv_vgpu_err(ctx->vgpu, "GSPRM: status TX header write failed ret=%d", ret); return ret; } /* status queue RX header (our command-queue read position, swap mode) */ - ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + tx.rxHdrOff, - &zero, sizeof(zero)); + ret = gsp_rm_write_buf(ctx, rm->stat_off + tx.rxHdrOff, + &zero, sizeof(zero)); if (ret) { nv_vgpu_err(ctx->vgpu, "GSPRM: status RX header write failed ret=%d", ret); return ret; @@ -1237,9 +1288,24 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem) /* Synchronous RPC: echo function+sequence with success so the * driver's rpcRecvPoll completes. */ rm->unknown_rpcs++; - nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u len=%u -> generic SUCCESS reply (unknown #%llu)", - gsp_rm_rpc_name(function), function, sequence, - hdr->length, rm->unknown_rpcs); + if (function == 10 || function == 47) { + /* FREE/UNLOADING: log hObject for unload-cycle debugging */ + const u8 *p = elem + GSPRM_ELEM_HDR_SIZE + + sizeof(struct gsprm_rpc_header); + u32 hc = 0, ho = 0; + + if (hdr->length >= sizeof(*hdr) + 8) { + memcpy(&hc, p, 4); + memcpy(&ho, p + 4, 4); + } + nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u hClient=0x%x hObject=0x%x cmd_read=%u", + gsp_rm_rpc_name(function), function, sequence, + hc, ho, rm->cmd_read); + } else { + nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u len=%u -> generic SUCCESS reply (unknown #%llu)", + gsp_rm_rpc_name(function), function, sequence, + hdr->length, rm->unknown_rpcs); + } if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0)) nv_vgpu_err(ctx->vgpu, "GSPRM: reply send failed fn=%u", function); else @@ -1263,7 +1329,7 @@ static void gsp_rm_poll_commands(struct gsp_emu_context *ctx) struct gsprm_msgq_tx_header tx; int ret; - ret = gsp_rm_read(ctx, gsp_rm_off_to_gpa(rm, rm->cmd_off), &tx, sizeof(tx)); + ret = gsp_rm_read_buf(ctx, rm->cmd_off, &tx, sizeof(tx)); if (ret) { /* VM may be gone or device closed; stay quiet, retry later */ return; @@ -1283,15 +1349,15 @@ static void gsp_rm_poll_commands(struct gsp_emu_context *ctx) /* Consume records until we catch up with the (wrapped) writePtr */ while (rm->cmd_read != tx.writePtr) { u8 *elem; - u64 elem_gpa = gsp_rm_off_to_gpa(rm, rm->cmd_off + rm->entry_off + - (u64)(rm->cmd_read % rm->cmd_msgcount) * GSPRM_QUEUE_ELEMENT_SIZE); + u64 elem_off = rm->cmd_off + rm->entry_off + + (u64)(rm->cmd_read % rm->cmd_msgcount) * GSPRM_QUEUE_ELEMENT_SIZE; u32 signature, elem_count; const struct gsprm_rpc_header *hdr; elem = kmalloc(GSPRM_QUEUE_ELEMENT_SIZE, GFP_KERNEL); if (!elem) return; - ret = gsp_rm_read(ctx, elem_gpa, elem, GSPRM_QUEUE_ELEMENT_SIZE); + ret = gsp_rm_read_buf(ctx, elem_off, elem, GSPRM_QUEUE_ELEMENT_SIZE); if (ret) { kfree(elem); return; @@ -1326,8 +1392,8 @@ static void gsp_rm_poll_commands(struct gsp_emu_context *ctx) /* Publish our command-queue read position into the status queue RX * header (MSGQ_FLAGS_SWAP_RX: read pointers live in the peer's queue). */ - gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + 0x20, - &rm->cmd_read, sizeof(rm->cmd_read)); + gsp_rm_write_buf(ctx, rm->stat_off + 0x20, + &rm->cmd_read, sizeof(rm->cmd_read)); } /* ============================================================ |
