From 4266fffc45352bee58323b7903354df00b3df03d Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 18:07:00 -0400 Subject: kernel: Multi-element msgq support + USER_REGISTER_ACCESS_MAP handler - gsp_rm_send: Split large RPC replies across multiple queue elements (fixes fortify_panic when reply > 4016 bytes RPC data) - gsp_rm_build_record: Build multi-element records with proper elemCount, cross-element data continuation, wrap-around handling - Add INTERNAL_GPU_GET_USER_REGISTER_ACCESS_MAP (0x20800a41) handler returning empty register access map (8204 bytes) --- kernel/gsp_rm_emulation.c | 195 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 167 insertions(+), 28 deletions(-) (limited to 'kernel') diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c index d3e4548..e152e5c 100644 --- a/kernel/gsp_rm_emulation.c +++ b/kernel/gsp_rm_emulation.c @@ -81,24 +81,55 @@ static u32 gsp_rm_checksum32(const void *data, u32 len) #define GSPRM_ELEM_HDR_SIZE 0x30 /* - * Build one 4KB queue element carrying an rpc_message_header_v + payload. - * Returns message length (element header + rpc length). + * Max RPC data that fits in one queue element: + * 4096 (element) - 0x30 (element hdr) - 32 (rpc hdr) = 4016 bytes. */ -static u32 gsp_rm_build_element(struct gsp_rm_emu *rm, u8 *elem, +#define GSPRM_MAX_SINGLE_ELEM_DATA (GSPRM_QUEUE_ELEMENT_SIZE - \ + GSPRM_ELEM_HDR_SIZE - \ + sizeof(struct gsprm_rpc_header)) + +/* + * Build a multi-element record into a contiguous buffer. + * + * The record spans elem_count consecutive queue elements: + * [0]: element header (0x30) + rpc header (32) + data[0..4015] + * [1..N-1]: continuation data only (no headers) + * + * Returns the total record size in bytes (elem_count * 4096). + * Sets *out_elem_count to the number of elements used. + */ +static u32 gsp_rm_build_record(struct gsp_rm_emu *rm, u8 *buf, u32 buf_size, u32 function, u32 sequence, u32 result, - const void *data, u32 data_len) + const void *data, u32 data_len, + u32 *out_elem_count) { struct gsprm_rpc_header *hdr = - (struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE); + (struct gsprm_rpc_header *)(buf + GSPRM_ELEM_HDR_SIZE); u32 rpc_len = sizeof(*hdr) + data_len; u32 msg_len = GSPRM_ELEM_HDR_SIZE + rpc_len; u32 aligned_len; - u32 elem_count = 1; + u32 elem_count; + u32 first_data; + u32 remaining; + u32 i; + + /* How many elements does this record span? */ + elem_count = (msg_len + GSPRM_QUEUE_ELEMENT_SIZE - 1) / + GSPRM_QUEUE_ELEMENT_SIZE; + if (elem_count == 0) + elem_count = 1; + + if (elem_count * GSPRM_QUEUE_ELEMENT_SIZE > buf_size) { + pr_err("GSPRM: build_record overflow: need %u have %u\n", + elem_count * GSPRM_QUEUE_ELEMENT_SIZE, buf_size); + return 0; + } + + memset(buf, 0, elem_count * GSPRM_QUEUE_ELEMENT_SIZE); - memset(elem, 0, GSPRM_QUEUE_ELEMENT_SIZE); - /* elem+0x20: checksum, filled below */ - memcpy(elem + 0x24, &rm->stat_seq, 4); - memcpy(elem + 0x28, &elem_count, 4); + /* First element: header + rpc header + start of data */ + memcpy(buf + 0x24, &rm->stat_seq, 4); /* seqNum */ + memcpy(buf + 0x28, &elem_count, 4); /* elemCount */ hdr->header_version = GSPRM_RPC_HEADER_VERSION; hdr->signature = GSPRM_RPC_SIGNATURE; @@ -108,50 +139,119 @@ static u32 gsp_rm_build_element(struct gsp_rm_emu *rm, u8 *elem, hdr->rpc_result_private = result; hdr->sequence = sequence; hdr->spare = 0; - if (data && data_len) - memcpy((u8 *)(hdr + 1), data, data_len); + + /* Copy data across element boundaries */ + first_data = min(data_len, (u32)GSPRM_MAX_SINGLE_ELEM_DATA); + if (data && first_data) + memcpy((u8 *)(hdr + 1), data, first_data); + + remaining = data_len - first_data; + if (remaining > 0 && data) { + const u8 *src = (const u8 *)data + first_data; + u8 *dst = buf + GSPRM_QUEUE_ELEMENT_SIZE; + + for (i = 1; i < elem_count && remaining > 0; i++) { + u32 chunk = min(remaining, + (u32)GSPRM_QUEUE_ELEMENT_SIZE); + + memcpy(dst, src, chunk); + src += chunk; + dst += GSPRM_QUEUE_ELEMENT_SIZE; + remaining -= chunk; + } + } /* checksum covers msg_len zero-padded to 8 (buffer already zeroed) */ aligned_len = (msg_len + 7) & ~7U; - *(u32 *)(elem + 0x20) = gsp_rm_checksum32(elem, aligned_len); + *(u32 *)(buf + 0x20) = gsp_rm_checksum32(buf, aligned_len); - return aligned_len; + *out_elem_count = elem_count; + return elem_count * GSPRM_QUEUE_ELEMENT_SIZE; } /* - * Write one status queue element and advance the status TX writePtr. + * Write a status queue record (1+ elements) and advance the writePtr. */ static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function, u32 sequence, u32 result, const void *data, u32 data_len) { struct gsp_rm_emu *rm = ctx->rm_emu; - u8 *elem; + u8 *buf; u64 elem_gpa; u32 slot; u32 new_wp; + u32 elem_count; + u32 record_size; + u32 buf_size; int ret; - elem = kzalloc(GSPRM_QUEUE_ELEMENT_SIZE, GFP_KERNEL); - if (!elem) + /* Allocate buffer for worst-case multi-element record */ + buf_size = GSPRM_QUEUE_ELEMENT_SIZE * + ((data_len + GSPRM_MAX_SINGLE_ELEM_DATA + + GSPRM_QUEUE_ELEMENT_SIZE - 1) / + GSPRM_QUEUE_ELEMENT_SIZE + 1); + if (buf_size < GSPRM_QUEUE_ELEMENT_SIZE) + buf_size = GSPRM_QUEUE_ELEMENT_SIZE; + + buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf) return -ENOMEM; - gsp_rm_build_element(rm, elem, function, sequence, result, data, - data_len); + record_size = gsp_rm_build_record(rm, buf, buf_size, function, + sequence, result, data, data_len, + &elem_count); + if (!record_size) { + kfree(buf); + return -EOVERFLOW; + } + + /* Check we don't overflow the status queue */ + if (elem_count > rm->stat_msgcount) { + nv_vgpu_err(ctx->vgpu, "GSPRM: record too large: %u elements > queue %u", + elem_count, rm->stat_msgcount); + kfree(buf); + return -EOVERFLOW; + } + /* Write all elements to consecutive queue slots */ 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); - ret = gsp_rm_write(ctx, elem_gpa, elem, GSPRM_QUEUE_ELEMENT_SIZE); - if (ret) { - nv_vgpu_err(ctx->vgpu, "GSPRM: status element write failed gpa=0x%llx ret=%d", - elem_gpa, ret); - kfree(elem); - return ret; + /* 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; + + ret = gsp_rm_write(ctx, elem_gpa, 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); + 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); + if (ret) { + nv_vgpu_err(ctx->vgpu, "GSPRM: status wrap write failed ret=%d", + ret); + kfree(buf); + return ret; + } + } } - kfree(elem); - rm->stat_write++; + kfree(buf); + + rm->stat_write += elem_count; rm->stat_seq++; /* bump status TX header writePtr (offset 0x10) */ @@ -685,6 +785,39 @@ static void gsp_rm_handle_internal_device_table(struct gsp_emu_context *ctx, TU104_INTERNAL_DEVICE_TABLE_SIZE); } +/* + * INTERNAL_GPU_GET_USER_REGISTER_ACCESS_MAP (0x20800a41) + * + * Returns a compressed register access map telling the guest which BAR0 + * registers are userspace-accessible. For our emulated GPU, return an + * empty map (no compressed data, no profiling ranges). + */ +static void gsp_rm_handle_user_register_access_map(struct gsp_emu_context *ctx, + u32 sequence, + u32 *out_status, + u8 *out_params, + u32 *out_params_size) +{ + /* NV2080_CTRL_INTERNAL_GPU_GET_USER_REGISTER_ACCESS_MAP_PARAMS: + * userRegisterAccessMapSize(4) + compressedSize(4) + + * compressedData[4096] + profilingRangesSize(4) + profilingRanges[4096] + * Total: 8204 bytes + */ + u32 val32 = 0; + + memset(out_params, 0, 8204); + memcpy(out_params + 0, &val32, 4); /* userRegisterAccessMapSize */ + memcpy(out_params + 4, &val32, 4); /* compressedSize */ + /* compressedData[4096] = zeros */ + memcpy(out_params + 4104, &val32, 4); /* profilingRangesSize */ + /* profilingRanges[4096] = zeros */ + + *out_params_size = 8204; + *out_status = 0; + + nv_vgpu_info(ctx->vgpu, "GSPRM: USER_REGISTER_ACCESS_MAP -> empty map"); +} + static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx, const u8 *elem, u32 sequence) { @@ -736,6 +869,12 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx, reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size); break; + case 0x20800a41: /* INTERNAL_GPU_GET_USER_REGISTER_ACCESS_MAP */ + gsp_rm_handle_user_register_access_map(ctx, sequence, + &out_status, + reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size); + break; + default: /* Unknown control command - return success with zeroed params */ nv_vgpu_info(ctx->vgpu, "GSPRM: RM_CONTROL unknown cmd=0x%x -> generic SUCCESS", -- cgit v1.2.3