From 5b11ce14036abf540d8a0abcacf67c60d7591d8d Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 1 Oct 2026 17:54:22 -0400 Subject: gsp-phys: rm-call diagnostics (failed-reply payload, RM log dump) and the static-config-handle ladder path --- kernel/gsp_phys_booter.c | 438 ++++++++++++++++++++++++++++++++++++++++++----- kernel/gsp_phys_booter.h | 28 ++- 2 files changed, 420 insertions(+), 46 deletions(-) (limited to 'kernel') diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c index 59ac892..8c23abe 100644 --- a/kernel/gsp_phys_booter.c +++ b/kernel/gsp_phys_booter.c @@ -774,6 +774,15 @@ static struct gsp_phys_boot_stage { u32 rm_probe_last_function; /* last probe reply function (0 = none) */ u32 rm_probe_last_payload_len; /* last probe reply payload bytes */ u32 rm_probe_attempts; /* rm-probe triggers this boot */ + /* + * Server internal handles persisted from the last successful rm-probe + * static config in this session (pins 1600/1604/1608); stage 5 of the + * rm-call ladder adopts them. Cleared with the rest of boot_stage. + */ + u32 rm_probe_h_internal_client; + u32 rm_probe_h_internal_device; + u32 rm_probe_h_internal_subdevice; + bool rm_probe_internal_valid; /* the three fields were in the reply */ bool seq_dump_logged; /* 0x1002 hex/opcode dump done this boot */ bool preinit_submitted; /* fn 72/73 queued before the booter kick */ u32 tx_seq_num; /* next command-queue element seqNum */ @@ -784,7 +793,8 @@ static struct gsp_phys_boot_stage { * is written; stage persists across writes within one session. */ bool rmcall_ran; /* the ladder ran at least once */ - u32 rmcall_stage; /* highest stage completed (0..4) */ + u32 rmcall_stage; /* highest stage completed (0..4; 1 after + * an adopted stage 5) */ u32 rmcall_last_stage; /* stage the last run stopped in */ int rmcall_last_ret; /* last run status (0 = success) */ u32 rmcall_rpcs; /* RPCs submitted by the ladder */ @@ -3831,6 +3841,45 @@ static void gsp_phys_sci_decode(const u8 *p, u32 len) gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_GPU_UEFI_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_EFI_INIT_OFF), gsp_phys_sci_u8(p, len, GSP_PHYS_SCI_CL_NOSNOOP_WAR_OFF)); + + /* + * Persist the server's internal handles for this session so the + * rm-call ladder's stage 5 can adopt them. All three live inside the + * minimum static config (offsets 1600/1604/1608, min size 1656), but + * bound and require all three nonzero before marking them valid. + */ + boot_stage.rm_probe_h_internal_client = 0; + boot_stage.rm_probe_h_internal_device = 0; + boot_stage.rm_probe_h_internal_subdevice = 0; + boot_stage.rm_probe_internal_valid = false; + if (len >= GSP_PHYS_SCI_H_INTERNAL_SUBDEVICE_OFF + 4) { + u32 hc = gsp_phys_sci_u32(p, len, + GSP_PHYS_SCI_H_INTERNAL_CLIENT_OFF); + u32 hd = gsp_phys_sci_u32(p, len, + GSP_PHYS_SCI_H_INTERNAL_DEVICE_OFF); + u32 hs = gsp_phys_sci_u32(p, len, + GSP_PHYS_SCI_H_INTERNAL_SUBDEVICE_OFF); + + if (hc && hd && hs) { + boot_stage.rm_probe_h_internal_client = hc; + boot_stage.rm_probe_h_internal_device = hd; + boot_stage.rm_probe_h_internal_subdevice = hs; + boot_stage.rm_probe_internal_valid = true; + } + } +} + +/* + * Drop the persisted static-config internal handles. A re-probe (or a failed + * fn65 reply) must not leave a previous successful decode's handles marked + * valid for the rm-call stage-5 adoption. + */ +static void gsp_phys_rm_probe_internal_clear(void) +{ + boot_stage.rm_probe_h_internal_client = 0; + boot_stage.rm_probe_h_internal_device = 0; + boot_stage.rm_probe_h_internal_subdevice = 0; + boot_stage.rm_probe_internal_valid = false; } /* @@ -3889,6 +3938,7 @@ static int gsp_phys_rm_probe_fn65(void __iomem *bar0, bool after_fn1) if (rec.rpc_result != 0) { pr_err("gsp-phys-boot: rm-probe: GET_GSP_STATIC_INFO (65) server rpc_result=0x%x (not success); no decode\n", rec.rpc_result); + gsp_phys_rm_probe_internal_clear(); gsp_phys_rx_record_free(&rec); return -EIO; } @@ -3970,11 +4020,17 @@ static int gsp_phys_rm_probe_fn1(void __iomem *bar0) * caller gates on boot_stage.rm_init_done). At most one probe cycle per * write. */ -static int gsp_phys_rm_probe(void __iomem *bar0) +/* + * The probe cycle itself (no locking): SET_GUEST_SYSTEM_INFO then + * GET_GSP_STATIC_INFO and decode. Called by gsp_phys_rm_probe() under + * gsp_phys_rm_lock, and by rm-call stage 5, which already holds that lock, as + * the "same probe logic inline" prerequisite for adopting the static-config + * handles. + */ +static int gsp_phys_rm_probe_cycle(void __iomem *bar0) { int ret, fn1_ret; - mutex_lock(&gsp_phys_rm_lock); /* * Defensive re-check: GSP-RM does not service RM RPCs before * init-done, so sending here could be dropped. @@ -3982,9 +4038,14 @@ static int gsp_phys_rm_probe(void __iomem *bar0) if (!boot_stage.rm_init_done) { pr_info("gsp-phys-boot: rm-probe: no-op: GSP-RM init-done (function 0x%x, rpc_result 0) was not observed\n", GSP_PHYS_MSG_FUNC_GSP_INIT_DONE); - mutex_unlock(&gsp_phys_rm_lock); return -EINVAL; } + /* + * Clear any handles persisted by an earlier decode first: if this cycle + * fails (no reply or rpc_result != 0), a previous success must not stay + * "valid" for the rm-call stage-5 adoption. + */ + gsp_phys_rm_probe_internal_clear(); boot_stage.rm_probe_attempts++; fn1_ret = gsp_phys_rm_probe_fn1(bar0); if (fn1_ret) @@ -3993,6 +4054,15 @@ static int gsp_phys_rm_probe(void __iomem *bar0) ret = gsp_phys_rm_probe_fn65(bar0, true); boot_stage.rm_probe_done = true; boot_stage.rm_probe_last_ret = ret; + return ret; +} + +static int gsp_phys_rm_probe(void __iomem *bar0) +{ + int ret; + + mutex_lock(&gsp_phys_rm_lock); + ret = gsp_phys_rm_probe_cycle(bar0); mutex_unlock(&gsp_phys_rm_lock); return ret; } @@ -4153,6 +4223,51 @@ static void gsp_phys_rmcall_dump_reply(const struct gsp_phys_rx_record *rec) false); } +/* + * Bounded failure dump for a reply whose rpc_result != 0 (works for fn103: + * the live NV01_ROOT rejection carried rpc_result=0x23 with the interesting + * words in the echoed payload). For GSP_RM_ALLOC only, payload+0 is the + * request's hClient field (what the class-0 post-check looks up), +16 the + * server status and +32 params[0]; for the other ladder RPCs (ALLOC_MEMORY, + * GSP_RM_CONTROL) those offsets hold different fields, so only the raw words + * are labelled there. Then prints the first 64 payload bytes as compact u32 + * hex on one line. Every read is bounded by payload_len (absent words read as + * zero). Line lengths: labelled line <= ~125 columns, hex line 156 columns. + */ +static void +gsp_phys_rmcall_dump_failed_reply(const struct gsp_phys_rx_record *rec) +{ + char hex[16 * 8 + 1]; + u32 shown, words, i, pos = 0; + const char *tail = rec->payload_len >= 36 ? "" : + " (words beyond payload_len read as zero)"; + + if (!rec->buf) + return; + + shown = min_t(u32, 64u, rec->payload_len); + words = shown / 4; + + if (rec->function == GSP_PHYS_RMCALL_FUNC_GSP_RM_ALLOC) + pr_info("gsp-phys-boot: rm-call: failed reply (fn103) payload=%u: [0](request hClient)=0x%08x [16](status)=0x%08x [32](params[0])=0x%08x%s\n", + rec->payload_len, gsp_phys_rmcall_reply_u32(rec, 0), + gsp_phys_rmcall_reply_u32(rec, 16), + gsp_phys_rmcall_reply_u32(rec, 32), tail); + else + pr_info("gsp-phys-boot: rm-call: failed reply (fn%u) payload=%u: raw words [0]=0x%08x [16]=0x%08x [32]=0x%08x%s\n", + rec->function, rec->payload_len, + gsp_phys_rmcall_reply_u32(rec, 0), + gsp_phys_rmcall_reply_u32(rec, 16), + gsp_phys_rmcall_reply_u32(rec, 32), tail); + + hex[0] = '\0'; + for (i = 0; i < words; i++) + pos += scnprintf(hex + pos, sizeof(hex) - pos, "%08x", + gsp_phys_rmcall_reply_u32(rec, i * 4)); + if (pos) + pr_info("gsp-phys-boot: rm-call: hex=%s\n", hex); +} + /* * One ladder RPC: submit, then wait up to GSP_PHYS_RMCALL_TIMEOUT_MS for the * reply with the same function id. rec is always owned by the caller (freed @@ -4194,6 +4309,7 @@ static int gsp_phys_rmcall_rpc(void __iomem *bar0, u32 function, if (rec->rpc_result != 0) { pr_err("gsp-phys-boot: rm-call: fn%u server rpc_result=0x%x (not success)\n", function, rec->rpc_result); + gsp_phys_rmcall_dump_failed_reply(rec); return -EIO; } if (rec->payload_len < reply_min) { @@ -4772,6 +4888,54 @@ static int gsp_phys_rmcall_stage4(void __iomem *bar0) return 0; } +/* + * Stage 5: adopt the server's internal client/device/subdevice handles from + * this session's decoded GspStaticConfigInfo instead of allocating our own + * NV01_ROOT/DEVICE/SUBDEVICE. If the rm-probe cycle has not produced them + * yet, run the same probe logic inline first (fn1 then fn65; already under + * gsp_phys_rm_lock). On success rmcall_client/device/subdevice hold the + * adopted handles and the run loop records stage 1 as satisfied, so a + * following write of 4 runs stages 2-4 against them. + */ +static int gsp_phys_rmcall_stage5(void __iomem *bar0) +{ + int ret; + + if (!boot_stage.rm_probe_done || !boot_stage.rm_probe_internal_valid) { + u32 tx_before = boot_stage.tx_seq_num; + + pr_info("gsp-phys-boot: rm-call: stage 5: no persisted static-config handles in this session; running the rm-probe cycle inline (fn1 then fn65)\n"); + ret = gsp_phys_rm_probe_cycle(bar0); + /* Count the inline probe's submitted RPCs in the ladder total. */ + boot_stage.rmcall_rpcs += boot_stage.tx_seq_num - tx_before; + if (ret) { + pr_err("gsp-phys-boot: rm-call: stage 5: inline rm-probe failed (%d); cannot adopt internal handles\n", + ret); + return ret; + } + } + + if (!boot_stage.rm_probe_internal_valid || + !boot_stage.rm_probe_h_internal_client || + !boot_stage.rm_probe_h_internal_device || + !boot_stage.rm_probe_h_internal_subdevice) { + pr_err("gsp-phys-boot: rm-call: stage 5: static config carries no usable internal handles (valid=%u hClient=0x%x hDevice=0x%x hSubdevice=0x%x)\n", + boot_stage.rm_probe_internal_valid, + boot_stage.rm_probe_h_internal_client, + boot_stage.rm_probe_h_internal_device, + boot_stage.rm_probe_h_internal_subdevice); + return -EIO; + } + + boot_stage.rmcall_client = boot_stage.rm_probe_h_internal_client; + boot_stage.rmcall_device = boot_stage.rm_probe_h_internal_device; + boot_stage.rmcall_subdevice = boot_stage.rm_probe_h_internal_subdevice; + pr_info("gsp-phys-boot: rm-call: stage 5: adopted server internal handles: hClient=0x%x hDevice=0x%x hSubdevice=0x%x (stage 1 satisfied by adoption; write 4 to run stages 2-4 with them)\n", + boot_stage.rmcall_client, boot_stage.rmcall_device, + boot_stage.rmcall_subdevice); + return 0; +} + /* Serialised by gsp_phys_rm_lock; stage state persists across writes. */ static int gsp_phys_rmcall_run(void __iomem *bar0, u32 target) { @@ -4793,37 +4957,63 @@ static int gsp_phys_rmcall_run(void __iomem *bar0, u32 target) return -EINVAL; } - for (stage = boot_stage.rmcall_stage + 1; stage <= target; stage++) { - boot_stage.rmcall_last_stage = stage; - switch (stage) { - case 1: - ret = gsp_phys_rmcall_stage1(bar0); - break; - case 2: - ret = gsp_phys_rmcall_stage2(bar0); - break; - case 3: - ret = gsp_phys_rmcall_stage3(bar0); - break; - case 4: - ret = gsp_phys_rmcall_stage4(bar0); - break; - default: - ret = -EINVAL; - break; + if (target == 5) { + /* + * Stage 5 is not a cumulative ladder step: it substitutes the + * stage-1 handles. Only valid on a fresh ladder (before any + * real alloc or adoption). + */ + boot_stage.rmcall_last_stage = 5; + if (boot_stage.rmcall_stage >= 1) { + pr_info("gsp-phys-boot: rm-call: stage 5 skipped: stage 1 is already satisfied (hClient=0x%x hDevice=0x%x hSubdevice=0x%x); write 2/3/4 for the remaining stages\n", + boot_stage.rmcall_client, + boot_stage.rmcall_device, + boot_stage.rmcall_subdevice); + } else { + ret = gsp_phys_rmcall_stage5(bar0); + if (ret) { + pr_err("gsp-phys-boot: rm-call: ladder aborted in stage 5 (ret %d, %u RPCs so far); no object teardown (UNLOAD + rmmod remains the fail-safe)\n", + ret, boot_stage.rmcall_rpcs); + } else { + boot_stage.rmcall_stage = 1; /* stage 1 satisfied */ + pr_info("gsp-phys-boot: rm-call: stage 5 (adopt) complete (%u RPCs so far); stage 2/3/4 will use the adopted handles\n", + boot_stage.rmcall_rpcs); + } } - if (ret) { - pr_err("gsp-phys-boot: rm-call: ladder aborted in stage %u (ret %d, %u RPCs so far); no object teardown (UNLOAD + rmmod remains the fail-safe)\n", - stage, ret, boot_stage.rmcall_rpcs); - break; + } else { + for (stage = boot_stage.rmcall_stage + 1; stage <= target; stage++) { + boot_stage.rmcall_last_stage = stage; + switch (stage) { + case 1: + ret = gsp_phys_rmcall_stage1(bar0); + break; + case 2: + ret = gsp_phys_rmcall_stage2(bar0); + break; + case 3: + ret = gsp_phys_rmcall_stage3(bar0); + break; + case 4: + ret = gsp_phys_rmcall_stage4(bar0); + break; + default: + ret = -EINVAL; + break; + } + if (ret) { + pr_err("gsp-phys-boot: rm-call: ladder aborted in stage %u (ret %d, %u RPCs so far); no object teardown (UNLOAD + rmmod remains the fail-safe)\n", + stage, ret, boot_stage.rmcall_rpcs); + break; + } + boot_stage.rmcall_stage = stage; + pr_info("gsp-phys-boot: rm-call: stage %u complete (%u RPCs so far)\n", + stage, boot_stage.rmcall_rpcs); } - boot_stage.rmcall_stage = stage; - pr_info("gsp-phys-boot: rm-call: stage %u complete (%u RPCs so far)\n", - stage, boot_stage.rmcall_rpcs); + if (!ret && target <= boot_stage.rmcall_stage) + pr_info("gsp-phys-boot: rm-call: ladder at stage %u (target %u; %u RPCs)\n", + boot_stage.rmcall_stage, target, + boot_stage.rmcall_rpcs); } - if (!ret && target <= boot_stage.rmcall_stage) - pr_info("gsp-phys-boot: rm-call: ladder at stage %u (target %u; %u RPCs)\n", - boot_stage.rmcall_stage, target, boot_stage.rmcall_rpcs); boot_stage.rmcall_ran = true; boot_stage.rmcall_last_ret = ret; @@ -5093,6 +5283,157 @@ static const struct file_operations gsp_phys_state_fops = { .release = single_release, }; +/* ============================================================ + * Debugfs log-buffer dump (read-only): gsp-phys-log-dump + * ============================================================ */ + +/* + * Read one 64-bit/32-bit word from a staged libos task-log chunk. Word and + * byte indices are chunk-relative and page-walked through the chunk helper so + * the per-page allocation fallback is handled. Read-only; nothing is written. + */ +static u64 gsp_phys_log_word(const struct gsp_phys_boot_chunk *c, u64 word) +{ + u64 byte = word * 8; + u32 page = byte / GSP_PHYS_PAGE_SIZE; + u32 off = byte % GSP_PHYS_PAGE_SIZE; + + return get_unaligned_le64( + (const u8 *)gsp_phys_chunk_page_va(c, page) + off); +} + +static u32 gsp_phys_log_u32(const struct gsp_phys_boot_chunk *c, u64 off) +{ + u32 page = off / GSP_PHYS_PAGE_SIZE; + u32 rem = off % GSP_PHYS_PAGE_SIZE; + + return get_unaligned_le32( + (const u8 *)gsp_phys_chunk_page_va(c, page) + rem); +} + +/* + * Raw hex view: 16 bytes per line, bounded to the allocated size and the + * first 64 KB of each buffer. This is the authoritative dump for offline + * decoding with the evidence tree's decode_logs.py. + */ +static void gsp_phys_log_dump_raw(struct seq_file *m, const char *tag, + const struct gsp_phys_boot_chunk *c) +{ + u64 size = min_t(u64, (u64)c->pages * GSP_PHYS_PAGE_SIZE, 64 * 1024); + u64 off; + u32 j; + + seq_printf(m, "%s-raw: %llu bytes (allocated %llu) VA=0x%lx dma=0x%llx\n", + tag, size, (u64)c->pages * GSP_PHYS_PAGE_SIZE, + (unsigned long)gsp_phys_chunk_page_va(c, 0), + (u64)gsp_phys_chunk_page_dma(c, 0)); + for (off = 0; off < size; off += 16) { + seq_printf(m, "%08x:", (u32)off); + for (j = 0; j < 16; j += 4) + seq_printf(m, " %08x", gsp_phys_log_u32(c, off + j)); + seq_puts(m, "\n"); + } +} + +/* + * Decoded view matching the known libos record format (see decode_logs.py): word[0] is the put pointer counting u64 + * words, records live at words[1 + (i % ring_entries)] and are laid out + * {args..., metadata_ptr, timestamp}; timestamp-like words are in + * 0x18d0..0x18e0 << 48. Prints at most the last 60 such records per buffer, + * read-only and bounded by the ring size. + */ +#define GSP_PHYS_LOG_TS_LO 0x18d0000000000000ULL +#define GSP_PHYS_LOG_TS_HI 0x18e0000000000000ULL +#define GSP_PHYS_LOG_TS_MAX 60u + +static void gsp_phys_log_dump_decoded(struct seq_file *m, const char *tag, + const struct gsp_phys_boot_chunk *c) +{ + u64 n_words = ((u64)c->pages * GSP_PHYS_PAGE_SIZE) / 8; + u64 entries = n_words > 1 ? n_words - 1 : 0; + u64 put, start, i, total = 0, seen = 0, prev_ts = 0; + bool have_prev = false; + + if (!entries) { + seq_printf(m, "%s-decoded: empty\n", tag); + return; + } + put = gsp_phys_log_word(c, 0); + start = put > entries ? put - entries : 0; + seq_printf(m, "%s-decoded: put=%llu words=%llu ring_entries=%llu (last <=%u timestamp-like records)\n", + tag, put, n_words, entries, GSP_PHYS_LOG_TS_MAX); + + for (i = start; i < put; i++) { + u64 w = gsp_phys_log_word(c, 1 + (i % entries)); + + if (w >= GSP_PHYS_LOG_TS_LO && w < GSP_PHYS_LOG_TS_HI) + total++; + } + for (i = start; i < put; i++) { + u64 w = gsp_phys_log_word(c, 1 + (i % entries)); + u64 lo, meta, j; + + if (w < GSP_PHYS_LOG_TS_LO || w >= GSP_PHYS_LOG_TS_HI) + continue; + seen++; + if (seen + GSP_PHYS_LOG_TS_MAX <= total) { + /* + * Not in the last-GSP_PHYS_LOG_TS_MAX set: remember it + * as the boundary for the next printed record but do not + * mark a printed predecessor yet (the first printed + * record falls back to the 12-word window, as + * decode_logs.py does). + */ + prev_ts = i; + continue; + } + lo = have_prev ? prev_ts + 1 : (i >= 12 ? i - 12 : 0); + meta = i > lo ? + gsp_phys_log_word(c, 1 + ((i - 1) % entries)) : 0; + seq_printf(m, "rec %5llu ts=0x%016llx meta=0x%016llx args=", + i, w, meta); + for (j = lo; j + 1 < i; j++) + seq_printf(m, "%s0x%llx", j == lo ? "" : ",", + gsp_phys_log_word(c, 1 + (j % entries))); + seq_puts(m, "\n"); + prev_ts = i; + have_prev = true; + } +} + +static int gsp_phys_log_dump_show(struct seq_file *m, void *v) +{ + if (!gsp_phys_gpu_claimed() || !boot_stage.primed || + !boot_stage.login.pages || !boot_stage.logrm.pages) { + seq_printf(m, + "gsp-phys-log-dump: inactive: claimed=%u primed=%u login_pages=%u logrm_pages=%u; no log buffers to dump\n", + gsp_phys_gpu_claimed(), boot_stage.primed, + boot_stage.login.pages, boot_stage.logrm.pages); + return 0; + } + + seq_puts(m, + "gsp-phys-boot: libos task log buffers (read-only; raw hex is authoritative, decode offline with backups/evidence-*/decode_logs.py)\n"); + gsp_phys_log_dump_raw(m, "LOGINIT", &boot_stage.login); + gsp_phys_log_dump_raw(m, "LOGRM", &boot_stage.logrm); + gsp_phys_log_dump_decoded(m, "LOGINIT", &boot_stage.login); + gsp_phys_log_dump_decoded(m, "LOGRM", &boot_stage.logrm); + return 0; +} + +static int gsp_phys_log_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_phys_log_dump_show, NULL); +} + +static const struct file_operations gsp_phys_log_dump_fops = { + .owner = THIS_MODULE, + .open = gsp_phys_log_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /* ============================================================ * Debugfs trigger (test-enabling; inert unless a live boot ran) * ============================================================ */ @@ -5231,11 +5572,14 @@ static const struct file_operations gsp_phys_rm_probe_fops = { /* * gsp-phys-rm-call (write-only, mode 0200): writing N (1..4) runs the host RM * client ladder cumulatively through stage N and remembers the reached stage - * for the rest of the session (write 4 after 2 only runs 3 and 4). Every - * other value or state (parameter off, GPU unclaimed, stage 3d not run, - * init-done not observed, unload already submitted, BAR0 unmapped) is a - * logged no-op. The whole ladder is serialised with the unload/probe triggers - * by gsp_phys_rm_lock; at most one run per write; WPR2 is never touched. + * for the rest of the session (write 4 after 2 only runs 3 and 4). Writing 5 + * adopts the server's internal static-config handles as the stage-1 + * equivalent (running the rm-probe cycle inline first when needed); a later + * write of 4 then runs stages 2-4 with the adopted handles. Every other value + * or state (parameter off, GPU unclaimed, stage 3d not run, init-done not + * observed, unload already submitted, BAR0 unmapped) is a logged no-op. The + * whole ladder is serialised with the unload/probe triggers by + * gsp_phys_rm_lock; at most one run per write; WPR2 is never touched. */ static ssize_t gsp_phys_rm_call_write(struct file *file, const char __user *ubuf, size_t len, @@ -5254,8 +5598,8 @@ static ssize_t gsp_phys_rm_call_write(struct file *file, if (kstrtoul(strim(buf), 0, &value)) return -EINVAL; - if (value < 1 || value > 4) { - pr_info("gsp-phys-boot: gsp-phys-rm-call: write ignored (write 1..4 to run stages 1..N)\n"); + if (value < 1 || value > 5) { + pr_info("gsp-phys-boot: gsp-phys-rm-call: write ignored (write 1..4 to run stages 1..N, or 5 to adopt the static-config handles)\n"); return len; } if (!gsp_phys_boot_enabled()) { @@ -5378,7 +5722,21 @@ int gsp_phys_boot_debugfs_init(void) err); return (int)err; } - pr_info("gsp-phys-boot: debugfs files /sys/kernel/debug/open-iov-vfio/{gsp-phys-state read-only, gsp-phys-unload-rpc write-only, gsp-phys-rm-probe write-only, gsp-phys-rm-call write-only} ready\n"); + file = debugfs_create_file("gsp-phys-log-dump", 0444, + gsp_phys_debugfs_root, NULL, + &gsp_phys_log_dump_fops); + if (IS_ERR_OR_NULL(file)) { + long err = IS_ERR(file) ? PTR_ERR(file) : -ENODEV; + + /* + * The log dump is diagnostic; the triggers and the state + * snapshot stay registered. + */ + pr_warn("gsp-phys-boot: debugfs file gsp-phys-log-dump creation failed: %ld (triggers and state snapshot stay available)\n", + err); + return (int)err; + } + pr_info("gsp-phys-boot: debugfs files /sys/kernel/debug/open-iov-vfio/{gsp-phys-state read-only, gsp-phys-log-dump read-only, gsp-phys-unload-rpc write-only, gsp-phys-rm-probe write-only, gsp-phys-rm-call write-only} ready\n"); return 0; } diff --git a/kernel/gsp_phys_booter.h b/kernel/gsp_phys_booter.h index 935777f..0902830 100644 --- a/kernel/gsp_phys_booter.h +++ b/kernel/gsp_phys_booter.h @@ -149,12 +149,28 @@ int gsp_phys_boot_rm_unload(void __iomem *bar0); * work-submit token (function 76); stage 4 schedules the channel, writes * the CE method block plus GPFIFO entry and USERD GP_PUT, rings the PF * channel doorbell and polls the sysmem semaphore for a real 4 KB - * sysmem-to-sysmem copy (function 76 + the CE doorbell path). Serialised - * with the other triggers by gsp_phys_rm_lock, per-RPC bounded - * (2000 ms), aborts at the first failure with no object teardown (UNLOAD - * + rmmod is the fail-safe), never touches WPR2, and is inert until the - * file is written. Ladder buffers are freed by - * gsp_phys_boot_staging_free() on release. + * sysmem-to-sysmem copy (function 76 + the CE doorbell path). Writing 5 + * is the alternative stage-1 path: it adopts the + * hInternalClient/hInternalDevice/hInternalSubdevice handles from the + * session's decoded GspStaticConfigInfo (running the fn1+fn65 probe cycle + * inline first when needed), marks stage 1 satisfied, and a following + * write of 4 runs stages 2-4 against the adopted handles. On a failed + * reply (rpc_result != 0, e.g. the fn103 NV01_ROOT rejection) the RPC + * helper logs the payload words at +0/+16/+32 and the first 64 payload + * bytes in bounded hex. Serialised with the other triggers by + * gsp_phys_rm_lock, per-RPC bounded (2000 ms), aborts at the first + * failure with no object teardown (UNLOAD + rmmod is the fail-safe), + * never touches WPR2, and is inert until the file is written. Ladder + * buffers are freed by gsp_phys_boot_staging_free() on release. + * - "gsp-phys-log-dump" (read-only, mode 0444): dumps the LOGINIT and + * LOGRM libos task-log host buffers allocated at stage-3d prime. Per + * buffer: a one-line header (name, allocated size, kernel VA, DMA + * address), the raw content as "%08x: %08x ..." hex lines (bounded to + * the allocated size and 64 KB per buffer; authoritative for offline + * decoding with the evidence tree's decode_logs.py) and a decoded view + * derived from the known libos record format (word[0]=put pointer, + * records {args..., metadata_ptr, timestamp}, last <=60 timestamp-like + * records). Read-only: no buffer byte is modified. * - "gsp-phys-state" (read-only, mode 0444): seq_file snapshot of the * claimed/booted state (falcon registers, scratch14, both queue TX * headers, a bounded read-only status-ring walk, the stage flags and the -- cgit v1.2.3