diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-29 16:14:12 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-29 16:14:12 -0400 |
| commit | 6d5131e87a2c9cb5c20437669c1917f7be6252b5 (patch) | |
| tree | 43934878bc90db539f697bdb2fe069ab0c484442 | |
| parent | 78b8f909784676c5ec6f02a6c48d51ec2a589f96 (diff) | |
| download | open-iov-6d5131e87a2c9cb5c20437669c1917f7be6252b5.tar.gz open-iov-6d5131e87a2c9cb5c20437669c1917f7be6252b5.zip | |
sema-svc: persist GR counter slot per channel; stable slot layout; bulk pin queue 4k
- counter_pa autoreg result is written back to the channel table so later
passes cannot re-target a different slot when the live GR count changes
during teardown; re-registration of the same channel preserves the slot.
- counter slot layout uses the max GR count ever seen in the generation
instead of the shrinking live count.
- sema_map_add queue 64 -> 4096 entries so a full-window pin burst drains
without ENOSPC retries.
- pin_gr_stream: one batched sudo pass per flush (was one process per run,
~4 s for 400 runs), retrying only queue-rejected runs.
- semfix_uvmt: SEMFIX_SKIP_PB=1 skips the blocking push-buffer chain walk
when the vsock window stream already supplies those pages.
| -rw-r--r-- | kernel/gsp_sema_service.c | 43 | ||||
| -rw-r--r-- | tools/guest-diag/pin_gr_stream.py | 53 | ||||
| -rwxr-xr-x | tools/guest-diag/semfix_uvmt.py | 7 |
3 files changed, 94 insertions, 9 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index c46cbbb..5bff6ca 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -481,6 +481,21 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc) } if (ch) { int k; + u64 keep_counter_pa = 0; + u8 keep_gr_index = 0, keep_ce_index = 0; + bool same = ch->valid && ch->h_object == desc->h_object; + + /* + * A re-registration of the same channel must not lose the + * counter slot learned for it (the RM re-issues registration + * for an existing channel on some control paths, and the + * counter layout does not change). + */ + if (same) { + keep_counter_pa = ch->counter_pa; + keep_gr_index = ch->gr_index; + keep_ce_index = ch->ce_index; + } memset(ch, 0, sizeof(*ch)); /* @@ -520,6 +535,11 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc) ch->notifier_pa = desc->notifier_pa; ch->notifier_as = desc->notifier_as ? desc->notifier_as : SVC_APERTURE_SYS; + if (same) { + ch->counter_pa = keep_counter_pa; + ch->gr_index = keep_gr_index; + ch->ce_index = keep_ce_index; + } } spin_unlock_irqrestore(&svc.chan_lock, flags); @@ -3987,6 +4007,13 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) { /* Only ever called from the service thread. */ static struct svc_channel local[SVC_MAX_CHANNELS]; + /* + * Total GR channels the client ever had in the current generation. + * The counter-slot layout is fixed when the client allocates all its + * channels, so the slot must not be recomputed with a shrinking live + * count (a teardown pass would re-target the wrong slot). + */ + static int svc_gr_max; unsigned long flags; int i, k, n = 0, n_gr = 0; bool stalled = false; @@ -4001,6 +4028,12 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) } spin_unlock_irqrestore(&svc.chan_lock, flags); + if (n_gr > svc_gr_max) + svc_gr_max = n_gr; + else if (!n_gr) + svc_gr_max = 0; + n_gr = svc_gr_max; + for (i = 0; i < n; i++) { struct svc_channel *ch = &local[i]; u64 ring_pa = 0; @@ -4354,6 +4387,14 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) svc.chan[k].rescue_last = ch->rescue_last; svc.chan[k].rescue_fail = ch->rescue_fail; svc.chan[k].defer_since = ch->defer_since; + /* + * Persist the counter slot autoreg resolved on the + * snapshot: without this the next pass re-derives + * it (with a different live GR count during + * teardown) and the publishes target the wrong + * slot. + */ + svc.chan[k].counter_pa = ch->counter_pa; break; } } @@ -4620,7 +4661,7 @@ int gsp_sema_service_dump_channels(void) * could not take a batch at all. */ #define SVC_PIN_REWIND 8 -#define SVC_MAP_ADD_MAX 64 +#define SVC_MAP_ADD_MAX 4096 struct svc_map_add_req { u64 va; diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py index 643145b..b80d30a 100644 --- a/tools/guest-diag/pin_gr_stream.py +++ b/tools/guest-diag/pin_gr_stream.py @@ -149,22 +149,59 @@ def main(): def _flush(runs, seen): - """Inject run lines, return number of pages pinned.""" - pages = 0 + """Inject run lines (one batched sudo pass), return number of pages pinned. + + The old implementation spawned one sudo process per run; with ~400 runs + that cost ~4 s, which landed the GR pins after the client's context-init + deadline. The batch writes all runs in a single shell, retrying only the + ones the kernel queue rejected (ENOSPC) individually. + """ + todo = [] for va, pa, n in runs: if seen.get(va) == pa and n == 1: continue + todo.append((va, pa, n)) + if not todo: + return 0 + + def mark(va, pa, n): for i in range(n): seen[va + i * 0x1000] = pa + i * 0x1000 - if not inject(va, pa, n * 0x1000): - for i in range(n): - if inject(va + i * 0x1000, pa + i * 0x1000, 0x1000): - pages += 1 + + script = [] + for va, pa, n in todo: + script.append( + "printf '0x%x,0x%x,0x%x,1' > %s/sema_map_add 2>/dev/null || " + "echo 'FAIL 0x%x 0x%x 0x%x'" + % (va, pa, n * 0x1000, PARAM, va, pa, n * 0x1000)) + failed = set() + try: + r = subprocess.run(["sudo", "-n", "sh", "-c", "\n".join(script)], + capture_output=True, text=True, timeout=60) + for line in r.stdout.splitlines(): + m = re.match(r"FAIL (0x[0-9a-f]+) (0x[0-9a-f]+) (0x[0-9a-f]+)", + line.strip()) + if m: + failed.add((int(m.group(1), 16), int(m.group(2), 16), + int(m.group(3), 16) // 0x1000)) + except Exception as e: + log("batch inject failed: %s" % e) + failed = set(todo) + + pages = 0 + for va, pa, n in todo: + if (va, pa, n) in failed: continue + mark(va, pa, n) pages += n + for va, pa, n in failed: + for i in range(n): + if inject(va + i * 0x1000, pa + i * 0x1000, 0x1000): + mark(va + i * 0x1000, pa + i * 0x1000, 1) + pages += 1 if pages: - log("pinned %d page(s) in %d run(s)" % (pages, len(runs))) - for va, pa, n in runs[:3]: + log("pinned %d page(s) in %d run(s)" % (pages, len(todo))) + for va, pa, n in todo[:3]: log(" va=0x%x -> pa=0x%x (%d)" % (va, pa, n)) return pages diff --git a/tools/guest-diag/semfix_uvmt.py b/tools/guest-diag/semfix_uvmt.py index 02d4f9e..fd58299 100755 --- a/tools/guest-diag/semfix_uvmt.py +++ b/tools/guest-diag/semfix_uvmt.py @@ -43,6 +43,11 @@ COUNTER_LO = int(os.environ.get("COUNTER_LO", "0x20400f000"), 16) COUNTER_HI = int(os.environ.get("COUNTER_HI", "0x204010000"), 16) SEMSTREAM = os.environ.get("SEM_STREAM_FILE", "/tmp/opencode/sem_stream.txt") TRACE_EVERY = int(os.environ.get("UVMT_TRACE_EVERY", "5")) +# The push-buffer resolution walks the channel/pool/manager chain with many +# QMP reads and can block the poller for tens of seconds. When the GR window +# stream (pin_gr_stream.py) already supplies the push-buffer pages, skip it so +# the tracking-sem poller stays live during the client's deadline window. +SKIP_PB = os.environ.get("SEMFIX_SKIP_PB", "0") == "1" class Stream: @@ -309,6 +314,8 @@ def main(): ok = pin(gv, gpa) log("semfix: sem=0x%x pool va=0x%x -> gpa=0x%x pin=%s" % (sem, gv, gpa, "ok" if ok else "FAILED")) + if SKIP_PB: + continue d = {} try: res = resolve_from_sem(q, sem, d) |
