summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_phys_booter.c
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 08:07:10 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-02 08:07:10 -0400
commit83ac7fdca1f9b0b7b6507819f8db508eb7852622 (patch)
tree7fbdb777f1c0d1711aad4a610e5ec77bebd47d36 /kernel/gsp_phys_booter.c
parent0a9887b1ff8c5546d6b2b33bd84248a9e02c8b6b (diff)
downloadopen-iov-83ac7fdca1f9b0b7b6507819f8db508eb7852622.tar.gz
open-iov-83ac7fdca1f9b0b7b6507819f8db508eb7852622.zip
gsp-phys: P6c C3 - relay forwarded-engine faults to the guest (default off)
New default-off gsp_phys_fault_relay: when a relayed copy times out, the GSP-RM status queue is drained and the first OS_ERROR_LOG (0x1006) recorded after the submission's doorbell is decoded (NOCAT corroboration), matched against the ladder channel's runlist/chid, and delivered to the guest by writing its channel notifier (info32=exceptType, info16=engine type, status=0xffff) and posting the channel event. A late-release recheck skips delivery if the engine actually completed, one delivery per hold, and the existing software fallback always runs first, so no release or data is lost. - pure decode helpers extracted from the status printers; prints unchanged - status-queue walks serialized with a mutex; unmatched 0x1006 records are no longer silently dropped - fault epoch snapshot at the doorbell prevents stale records from matching - fault counters and faults:/fault_relay: state lines - default-off gsp_phys_fault_inject debug knob forces the next relayed pair's source PTE to zero to produce a real engine fault - pump notifier latch prevents clobbering a delivered fault Live: injection run produced a real CE0 FAULT_PTE (0x1f @0x200000), decoded with errString, chid/runlist matched, notifier delivered, software fallback applied (applied+fallback==engaged), clean release; the guest RM reacted to the delivered fault by tearing down its channels (faithful semantics). Healthy control: faults all zero, relay requested=40 ok=40, smoke matched=64/64, clean release. Caveat: the auxiliary event post failed (no_event=1, no MSI-X) and the guest teardown means fault injection is not smoke-compatible; the parameter stays off.
Diffstat (limited to 'kernel/gsp_phys_booter.c')
-rw-r--r--kernel/gsp_phys_booter.c539
1 files changed, 497 insertions, 42 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 5c36c35..a033022 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -2538,6 +2538,48 @@ static void gsp_phys_rx_record_free(struct gsp_phys_rx_record *rec)
}
/*
+ * P6c C3 fault observation: every status-queue walk (the wait, both drain
+ * variants and the stage-3e poll) shares the persisted read pointer, so all
+ * of them serialize on this mutex. gsp_phys_fault_epoch advances whenever a
+ * 0x1006 record is consumed; the relay submit snapshots it immediately before
+ * the doorbell (gsp_phys_fault_submit_epoch) so a fault older than the
+ * submission can never be relayed. The last-4 rings keep the decoded 0x1006
+ * records and the last 0x1020 NOCAT records for corroboration and for the
+ * debugfs state line.
+ */
+static DEFINE_MUTEX(gsp_phys_status_walk_lock);
+
+#define GSP_PHYS_FAULT_NOTE_MAX 4
+#define GSP_PHYS_FAULT_ENGINE_MAX 64
+
+struct gsp_phys_fault_note {
+ bool valid;
+ u32 rec_type;
+ u64 error_code;
+ u32 subsystem;
+ u32 seq;
+ char engine[GSP_PHYS_FAULT_ENGINE_MAX];
+};
+
+static struct gsp_phys_fault_info
+ gsp_phys_fault_notes[GSP_PHYS_FAULT_NOTE_MAX];
+static u32 gsp_phys_fault_note_next;
+static struct gsp_phys_fault_note
+ gsp_phys_nocat_notes[GSP_PHYS_FAULT_NOTE_MAX];
+static u32 gsp_phys_nocat_note_next;
+static u32 gsp_phys_fault_epoch;
+static u32 gsp_phys_fault_submit_epoch;
+static u64 gsp_phys_fault_seen;
+static u64 gsp_phys_fault_stale;
+static u64 gsp_phys_fault_drained;
+static u64 gsp_phys_fault_wait_unmatched;
+
+static void gsp_phys_fault_observe(const struct gsp_phys_rx_record *rec,
+ struct gsp_phys_fault_info *out);
+static void gsp_phys_nocat_note(const struct gsp_phys_rx_record *rec);
+static void gsp_phys_fault_corroborate(struct gsp_phys_fault_info *out);
+
+/*
* GSP-RM NOCAT journal records (status-queue function 0x1020,
* NV_VGPU_MSG_EVENT_GSP_POST_NOCAT_RECORD). The RPC payload is the reference
* NV2080CtrlNocatJournalInsertRecord (ctrl2080nvd.h): a u64 timestamp, then
@@ -2938,11 +2980,13 @@ static int gsp_phys_status_queue_next(struct gsp_phys_status_queue *sq,
* probe uses match_sequence=false; the parameter covers variants that carry
* a meaningful rpc.sequence. Returns 0 with rec filled on a match,
* -ETIMEDOUT on the bound, or a negative transport error. The caller owns
- * rec->buf.
+ * rec->buf. The walk holds the status-walk mutex for its whole duration, so
+ * it can never race the drains over the shared read pointer.
*/
-static int gsp_phys_status_wait(struct gsp_phys_status_queue *sq,
- u32 function, bool match_sequence, u32 sequence,
- u32 timeout_ms, struct gsp_phys_rx_record *rec)
+static int gsp_phys_status_wait_locked(struct gsp_phys_status_queue *sq,
+ u32 function, bool match_sequence,
+ u32 sequence, u32 timeout_ms,
+ struct gsp_phys_rx_record *rec)
{
unsigned long deadline = jiffies + msecs_to_jiffies(timeout_ms);
int ret;
@@ -2976,23 +3020,50 @@ static int gsp_phys_status_wait(struct gsp_phys_status_queue *sq,
* Matched records are logged by the caller, not here. Unmatched
* async events are consumed (readPtr advances) and must not be
* dropped silently: NOCAT journals feed the bounded unique
- * summary; everything else is debug-only (no unbounded logs).
+ * summary and the C3 corroboration ring; a 0x1006 is decoded
+ * and noted in the bounded last-4 fault ring and counted;
+ * everything else stays debug-only (no unbounded logs).
*/
if (one.function == function &&
(!match_sequence || one.rpc_sequence == sequence)) {
+ if (one.function == GSP_PHYS_MSG_FUNC_OS_ERROR_LOG) {
+ struct gsp_phys_fault_info fi;
+
+ gsp_phys_fault_observe(&one, &fi);
+ }
*rec = one;
return 0;
}
- if (one.function == GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD)
+ if (one.function == GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD) {
+ gsp_phys_nocat_note(&one);
gsp_phys_nocat_record(&one);
- else
+ } else if (one.function == GSP_PHYS_MSG_FUNC_OS_ERROR_LOG) {
+ struct gsp_phys_fault_info fi;
+
+ gsp_phys_fault_observe(&one, &fi);
+ gsp_phys_fault_wait_unmatched++;
+ } else {
pr_debug("gsp-phys-boot: status wait: dropping unmatched record slot %u (seqNum=%u rpc.len=%u checksum=0x%08x): function=0x%x rpc_result=%u\n",
one.first_slot, one.seq_num, one.rpc_length,
one.checksum, one.function, one.rpc_result);
+ }
gsp_phys_rx_record_free(&one);
}
}
+static int gsp_phys_status_wait(struct gsp_phys_status_queue *sq,
+ u32 function, bool match_sequence, u32 sequence,
+ u32 timeout_ms, struct gsp_phys_rx_record *rec)
+{
+ int ret;
+
+ mutex_lock(&gsp_phys_status_walk_lock);
+ ret = gsp_phys_status_wait_locked(sq, function, match_sequence,
+ sequence, timeout_ms, rec);
+ mutex_unlock(&gsp_phys_status_walk_lock);
+ return ret;
+}
+
/*
* Publish the host's consumed status-queue read pointer (a ring slot index)
* into the command-queue RX header. With MSGQ_FLAGS_SWAP_RX on both queues the
@@ -3080,43 +3151,227 @@ static void gsp_phys_status_emit(struct seq_file *m, const char *fmt, ...)
seq_printf(m, "gsp-phys-status: %s\n", line);
}
-static void gsp_phys_status_os_error_log(struct seq_file *m,
- const struct gsp_phys_rx_record *rec)
+/*
+ * Pure 0x1006 decode: every field read is bounded by the received payload and
+ * the errString is copied into a terminated buffer. Used by the print decoder
+ * and by the C3 fault observation path; it never prints.
+ */
+static void gsp_phys_oserr_decode(const struct gsp_phys_rx_record *rec,
+ struct gsp_phys_fault_info *out)
{
const u8 *p = rec->buf + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
u32 len = rec->payload_len;
- char err[GSP_PHYS_OSERR_LEN_ERRSTRING];
- u32 except_type = 0, runlist_id = 0, chid = 0, prev_xid = 0;
- bool have_prev;
+ memset(out, 0, sizeof(*out));
if (len > rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF)
len = rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
+ out->valid = true;
+ out->function = rec->function;
if (len >= GSP_PHYS_OSERR_OFF_EXCEPTTYPE + 4)
- except_type = get_unaligned_le32(p +
- GSP_PHYS_OSERR_OFF_EXCEPTTYPE);
+ out->except_type = get_unaligned_le32(p +
+ GSP_PHYS_OSERR_OFF_EXCEPTTYPE);
if (len >= GSP_PHYS_OSERR_OFF_RUNLISTID + 4)
- runlist_id = get_unaligned_le32(p +
- GSP_PHYS_OSERR_OFF_RUNLISTID);
+ out->runlist_id = get_unaligned_le32(p +
+ GSP_PHYS_OSERR_OFF_RUNLISTID);
if (len >= GSP_PHYS_OSERR_OFF_CHID + 4)
- chid = get_unaligned_le32(p + GSP_PHYS_OSERR_OFF_CHID);
- have_prev = len >= GSP_PHYS_OSERR_OFF_PREVXID + 4;
- if (have_prev)
- prev_xid = get_unaligned_le32(p + GSP_PHYS_OSERR_OFF_PREVXID);
- gsp_phys_nocat_copy_str(err, sizeof(err), p, len,
+ out->chid = get_unaligned_le32(p + GSP_PHYS_OSERR_OFF_CHID);
+ if (len >= GSP_PHYS_OSERR_OFF_PREVXID + 4)
+ out->prev_xid = get_unaligned_le32(p +
+ GSP_PHYS_OSERR_OFF_PREVXID);
+ gsp_phys_nocat_copy_str(out->err, sizeof(out->err), p, len,
GSP_PHYS_OSERR_OFF_ERRSTRING,
GSP_PHYS_OSERR_LEN_ERRSTRING);
+}
+
+/*
+ * Pure 0x1020 decode (numeric fields and the faultingEngine string). The
+ * caller zeroes *out; only the NOCAT members are written.
+ */
+static void gsp_phys_nocat_decode(const struct gsp_phys_rx_record *rec,
+ struct gsp_phys_fault_info *out)
+{
+ const u8 *p = rec->buf + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
+ u32 len = rec->payload_len;
+
+ if (len > rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF)
+ len = rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
+ if (len > GSP_PHYS_NOCAT_OFF_RECTYPE)
+ out->nocat_rec_type = p[GSP_PHYS_NOCAT_OFF_RECTYPE];
+ if (len >= GSP_PHYS_NOCAT_OFF_SUBSYSTEM + 4)
+ out->nocat_subsystem = get_unaligned_le32(p +
+ GSP_PHYS_NOCAT_OFF_SUBSYSTEM);
+ if (len >= GSP_PHYS_NOCAT_OFF_ERRORCODE + 8)
+ out->nocat_error_code = get_unaligned_le64(p +
+ GSP_PHYS_NOCAT_OFF_ERRORCODE);
+ gsp_phys_nocat_copy_str(out->nocat_engine, sizeof(out->nocat_engine),
+ p, len, GSP_PHYS_NOCAT_OFF_FAULTENGINE,
+ GSP_PHYS_NOCAT_LEN_SOURCE);
+}
+
+/*
+ * The 0x1006 v17_00 carries no engineType, so the guest-visible engine is
+ * recovered from the errString/NOCAT text. NV2080 engine values:
+ * GR0/GRAPHICS=1, COPYn=9+n (n<10) / 0x34+(n-10), plus the common fixed
+ * engines. An unknown name yields 0 and the delivery keeps the channel's own
+ * engine type instead.
+ */
+static u32 gsp_phys_engine_type_from_name(const char *name)
+{
+ const char *p = name;
+ u32 num = 0;
+ bool have_idx = false;
+
+ while (*p == ' ')
+ p++;
+ if (!strncasecmp(p, "COPY", 4)) {
+ p += 4;
+ } else if (!strncasecmp(p, "CE", 2) && p[2] >= '0' && p[2] <= '9') {
+ p += 2;
+ } else if (!strncasecmp(p, "GR", 2)) {
+ return 1; /* NV2080_ENGINE_TYPE_GRAPHICS / GR0 */
+ } else if (!strncasecmp(p, "HOST", 4)) {
+ return 0x27; /* NV2080_ENGINE_TYPE_HOST */
+ } else if (!strncasecmp(p, "VIC", 3)) {
+ return 0x24; /* NV2080_ENGINE_TYPE_VIC */
+ } else if (!strncasecmp(p, "SEC2", 4)) {
+ return 0x26; /* NV2080_ENGINE_TYPE_SEC2 */
+ } else if (!strncasecmp(p, "NVDEC", 5)) {
+ return 0x13; /* NV2080_ENGINE_TYPE_NVDEC0 / BSP */
+ } else if (!strncasecmp(p, "NVENC", 5) ||
+ !strncasecmp(p, "MSENC", 5)) {
+ return 0x1b; /* NV2080_ENGINE_TYPE_NVENC0 / MSENC */
+ } else if (!strncasecmp(p, "PMU", 3)) {
+ return 0x29; /* NV2080_ENGINE_TYPE_PMU */
+ } else if (!strncasecmp(p, "FBFLCN", 6)) {
+ return 0x2a; /* NV2080_ENGINE_TYPE_FBFLCN */
+ } else if (!strncasecmp(p, "SW", 2)) {
+ return 0x22; /* NV2080_ENGINE_TYPE_SW */
+ } else {
+ return 0;
+ }
+ while (*p >= '0' && *p <= '9') {
+ have_idx = true;
+ num = num * 10 + (u32)(*p - '0');
+ p++;
+ }
+ if (!have_idx || num >= 64)
+ num = 0;
+ if (num < 10)
+ return 9 + num; /* NV2080_ENGINE_TYPE_COPY0..COPY9 */
+ return 0x34 + (num - 10); /* NV2080_ENGINE_TYPE_COPY10.. */
+}
+
+/* Recover the engine from the 0x1006 errString's "ENGINE <name>" token. */
+static u32 gsp_phys_engine_type_from_errstr(const char *err)
+{
+ const char *p = strstr(err, "ENGINE ");
+
+ if (!p)
+ return 0;
+ return gsp_phys_engine_type_from_name(p + 7);
+}
+
+/*
+ * Note every consumed 0x1006 in the bounded last-4 ring, advance the fault
+ * epoch (the relay submit snapshot compares against it) and log the designed
+ * observation line when the fault relay is armed. Decoding and counting are
+ * unconditional so an unmatched record is never dropped silently; with the
+ * parameter off the log stays silent, keeping the default dmesg identical.
+ */
+static void gsp_phys_fault_observe(const struct gsp_phys_rx_record *rec,
+ struct gsp_phys_fault_info *out)
+{
+ gsp_phys_oserr_decode(rec, out);
+ gsp_phys_fault_notes[gsp_phys_fault_note_next] = *out;
+ gsp_phys_fault_note_next = (gsp_phys_fault_note_next + 1) %
+ GSP_PHYS_FAULT_NOTE_MAX;
+ gsp_phys_fault_seen++;
+ gsp_phys_fault_epoch++;
+ if (gsp_phys_fault_relay_enabled())
+ pr_info_ratelimited("gsp-phys-fault: func=0x1006 seq=%u exceptType=0x%x runlistId=%u chid=%u prevXid=0x%x errString=\"%s\"\n",
+ rec->seq_num, out->except_type, out->runlist_id,
+ out->chid, out->prev_xid, out->err);
+}
+
+/* Note every consumed 0x1020 for the C3 corroboration ring. */
+static void gsp_phys_nocat_note(const struct gsp_phys_rx_record *rec)
+{
+ struct gsp_phys_fault_info fi;
+ struct gsp_phys_fault_note *note =
+ &gsp_phys_nocat_notes[gsp_phys_nocat_note_next];
+
+ memset(&fi, 0, sizeof(fi));
+ gsp_phys_nocat_decode(rec, &fi);
+ note->valid = true;
+ note->rec_type = fi.nocat_rec_type;
+ note->error_code = fi.nocat_error_code;
+ note->subsystem = fi.nocat_subsystem;
+ note->seq = rec->seq_num;
+ memcpy(note->engine, fi.nocat_engine, sizeof(note->engine));
+ gsp_phys_nocat_note_next = (gsp_phys_nocat_note_next + 1) %
+ GSP_PHYS_FAULT_NOTE_MAX;
+}
+/*
+ * Corroborate a decoded 0x1006 with the newest NOCAT whose errorCode equals
+ * its exceptType (the observed pairing) and derive the engine type from the
+ * NOCAT faultingEngine text, falling back to the errString token. A missing
+ * NOCAT leaves the NOCAT members zero; the fault itself stays valid.
+ */
+static void gsp_phys_fault_corroborate(struct gsp_phys_fault_info *out)
+{
+ const struct gsp_phys_fault_note *best = NULL;
+ u32 i;
+
+ for (i = 0; i < GSP_PHYS_FAULT_NOTE_MAX; i++) {
+ const struct gsp_phys_fault_note *n =
+ &gsp_phys_nocat_notes[(gsp_phys_nocat_note_next +
+ GSP_PHYS_FAULT_NOTE_MAX - 1 - i) %
+ GSP_PHYS_FAULT_NOTE_MAX];
+
+ if (!n->valid)
+ continue;
+ if (n->error_code == out->except_type) {
+ best = n;
+ break;
+ }
+ }
+ if (best) {
+ out->nocat_rec_type = best->rec_type;
+ out->nocat_error_code = best->error_code;
+ out->nocat_subsystem = best->subsystem;
+ memcpy(out->nocat_engine, best->engine,
+ sizeof(out->nocat_engine));
+ out->engine = gsp_phys_engine_type_from_name(best->engine);
+ }
+ if (!out->engine)
+ out->engine = gsp_phys_engine_type_from_errstr(out->err);
+}
+
+static void gsp_phys_status_os_error_log(struct seq_file *m,
+ const struct gsp_phys_rx_record *rec)
+{
+ u32 len = rec->payload_len;
+ struct gsp_phys_fault_info fi;
+ bool have_prev;
+
+ gsp_phys_oserr_decode(rec, &fi);
+
+ if (len > rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF)
+ len = rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
+ have_prev = len >= GSP_PHYS_OSERR_OFF_PREVXID + 4;
if (have_prev)
gsp_phys_status_emit(m,
"func=0x1006 status=%u seq=%u elem=%u slot=%u exceptType=0x%x runlistId=%u chid=%u prevXid=0x%x errString=\"%s\"",
rec->rpc_result, rec->seq_num, rec->elem_count,
- rec->first_slot, except_type, runlist_id, chid,
- prev_xid, err);
+ rec->first_slot, fi.except_type, fi.runlist_id, fi.chid,
+ fi.prev_xid, fi.err);
else
gsp_phys_status_emit(m,
"func=0x1006 status=%u seq=%u elem=%u slot=%u exceptType=0x%x runlistId=%u chid=%u errString=\"%s\"",
rec->rpc_result, rec->seq_num, rec->elem_count,
- rec->first_slot, except_type, runlist_id, chid, err);
+ rec->first_slot, fi.except_type, fi.runlist_id, fi.chid,
+ fi.err);
}
/*
@@ -3130,25 +3385,18 @@ static void gsp_phys_status_nocat_record(struct seq_file *m,
{
const u8 *p = rec->buf + GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
u32 len = rec->payload_len;
+ struct gsp_phys_fault_info fi;
char engine[GSP_PHYS_NOCAT_LEN_SOURCE];
char diag[GSP_PHYS_STATUS_DIAG_MAX + 1];
- u8 rec_type = 0;
- u32 subsystem = 0, bugcheck = 0, diag_len = 0, diag_avail = 0, i;
- u64 error_code = 0;
+ u32 bugcheck = 0, diag_len = 0, diag_avail = 0, i;
+ memset(&fi, 0, sizeof(fi));
+ gsp_phys_nocat_decode(rec, &fi);
if (len > rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF)
len = rec->buf_len - GSP_PHYS_MSG_ELEMENT_PAYLOAD_OFF;
- if (len > GSP_PHYS_NOCAT_OFF_RECTYPE)
- rec_type = p[GSP_PHYS_NOCAT_OFF_RECTYPE];
if (len >= GSP_PHYS_NOCAT_OFF_BUGCHECK + 4)
bugcheck = get_unaligned_le32(p +
GSP_PHYS_NOCAT_OFF_BUGCHECK);
- if (len >= GSP_PHYS_NOCAT_OFF_SUBSYSTEM + 4)
- subsystem = get_unaligned_le32(p +
- GSP_PHYS_NOCAT_OFF_SUBSYSTEM);
- if (len >= GSP_PHYS_NOCAT_OFF_ERRORCODE + 8)
- error_code = get_unaligned_le64(p +
- GSP_PHYS_NOCAT_OFF_ERRORCODE);
if (len >= GSP_PHYS_NOCAT_OFF_DIAGBUFLEN + 4)
diag_len = get_unaligned_le32(p +
GSP_PHYS_NOCAT_OFF_DIAGBUFLEN);
@@ -3164,6 +3412,10 @@ static void gsp_phys_status_nocat_record(struct seq_file *m,
diag[i] = (c >= 0x20 && c < 0x7f) ? (char)c : '.';
}
diag[diag_avail] = '\0';
+ /*
+ * Keep the print's own 65-byte engine copy: the shared decoder stores
+ * 64 bytes, and the existing output must stay byte-for-byte identical.
+ */
gsp_phys_nocat_copy_str(engine, sizeof(engine), p, len,
GSP_PHYS_NOCAT_OFF_FAULTENGINE,
GSP_PHYS_NOCAT_LEN_SOURCE);
@@ -3171,8 +3423,9 @@ static void gsp_phys_status_nocat_record(struct seq_file *m,
gsp_phys_status_emit(m,
"func=0x1020 status=%u seq=%u elem=%u slot=%u recType=%u errorCode=0x%llx subsystem=0x%x bugcheck=0x%x diagLen=%u printed=%u faultingEngine=\"%s\" diag=\"%s\"",
rec->rpc_result, rec->seq_num, rec->elem_count,
- rec->first_slot, rec_type, error_code, subsystem, bugcheck,
- diag_len, diag_avail, engine, diag);
+ rec->first_slot, fi.nocat_rec_type, fi.nocat_error_code,
+ fi.nocat_subsystem, bugcheck, diag_len, diag_avail, engine,
+ diag);
}
/* Any other function: one compact header line. */
@@ -3191,9 +3444,10 @@ static void gsp_phys_status_generic(struct seq_file *m,
* completed (possibly empty or truncated by the bound), -EINVAL when the queue
* was never staged/validated and a negative transport error otherwise. The
* read pointer is published per consumed record by the ring walk itself, so an
- * interrupted walk still accounts for everything it consumed.
+ * interrupted walk still accounts for everything it consumed. Shared with the
+ * C3 fault drain through the status-walk mutex.
*/
-static int gsp_phys_status_drain(struct seq_file *m)
+static int gsp_phys_status_drain_locked(struct seq_file *m)
{
struct gsp_phys_status_queue sq;
struct gsp_phys_rx_record rec;
@@ -3232,10 +3486,15 @@ static int gsp_phys_status_drain(struct seq_file *m)
}
switch (rec.function) {
- case GSP_PHYS_MSG_FUNC_OS_ERROR_LOG:
+ case GSP_PHYS_MSG_FUNC_OS_ERROR_LOG: {
+ struct gsp_phys_fault_info fi;
+
+ gsp_phys_fault_observe(&rec, &fi);
gsp_phys_status_os_error_log(m, &rec);
break;
+ }
case GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD:
+ gsp_phys_nocat_note(&rec);
gsp_phys_status_nocat_record(m, &rec);
break;
default:
@@ -3253,6 +3512,79 @@ static int gsp_phys_status_drain(struct seq_file *m)
return 0;
}
+static int gsp_phys_status_drain(struct seq_file *m)
+{
+ int ret;
+
+ mutex_lock(&gsp_phys_status_walk_lock);
+ ret = gsp_phys_status_drain_locked(m);
+ mutex_unlock(&gsp_phys_status_walk_lock);
+ return ret;
+}
+
+/*
+ * C3 fault drain (see gsp_phys_booter.h): like the debugfs drain but returns
+ * the first decoded 0x1006 (with NOCAT corroboration) and the fault epoch
+ * captured when that record was consumed. The walk still consumes every
+ * record it steps over, so GSP-RM's status-ring free space is restored and a
+ * later walk cannot re-read them.
+ */
+int gsp_phys_status_drain_fault(struct gsp_phys_fault_info *out, u32 *epoch)
+{
+ struct gsp_phys_status_queue sq;
+ struct gsp_phys_rx_record rec;
+ u32 drained = 0, fault_epoch = 0;
+ int ret;
+
+ if (!out)
+ return -EINVAL;
+ memset(out, 0, sizeof(*out));
+ if (epoch)
+ *epoch = 0;
+
+ mutex_lock(&gsp_phys_status_walk_lock);
+ gsp_phys_status_queue_init(&sq);
+ ret = gsp_phys_status_queue_link(&sq, &boot_stage.queue);
+ if (ret) {
+ mutex_unlock(&gsp_phys_status_walk_lock);
+ return ret;
+ }
+
+ while (drained < GSP_PHYS_STATUS_DRAIN_MAX) {
+ ret = gsp_phys_status_queue_next(&sq, &rec);
+ if (ret == -EAGAIN) {
+ ret = 0;
+ break;
+ }
+ if (ret)
+ break;
+
+ if (rec.function == GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD) {
+ gsp_phys_nocat_note(&rec);
+ } else if (rec.function == GSP_PHYS_MSG_FUNC_OS_ERROR_LOG) {
+ if (!out->valid) {
+ gsp_phys_fault_observe(&rec, out);
+ fault_epoch = gsp_phys_fault_epoch;
+ } else {
+ struct gsp_phys_fault_info extra;
+
+ gsp_phys_fault_observe(&rec, &extra);
+ }
+ }
+ gsp_phys_rx_record_free(&rec);
+ drained++;
+ }
+ mutex_unlock(&gsp_phys_status_walk_lock);
+
+ if (out->valid) {
+ gsp_phys_fault_corroborate(out);
+ if (epoch)
+ *epoch = fault_epoch;
+ return 0;
+ }
+ return ret ? ret : -ENODATA;
+}
+
void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0,
resource_size_t bar0_len)
{
@@ -3300,8 +3632,16 @@ void gsp_phys_boot_rmwait(struct gsp_phys_boot *boot, void __iomem *bar0,
rec.elem_count, rec.function,
rec.rpc_result);
if (rec.function ==
- GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD)
+ GSP_PHYS_MSG_FUNC_GSP_POST_NOCAT_RECORD) {
+ gsp_phys_nocat_note(&rec);
gsp_phys_nocat_record(&rec);
+ }
+ if (rec.function ==
+ GSP_PHYS_MSG_FUNC_OS_ERROR_LOG) {
+ struct gsp_phys_fault_info fi;
+
+ gsp_phys_fault_observe(&rec, &fi);
+ }
if (rec.function ==
GSP_PHYS_MSG_FUNC_RUN_CPU_SEQUENCER) {
u32 seq_buffer = 0, seq_index = 0;
@@ -6336,6 +6676,12 @@ static int gsp_phys_rmcall_ce_submit_locked(void __iomem *bar0, u64 src_va,
if (doorbell != boot_stage.rmcall_token)
pr_warn("gsp-phys-boot: rm-call: token 0x%x carries bits outside runlistId[22:16]|chId[11:0]; writing the recomposed doorbell 0x%x\n",
boot_stage.rmcall_token, doorbell);
+ /*
+ * C3: snapshot the fault epoch immediately before the doorbell, so a
+ * 0x1006 consumed by this submission's timeout drain is accepted only
+ * when it is newer than the submission itself.
+ */
+ WRITE_ONCE(gsp_phys_fault_submit_epoch, READ_ONCE(gsp_phys_fault_epoch));
wmb();
writel(doorbell, bar0 + GSP_PHYS_RMCALL_DOORBELL_OFF);
@@ -6670,6 +7016,40 @@ void gsp_phys_backend_relay_counters(u64 *requested, u64 *ok, u64 *verify_fail,
EXPORT_SYMBOL_GPL(gsp_phys_backend_relay_counters);
/*
+ * C3: the ladder channel's runlistId/chid, decomposed from the work-submit
+ * token exactly like the doorbell (kernel_fifo_tu102.c:118-121), so the fault
+ * relay can require a 0x1006 that names this channel. False when no live
+ * ladder token exists.
+ */
+bool gsp_phys_backend_ladder_channel_ids(u32 *runlist_id, u32 *chid)
+{
+ if (!boot_stage.rmcall_token || boot_stage.rmcall_channel_server_mem)
+ return false;
+ if (runlist_id)
+ *runlist_id = (boot_stage.rmcall_token >> 16) & 0x7fu;
+ if (chid)
+ *chid = boot_stage.rmcall_token & 0xfffu;
+ return true;
+}
+EXPORT_SYMBOL_GPL(gsp_phys_backend_ladder_channel_ids);
+
+void gsp_phys_backend_fault_counters(u64 *seen, u64 *stale, u64 *drained,
+ u64 *wait_unmatched, u32 *epoch)
+{
+ if (seen)
+ *seen = gsp_phys_fault_seen;
+ if (stale)
+ *stale = gsp_phys_fault_stale;
+ if (drained)
+ *drained = gsp_phys_fault_drained;
+ if (wait_unmatched)
+ *wait_unmatched = gsp_phys_fault_wait_unmatched;
+ if (epoch)
+ *epoch = READ_ONCE(gsp_phys_fault_epoch);
+}
+EXPORT_SYMBOL_GPL(gsp_phys_backend_fault_counters);
+
+/*
* Allocate one fresh 128 B GMMU PTE fetch line from the guest scratch PT pool
* and write both operand PTEs into it (src at 16*line_in_page, dst at
* 16*line_in_page + 1). Called with gsp_phys_rm_lock held, so the monotonic
@@ -6715,7 +7095,14 @@ static int gsp_phys_rmcall_guest_map_locked(u64 src_hpa, u64 dst_hpa,
pt = gsp_phys_chunk_page_va(guest_pt_chunk, 0);
if (!pt)
return -EIO;
- put_unaligned_le64(gsp_phys_rmcall_pte(src_hpa),
+ /*
+ * C3 debug knob: only a relayed pair (nonzero sem_hpa) may force the
+ * source PTE to an invalid zero entry; every other copy and the
+ * fault_inject-off case program the resolved source HPA unchanged.
+ */
+ if (sem_hpa && gsp_phys_fault_inject_enabled())
+ src_hpa = 0;
+ put_unaligned_le64(src_hpa ? gsp_phys_rmcall_pte(src_hpa) : 0,
pt + (size_t)(GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES *
line_in_page) * 8);
put_unaligned_le64(gsp_phys_rmcall_pte(dst_hpa),
@@ -6740,6 +7127,20 @@ static int gsp_phys_rmcall_guest_map_locked(u64 src_hpa, u64 dst_hpa,
}
/*
+ * C3 use-time gate: the fault relay only exists when the fault parameter and
+ * every parameter its delivery depends on are set. All checked at use time so
+ * a runtime parameter flip is honoured.
+ */
+static bool gsp_phys_fault_relay_active(void)
+{
+ return gsp_phys_fault_relay_enabled() &&
+ gsp_phys_relay_enabled() &&
+ gsp_phys_work_forward_enabled() &&
+ gsp_phys_guest_mem_enabled() &&
+ gsp_phys_work_defer_release_enabled();
+}
+
+/*
* Shared body of the P6b forwarding API and the P6a trigger variant. When
* need_work_forward is true this is exactly the exported forwarding gate
* (gsp_phys_work_forward required); when false it keeps every other gate
@@ -6777,6 +7178,7 @@ static int gsp_phys_guest_ce_copy_common(u64 src_hpa, u32 src_off, u64 dst_hpa,
unsigned long start = jiffies;
unsigned long deadline;
u64 src_va = 0, dst_va = 0, sem_va = 0;
+ struct gsp_phys_fault_info fault = { };
u32 poll = 0;
bool relay = sem_hpa != 0;
int ret;
@@ -6962,6 +7364,30 @@ static int gsp_phys_guest_ce_copy_common(u64 src_hpa, u32 src_off, u64 dst_hpa,
pr_err("gsp-phys-work: GUEST CE RELAY FAILED: timeout after %u ms (guest sem=0x%08x want=0x%08x) sem_hpa=0x%llx+%u src_hpa=0x%llx dst_hpa=0x%llx bytes=%u\n",
poll_ms, poll, sem_payload, sem_hpa,
sem_off, src_hpa, dst_hpa, bytes);
+ /*
+ * C3: the timeout may be a real engine fault.
+ * Drain the status queue (before the software
+ * fallback runs), accept only a 0x1006 newer
+ * than this submission's doorbell snapshot and
+ * carry it back through *out for the hold to
+ * deliver after its channel/chid match.
+ */
+ if (gsp_phys_fault_relay_active()) {
+ struct gsp_phys_fault_info fi;
+ u32 fep = 0;
+
+ if (!gsp_phys_status_drain_fault(&fi, &fep) &&
+ fi.valid &&
+ fi.function ==
+ GSP_PHYS_MSG_FUNC_OS_ERROR_LOG) {
+ if (fep > READ_ONCE(gsp_phys_fault_submit_epoch)) {
+ fault = fi;
+ gsp_phys_fault_drained++;
+ } else {
+ gsp_phys_fault_stale++;
+ }
+ }
+ }
} else {
gsp_phys_guest_refused++;
pr_err("gsp-phys-work: GUEST CE COPY FAILED: timeout after %u ms (sem=0x%08x want=0x%08x) src_hpa=0x%llx dst_hpa=0x%llx bytes=%u\n",
@@ -7025,6 +7451,7 @@ out:
out->sem_value = poll;
out->elapsed_ms = jiffies_to_msecs(jiffies - start);
out->rc = ret;
+ out->fault = fault;
}
return ret;
}
@@ -7921,6 +8348,34 @@ static int gsp_phys_state_show(struct seq_file *m, void *v)
gsp_phys_relay_enabled());
}
{
+ /*
+ * P6c C3 fault observation/relay: the booter's 0x1006
+ * observation counters (seen/stale/drained/wait_unmatched,
+ * epoch) and the sema service's delivery counters
+ * (matched/unmatched/delivered/no_event/late_release). All zero
+ * while no fault occurs; the param state is shown last.
+ */
+ u64 f_seen = 0, f_stale = 0, f_drained = 0, f_wait = 0;
+ u64 fr_match = 0, fr_unmatch = 0, fr_delivered = 0;
+ u64 fr_no_event = 0, fr_late = 0;
+ u32 f_epoch = 0;
+
+ gsp_phys_backend_fault_counters(&f_seen, &f_stale, &f_drained,
+ &f_wait, &f_epoch);
+ gsp_sema_service_fwd_fault_stats(&fr_match, &fr_unmatch,
+ &fr_delivered, &fr_no_event,
+ &fr_late);
+ seq_printf(m,
+ "faults: seen=%llu stale=%llu drained=%llu wait_unmatched=%llu epoch=%u\n",
+ f_seen, f_stale, f_drained, f_wait, f_epoch);
+ seq_printf(m,
+ "fault_relay: matched=%llu unmatched=%llu delivered=%llu no_event=%llu late_release=%llu relay_param=%u fault_relay_param=%u fault_inject_param=%u\n",
+ fr_match, fr_unmatch, fr_delivered, fr_no_event,
+ fr_late, gsp_phys_relay_enabled(),
+ gsp_phys_fault_relay_enabled(),
+ gsp_phys_fault_inject_enabled());
+ }
+ {
char guest_ram[512];
gsp_phys_guest_ram_state(guest_ram, sizeof(guest_ram));