summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_rm_emulation.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-23 11:46:33 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-23 11:46:33 -0400
commit534131933ef24d602314281573db4ef16cc08a93 (patch)
tree5ec97ff6576e1d67e559dddffcb9d4b03c68f984 /kernel/gsp_rm_emulation.c
parent7ec899ccdfd0298f365e1613c1ac5bfcfa551475 (diff)
downloadopen-iov-534131933ef24d602314281573db4ef16cc08a93.tar.gz
open-iov-534131933ef24d602314281573db4ef16cc08a93.zip
kernel: coherent GSP-RM replies for engine-context creation
- GSP_RM_ALLOC: echo the request payload back with status forced to NV_OK. The kernel driver copies the reply params over the caller's params buffer using the class-expected size, so a zeroed reply smashed libcuda's stack (SIGSEGV at the engine-registration continuation); echoing makes the copy-back idempotent. - CE_GET_PHYSICAL_CAPS / CE_GET_ALL(_PHYSICAL)_CAPS: report sysmem, P2P and BL copy caps and mark the GR-synchronous CE (GRCE, COPY0). Without a GRCE the client's context walk ends with an empty secondary-engine record and the engine RM alloc is issued with class 0 (rejected -> cuCtxCreate fails). - TU104 engine table: CE0 shares the GR PBDMA ids (the driver's GR/GRCE runqueue validation requires it), plus an entry for the async copy engine (COPY14) advertised in the GSP static info engine caps. - tools/guest-diag/ioctl_trace: decode the RM status fields of alloc, control and free so the first failing driver call is visible in traces. gitignore: keep the CUDA analysis scratch tree (incl. WORKING-HERE.md) local.
Diffstat (limited to 'kernel/gsp_rm_emulation.c')
-rw-r--r--kernel/gsp_rm_emulation.c154
1 files changed, 111 insertions, 43 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index b9f8417..44de6c0 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -352,6 +352,10 @@ static u32 gsp_rm_checksum32(const void *data, u32 len)
*/
#define GSPRM_ELEM_HDR_SIZE 0x30
+/* rpc_gsp_rm_alloc_v03_00 fixed header: hClient, hParent, hObject, hClass,
+ * status, paramsSize, flags, reserved[4]. */
+#define GSPRM_ALLOC_HDR_SIZE 32
+
/*
* Max RPC data that fits in one queue element:
* 4096 (element) - 0x30 (element hdr) - 32 (rpc hdr) = 4016 bytes.
@@ -817,8 +821,15 @@ static void gsp_rm_build_static_info(u8 *buf)
val32 = 0;
memcpy(buf + TU104_GSCI_OFF_sriovMaxGfid, &val32, 4);
- /* engineCaps (offset 1204, 12 bytes) */
- val32 = 0x60003E02; /* GR0|CE0-4|NVDEC0|NVDEC1 */
+ /* engineCaps (offset 1204, 12 bytes)
+ *
+ * Bit N = RM_ENGINE_TYPE_<N> present (RM_ENGINE_TYPE_* equals the bit
+ * index, see gpu_engine_type.h). The async copy engine is
+ * RM_ENGINE_TYPE_COPY14 (0x17); clients (libcuda) refuse to create a
+ * context without it: the secondary/async engine record stays empty and
+ * the engine-context RM alloc is issued with class 0 (NV01_ROOT), which
+ * the driver must reject. */
+ val32 = 0x60803E02; /* GR0|CE0-4|COPY14|NVDEC0|NVDEC1 */
memcpy(buf + TU104_GSCI_OFF_engineCaps, &val32, 4);
val32 = 0x00362020; /* NVENC0|SW|SEC2|HOST|PMU|FBFLCN */
memcpy(buf + TU104_GSCI_OFF_engineCaps + 4, &val32, 4);
@@ -1308,8 +1319,19 @@ static void gsp_rm_handle_ce_physical_caps(struct gsp_emu_context *ctx,
if (ce_type >= TU104_ENGINE_TYPE_COPY0 &&
ce_type < TU104_ENGINE_TYPE_COPY0 + TU104_NUM_CES) {
+ u32 idx = ce_type - TU104_ENGINE_TYPE_COPY0;
+
out_params[4] = TU104_CE_CAPS0;
out_params[5] = TU104_CE_CAPS1;
+
+ /* The GR-synchronous CEs (GRCE) are the first ones: the client
+ * picks the last GRCE as its compute companion ("ceForCompute",
+ * rm_device.c) and every non-GRCE CE becomes an async CE. With
+ * no GRCE at all the client's context-creation walk ends with an
+ * empty secondary-engine record and the engine RM alloc is issued
+ * with class 0. */
+ if (idx == TU104_GRCE_IDX)
+ out_params[4] |= TU104_CE_CAPS0_GRCE;
} else {
*out_status = 0x56; /* NV_ERR_NOT_SUPPORTED */
*out_params_size = req_size;
@@ -1342,6 +1364,8 @@ static void gsp_rm_handle_ce_all_caps(struct gsp_emu_context *ctx,
for (i = 0; i < TU104_NUM_CES; i++) {
out_params[i * 2 + 0] = TU104_CE_CAPS0;
out_params[i * 2 + 1] = TU104_CE_CAPS1;
+ if (i == TU104_GRCE_IDX)
+ out_params[i * 2 + 0] |= TU104_CE_CAPS0_GRCE;
present |= 1ULL << i;
}
memcpy(out_params + 128, &present, 8);
@@ -1898,6 +1922,87 @@ static void gsp_rm_handle_copy_server_pdes(struct gsp_emu_context *ctx,
}
}
+static bool gsp_rm_alloc_interesting(u32 class_id);
+
+/*
+ * GSP_RM_ALLOC (103) - rpc_gsp_rm_alloc_v03_00 (32 byte header) followed by
+ * the class-specific allocation parameters.
+ *
+ * The reply MUST echo the request payload back: the client copies the reply
+ * params over the caller's params buffer using the class-expected size (not
+ * the size the caller reserved), so replying with zeroed params smashes
+ * whatever sits above the caller's params buffer. The alloc params are
+ * in/out for the classes we do not model in detail, so echoing the input is
+ * both the safe and the correct "unchanged" answer. status (offset 16) is
+ * the only field we deliberately rewrite (to NV_OK).
+ *
+ * NOTE: the RPC header 'length' only counts the fixed struct size, so it
+ * must NOT be used to bound the params - read the paramsSize field from the
+ * alloc header instead (see handoff PART 6).
+ */
+static void gsp_rm_handle_rm_alloc(struct gsp_emu_context *ctx,
+ const u8 *elem, u32 sequence)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const u8 *p = elem + GSPRM_ELEM_HDR_SIZE +
+ sizeof(struct gsprm_rpc_header);
+ const u8 *params = p + GSPRM_ALLOC_HDR_SIZE;
+ u32 hc = 0, hp = 0, ho = 0, class_id = 0;
+ u32 params_size = 0, flags = 0;
+ u32 echo_len, reply_len;
+ u8 *reply;
+
+ memcpy(&hc, p + 0, 4);
+ memcpy(&hp, p + 4, 4);
+ memcpy(&ho, p + 8, 4);
+ memcpy(&class_id, p + 12, 4);
+ memcpy(&params_size, p + 20, 4);
+ memcpy(&flags, p + 24, 4);
+
+ nv_vgpu_info(ctx->vgpu,
+ "GSPRM: GSP_RM_ALLOC class=0x%x paramsSize=%u flags=0x%x hClient=0x%x hParent=0x%x hObject=0x%x",
+ class_id, params_size, flags, hc, hp, ho);
+
+ /* Full param dump for memory/channel classes and any class we have not
+ * classified yet (rate-limited). %phN is capped at 64 bytes per print,
+ * so chunk it. */
+ if (gsp_rm_alloc_interesting(class_id) && rm->alloc_param_dumps < 60) {
+ u32 shown = min(params_size, (u32)384);
+ u32 off;
+
+ rm->alloc_param_dumps++;
+ for (off = 0; off < shown; off += 64) {
+ u32 n = min(shown - off, (u32)64);
+
+ nv_vgpu_info(ctx->vgpu,
+ "GSPRM: alloc class=0x%x params[%u..%u]=%*phN",
+ class_id, off, off + n, (int)n,
+ params + off);
+ }
+ }
+
+ /* Echo the fixed alloc header + as many param bytes as are contiguous
+ * in the first queue element. */
+ echo_len = min(params_size,
+ (u32)GSPRM_MAX_SINGLE_ELEM_DATA - GSPRM_ALLOC_HDR_SIZE);
+ reply_len = GSPRM_ALLOC_HDR_SIZE + echo_len;
+
+ reply = kmalloc(reply_len, GFP_KERNEL);
+ if (!reply) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GSP_RM_ALLOC reply alloc failed");
+ return;
+ }
+
+ memcpy(reply, p, reply_len);
+ memset(reply + 16, 0, 4); /* status = NV_OK */
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_ALLOC, sequence, 0,
+ reply, reply_len))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GSP_RM_ALLOC reply send failed");
+
+ kfree(reply);
+}
+
static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
const u8 *elem, u32 sequence)
{
@@ -2200,6 +2305,10 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
gsp_rm_handle_get_static_info(ctx, sequence);
return;
+ case GSPRM_RPC_FUNC_GSP_RM_ALLOC:
+ gsp_rm_handle_rm_alloc(ctx, elem, sequence);
+ return;
+
case GSPRM_RPC_FUNC_GSP_RM_CONTROL:
gsp_rm_handle_rm_control(ctx, elem, sequence);
return;
@@ -2217,47 +2326,6 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u cmd_read=%u stat_write=%u stat_seq=%u",
gsp_rm_rpc_name(function), function, sequence,
rm->cmd_read, rm->stat_write, rm->stat_seq);
- } else if (function == GSPRM_RPC_FUNC_GSP_RM_ALLOC) {
- /* GSP_RM_ALLOC (103): rpc_gsp_rm_alloc_v03_00 at
- * elem+GSPRM_ELEM_HDR_SIZE. NOTE: the RPC header
- * 'length' only counts the fixed struct size (64), so
- * it must NOT be used to bound the params - read the
- * alloc header directly. See handoff PART 6. */
- const u8 *p = elem + GSPRM_ELEM_HDR_SIZE +
- sizeof(struct gsprm_rpc_header);
- const u8 *params = p + 32;
- u32 hc = 0, hp = 0, ho = 0, class_id = 0;
- u32 params_size = 0, flags = 0;
-
- memcpy(&hc, p + 0, 4);
- memcpy(&hp, p + 4, 4);
- memcpy(&ho, p + 8, 4);
- memcpy(&class_id, p + 12, 4);
- memcpy(&params_size, p + 20, 4);
- memcpy(&flags, p + 24, 4);
-
- nv_vgpu_info(ctx->vgpu,
- "GSPRM: GSP_RM_ALLOC class=0x%x paramsSize=%u flags=0x%x hClient=0x%x hParent=0x%x hObject=0x%x",
- class_id, params_size, flags, hc, hp, ho);
-
- /* Full param dump for memory/channel classes and any
- * class we have not classified yet (rate-limited).
- * %phN is capped at 64 bytes per print, so chunk it. */
- if (gsp_rm_alloc_interesting(class_id) &&
- rm->alloc_param_dumps < 60) {
- u32 shown = min(params_size, (u32)384);
- u32 off;
-
- rm->alloc_param_dumps++;
- for (off = 0; off < shown; off += 64) {
- u32 n = min(shown - off, (u32)64);
-
- nv_vgpu_info(ctx->vgpu,
- "GSPRM: alloc class=0x%x params[%u..%u]=%*phN",
- class_id, off, off + n, (int)n,
- params + off);
- }
- }
} 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,