summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_sema_service.c181
1 files changed, 146 insertions, 35 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 1ea8038..58778c2 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -149,6 +149,14 @@ struct svc_channel {
* entries, and an INC release must not advance
* the tracking semaphore twice for one push */
u32 expected_sem_payload; /* next semaphore payload we expect for this channel's tracking sem */
+ u32 entry_want; /* expected_sem_payload when the pending GPFIFO
+ * entry was first examined: if the pool has
+ * advanced past it since, the entry's release
+ * was already delivered by another path (the
+ * content sweep) and the entry can be consumed
+ * immediately instead of waiting out the grace
+ * window (observed: one 8 s grace per push,
+ * serializing the runtime init) */
u64 last_rel_va; /* last tracking-sem VA seen in a readable push */
u32 logged_expected; /* last pool value logged (rate limit) */
unsigned long rescue_last; /* rate limit for the full-RAM rescue scan */
@@ -2429,23 +2437,72 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
}
/*
- * Does this buffer start like a push? The discovered VA->PA map is
+ * Parse a buffer as a complete push method stream.
+ *
+ * The B06F/C5B5 method encoding carries four method shapes: SEC_OP 1
+ * (INC_METHOD), 3 (NON_INC_METHOD), 4 (IMMD_DATA_METHOD, the immediate data
+ * lives in the header's count field so it has no payload words), 5
+ * (ONE_INC) and 7 (END_PB_SEGMENT, header only); a zero word is padding.
+ * UVM emits INC for almost everything but a CE push can *start* with a
+ * NON_INC method (observed: a runtime channel's second push begins
+ * `0x60080002` = NON_INC count 8), so a parser that only accepts op 1
+ * rejects real, readable pushes and no content rescue can validate their
+ * page.
+ *
+ * Returns true when every word is covered by a method (trailing zero
+ * padding allowed) and at least one method was seen. Arbitrary data
+ * (firmware, page tables, allocator lists) practically never parses to an
+ * exact method boundary, which is what keeps the content rescue safe.
+ */
+static bool svc_stream_parse(const u8 *buf, u32 len)
+{
+ u32 off = 0, methods = 0;
+
+ for (;;) {
+ u32 hdr, op, count;
+
+ if (off + 4 > len)
+ break;
+ memcpy(&hdr, buf + off, 4);
+ if (hdr == 0)
+ break; /* trailing zero padding */
+ op = (hdr >> 29) & 0x7;
+ count = (hdr >> 16) & 0xfff;
+ switch (op) {
+ case 1: /* INC_METHOD */
+ case 3: /* NON_INC_METHOD */
+ case 5: /* ONE_INC */
+ if (!count || count > 64 ||
+ off + 4 + count * 4 > len)
+ return false;
+ off += 4 + count * 4;
+ break;
+ case 4: /* IMMD_DATA_METHOD: header only */
+ off += 4;
+ break;
+ case 7: /* END_PB_SEGMENT: header only */
+ off += 4;
+ break;
+ default:
+ return false;
+ }
+ if (++methods > 1024)
+ return false;
+ }
+ while (off < len && buf[off] == 0)
+ off++;
+ return methods > 0 && off == len;
+}
+
+/*
+ * Does this buffer read as a push? The discovered VA->PA map is
* content-scanned and can hand a push VA an unrelated page (observed: GSP
- * firmware and kernel data pages). A real push starts with a method at
- * address 0 (SET_OBJECT / INC_METHOD class bind) with a count that fits
- * the buffer.
+ * firmware and kernel data pages), so a readable page is only trusted when
+ * the whole entry parses as a method stream.
*/
static bool svc_method_stream_plausible(const u8 *buf, u32 len)
{
- u32 hdr = 0, op, count;
-
- if (len < 8)
- return false;
- memcpy(&hdr, buf, 4);
- op = (hdr >> 29) & 0x7;
- count = (hdr >> 16) & 0xfff;
- return op == 1 && count && count <= 64 &&
- 4 + count * 4 <= len && ((hdr & 0x1fff) << 2) == 0;
+ return svc_stream_parse(buf, len);
}
/*
@@ -2821,12 +2878,32 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
continue;
}
- if (op != 1 || !count || count > 64 ||
+ if (op == 4 || op == 7) {
+ /* Header-only methods (immediate data in the header
+ * for op 4; end-of-push segment for op 7). */
+ off += 4;
+ continue;
+ }
+
+ if ((op != 1 && op != 3 && op != 5) || !count || count > 64 ||
off + 4 + count * 4 > len) {
off += 4; /* resync */
continue;
}
+ if (op == 3 || op == 5) {
+ /*
+ * NON_INC / ONE_INC methods carry their own data
+ * words. Skip the method whole: resyncing word by
+ * word through the data misreads payload words as
+ * headers (observed: a NON_INC block whose data
+ * held INC-shaped words queued a bogus LAUNCH_DMA).
+ * No release is decoded from these encodings.
+ */
+ off += 4 + count * 4;
+ continue;
+ }
+
if (count >= 1)
memcpy(&v0, buf + off + 4, 4);
if (count >= 2)
@@ -4486,27 +4563,7 @@ static u32 svc_rescue_match_buf(const u8 *buf, u32 len, u64 base_pa, u32 slot,
*/
static bool svc_stream_full_ok(const u8 *buf, u32 len)
{
- u32 off = 0, methods = 0;
-
- while (off + 4 <= len) {
- u32 hdr, op, count;
-
- memcpy(&hdr, buf + off, 4);
- if (hdr == 0)
- break; /* trailing zero padding */
- op = (hdr >> 29) & 0x7;
- count = (hdr >> 16) & 0xfff;
- if (op != 1 || !count || count > 64 ||
- off + 4 + count * 4 > len)
- return false;
- off += 4 + count * 4;
- if (++methods > 1024)
- return false;
- }
- /* Trailing zero padding is common in recycled push slots. */
- while (off < len && buf[off] == 0)
- off++;
- return methods > 0 && off == len;
+ return svc_stream_parse(buf, len);
}
/*
@@ -5015,6 +5072,18 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
u64 va, idx = ch->last_get;
u32 len, e0, e1, g;
+ /*
+ * Remember the payload this entry should deliver the
+ * first time it is examined. If the pool advances past
+ * it later, the release was already delivered (the
+ * content sweep applies releases from RAM pages before
+ * the pump reads their entries) and the entry must be
+ * consumed instead of waiting out the grace window -
+ * otherwise every push costs one full grace period.
+ */
+ if (!ch->defer_since)
+ ch->entry_want = ch->expected_sem_payload;
+
if (rrc) {
stalled = true; /* ring unreadable: sweep RAM */
break;
@@ -5158,6 +5227,27 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
if (!rel_seen && gsp_sema_fake_rel)
svc_fake_release(vgpu, ch);
else if (!rel_seen) {
+ /*
+ * Already delivered by the
+ * content sweep: the pool
+ * advanced past this entry's
+ * payload while it was pending
+ * (or decoded no release). The
+ * entry has nothing left to
+ * deliver; consume it now, do
+ * not pay the grace window.
+ */
+ if (ch->entry_want &&
+ ch->expected_sem_payload >
+ ch->entry_want) {
+ ch->defer_since = 0;
+ ch->rescue_fail = 0;
+ ch->last_get = (idx + 1) %
+ ch->gpfifo_entries;
+ svc.gpfifo_skipped++;
+ kfree(pb);
+ continue;
+ }
if (!ch->defer_since)
ch->defer_since =
jiffies;
@@ -5326,6 +5416,27 @@ if (gsp_sema_fake_rel &&
* - that consumed the client's
* never-delivered wait.
*/
+ if (ch->entry_want &&
+ ch->expected_sem_payload >
+ ch->entry_want) {
+ /*
+ * Already delivered
+ * (the content sweep
+ * applied this entry's
+ * release from RAM):
+ * consume now instead
+ * of waiting the grace
+ * window per push.
+ */
+ ch->rescue_fail = 0;
+ ch->defer_since = 0;
+ ch->last_get =
+ (idx + 1) %
+ ch->gpfifo_entries;
+ svc.gpfifo_skipped++;
+ kfree(pb);
+ continue;
+ }
if (!ch->defer_since)
ch->defer_since =
jiffies;