summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 18:07:00 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 18:07:00 -0400
commit4266fffc45352bee58323b7903354df00b3df03d (patch)
tree2e5d4a50fff18ba07ab0b6d844b585ba94660573 /kernel
parent6aadb593750736f239fd54a0ccef4b3834c08fc4 (diff)
downloadopen-iov-4266fffc45352bee58323b7903354df00b3df03d.tar.gz
open-iov-4266fffc45352bee58323b7903354df00b3df03d.zip
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)
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_rm_emulation.c195
1 files changed, 167 insertions, 28 deletions
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",