summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 17:54:22 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 17:54:22 -0400
commit5b11ce14036abf540d8a0abcacf67c60d7591d8d (patch)
treef33d654fb6c19583f36d5a058bf917057cb30485
parent3d960c88f6167aad599a2921c0cf2fa635d0484d (diff)
downloadopen-iov-5b11ce14036abf540d8a0abcacf67c60d7591d8d.tar.gz
open-iov-5b11ce14036abf540d8a0abcacf67c60d7591d8d.zip
gsp-phys: rm-call diagnostics (failed-reply payload, RM log dump) and the static-config-handle ladder path
-rw-r--r--kernel/gsp_phys_booter.c438
-rw-r--r--kernel/gsp_phys_booter.h28
2 files changed, 420 insertions, 46 deletions
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;
}
@@ -4154,6 +4224,51 @@ static void gsp_phys_rmcall_dump_reply(const struct gsp_phys_rx_record *rec)
}
/*
+ * 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
* with gsp_phys_rx_record_free(); zeroed when nothing arrived). Aborts on the
@@ -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;
@@ -5094,6 +5284,157 @@ static const struct file_operations gsp_phys_state_fops = {
};
/* ============================================================
+ * 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