summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 20:43:39 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 20:43:39 -0400
commit92fa88178956b94439857b8ec97da7f29f42bf9f (patch)
tree5ba65db30dac154b73790af24bfb2d4d07599027 /kernel
parent1e73a2e00c76e25e4ce2980e5d04a533b0c6158d (diff)
downloadopen-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.c122
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));
}
/* ============================================================