diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-29 13:52:49 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-29 13:52:49 -0400 |
| commit | 6e0efb5bb1282c673f0b4ea0eba0022cbb16ab31 (patch) | |
| tree | bbff5f338d3f595e74e238b95d9efaab27e3d9b8 | |
| parent | ed7cbc582a5602db46932917df002c0d31fdb8fb (diff) | |
| download | open-iov-6e0efb5bb1282c673f0b4ea0eba0022cbb16ab31.tar.gz open-iov-6e0efb5bb1282c673f0b4ea0eba0022cbb16ab31.zip | |
kernel: authoritative host-injected VA->PA pins (ipin table) + wider pin table
The CE push-buffer page 0x120000000 resolved to a zero-filled page even after
sema_map_add injected the correct translation: the shared pin table silently
dropped inserts once a hash cluster exceeded the 32-probe window (the page
hashes to slot 0, a hot cluster), and lookups fell through to the
content-scanned map and answered with a stale page - so every CE push read as
implausible and memset stayed 0. Injected translations now live in a separate
authoritative table checked before every other source and are never written by
self-discovery; the general pin table is 4x larger with a 4x probe window and
counts drops (pin_drop/ipin_drop) in the stats line. guest_kprobes.sh disables
the kprobe group before removal (EBUSY) and matches p:kprobes/<name>; the
guest wait sampler can run counters-only with GDB=0.
| -rw-r--r-- | kernel/gsp_sema_service.c | 90 | ||||
| -rw-r--r-- | tools/guest-diag/guest_kprobes.sh | 12 | ||||
| -rw-r--r-- | tools/guest-diag/track_waits.sh | 2 |
3 files changed, 82 insertions, 22 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 7475ea7..fa0fe65 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -167,14 +167,21 @@ static u32 svc_map_hash[SEMA_MAP_MAX * 2]; * (debug injection) and that must not be evicted by the learned cache, which * is content-addressed and gets thrashed by the lookup stream. */ -#define SVC_PIN_SLOTS 32768 +#define SVC_PIN_SLOTS (1u << 17) /* * Linear-probe window for the pin table. The table is now the primary * VA->PA store (push-buffer regions and bridged client mappings add * thousands of pages at a time), so a short window would silently drop - * inserts and mis-resolve lookups once it is half full. + * inserts and mis-resolve lookups once it is half full. Drops are counted + * (pin_drop) instead of being invisible. */ -#define SVC_PIN_PROBE 32 +#define SVC_PIN_PROBE 128 +/* + * Host-injected translations (debug `sema_map_add`): authoritative, never + * overwritten by self-discovery, checked before every other source. + */ +#define SVC_IPIN_SLOTS (1u << 13) +#define SVC_IPIN_PROBE 64 struct svc_pin { u64 va_page; @@ -305,6 +312,10 @@ struct sema_svc { struct svc_lat lat[SVC_LAT_MAX]; struct svc_pin pin[SVC_PIN_SLOTS]; u32 pin_n; + u64 pin_drop; + struct svc_pin ipin[SVC_IPIN_SLOTS]; + u32 ipin_n; + u64 ipin_drop; /* stats */ u64 walks_ok; @@ -1033,15 +1044,16 @@ static void svc_lat_store(u64 va, u64 addr, int ap) l->valid = true; } -static int svc_pin_lookup(u64 va, u64 *pa, int *ap) +static int pin_lookup_tab(const struct svc_pin *tab, u32 slots, u32 probe_max, + u64 va, u64 *pa, int *ap) { u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); - u32 mask = SVC_PIN_SLOTS - 1; + u32 mask = slots - 1; u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask; u32 probe; - for (probe = 0; probe < SVC_PIN_PROBE; probe++) { - struct svc_pin *p = &svc.pin[(h + probe) & mask]; + for (probe = 0; probe < probe_max; probe++) { + const struct svc_pin *p = &tab[(h + probe) & mask]; if (!p->va_page) return -ENOENT; @@ -1054,25 +1066,66 @@ static int svc_pin_lookup(u64 va, u64 *pa, int *ap) return -ENOENT; } -static void svc_pin_store(u64 va, u64 pa, int ap) +static void pin_store_tab(struct svc_pin *tab, u32 slots, u32 probe_max, + u32 *n, u64 *drops, u64 va, u64 pa, int ap) { u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1); - u32 mask = SVC_PIN_SLOTS - 1; + u32 mask = slots - 1; u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask; u32 probe; - for (probe = 0; probe < SVC_PIN_PROBE; probe++) { - struct svc_pin *p = &svc.pin[(h + probe) & mask]; + for (probe = 0; probe < probe_max; probe++) { + struct svc_pin *p = &tab[(h + probe) & mask]; if (!p->va_page || p->va_page == page) { if (!p->va_page) - svc.pin_n++; + (*n)++; p->va_page = page; p->pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1); p->ap = ap; return; } } + (*drops)++; +} + +static int svc_pin_lookup(u64 va, u64 *pa, int *ap) +{ + return pin_lookup_tab(svc.pin, SVC_PIN_SLOTS, SVC_PIN_PROBE, va, pa, ap); +} + +static void svc_pin_store(u64 va, u64 pa, int ap) +{ + pin_store_tab(svc.pin, SVC_PIN_SLOTS, SVC_PIN_PROBE, &svc.pin_n, + &svc.pin_drop, va, pa, ap); +} + +/* + * Injected (host-tool) translations. Kept separate from the discovered pin + * table so a hand-verified VA->PA pair can never be shadowed, evicted or + * silently dropped by the self-discovery stream: in the shared table a hot + * hash cluster longer than the probe window dropped inserts while lookups + * fell through to the content-scanned map and answered with a stale page + * (observed: the CE push-buffer page resolved to a zero-filled page and + * every push was "implausible"). + */ +static int svc_ipin_lookup(u64 va, u64 *pa, int *ap) +{ + return pin_lookup_tab(svc.ipin, SVC_IPIN_SLOTS, SVC_IPIN_PROBE, va, pa, ap); +} + +static void svc_ipin_store(u64 va, u64 pa, int ap) +{ + pin_store_tab(svc.ipin, SVC_IPIN_SLOTS, SVC_IPIN_PROBE, &svc.ipin_n, + &svc.ipin_drop, va, pa, ap); +} + +/* Any pinned translation: injected truth first, then self-discovered pins. */ +static int svc_pin_lookup_any(u64 va, u64 *pa, int *ap) +{ + if (!svc_ipin_lookup(va, pa, ap)) + return 0; + return svc_pin_lookup(va, pa, ap); } static void svc_add_map(u64 gpu_va, u64 pa); @@ -1258,7 +1311,7 @@ static int svc_lookup_trusted(struct nv_vgpu_device *vgpu, u64 va, u64 *addr, { u64 pa = 0; - if (!svc_pin_lookup(va, addr, ap)) { + if (!svc_pin_lookup_any(va, addr, ap)) { return 0; } if (!svc_lat_lookup(va, addr, ap)) @@ -1711,9 +1764,10 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu) svc.pushes_nomap, svc.memsets_done, svc.memsets_nomap, svc.memsets_fail, svc.ring_ops, svc.ops_denied, svc.false_trans, svc.gpfifo_skipped, svc.releases_faked); - pr_info("sema-svc: gpfifo done=%llu nomap=%llu vidmem_writes=%llu ring_auto=%u pb=%u/%u\n", + pr_info("sema-svc: gpfifo done=%llu nomap=%llu vidmem_writes=%llu ring_auto=%u pb=%u/%u pins=%u/%llu ipins=%u/%llu\n", svc.gpfifo_entries_done, svc.gpfifo_nomap, - svc.vidmem_writes, svc_ring_n, svc_pb_n, svc_pb_passes); + svc.vidmem_writes, svc_ring_n, svc_pb_n, svc_pb_passes, + svc.pin_n, svc.pin_drop, svc.ipin_n, svc.ipin_drop); /* show a few entries so mappings can be verified from dmesg */ { u32 shown; @@ -3266,7 +3320,7 @@ static void svc_gr_ring_autopin(struct nv_vgpu_device *vgpu, * The candidate chunk did not validate. If another source already * translates this ring VA (bridge or manual pin), trust it. */ - if (!svc_pin_lookup(ch->gpfifo_va, &pin_pa, &pin_ap)) { + if (!svc_pin_lookup_any(ch->gpfifo_va, &pin_pa, &pin_ap)) { set_bit(ch->gr_index, &svc_gr_ring_done); svc_ring_n++; return; @@ -3293,7 +3347,7 @@ static void svc_ring_autopin(struct nv_vgpu_device *vgpu, return; if (test_bit(ch->ce_index, &svc_ring_done)) return; - if (!svc_pin_lookup(ch->gpfifo_va, &pa, &ap)) { + if (!svc_pin_lookup_any(ch->gpfifo_va, &pa, &ap)) { set_bit(ch->ce_index, &svc_ring_done); svc_ring_n++; return; @@ -4327,7 +4381,7 @@ static void svc_handle_map_add(void) ap = req.ap ? req.ap : SVC_APERTURE_SYS; for (off = 0; off < (req.len ? req.len : 1); off += SEMA_PAGE_SIZE) { - svc_pin_store(req.va + off, req.pa + off, ap); + svc_ipin_store(req.va + off, req.pa + off, ap); svc_lat_store(req.va + off, req.pa + off, ap); } /* diff --git a/tools/guest-diag/guest_kprobes.sh b/tools/guest-diag/guest_kprobes.sh index f676e20..0afe6c6 100644 --- a/tools/guest-diag/guest_kprobes.sh +++ b/tools/guest-diag/guest_kprobes.sh @@ -15,29 +15,35 @@ TR=/sys/kernel/tracing [ -d "$TR" ] || TR=/sys/kernel/debug/tracing +# An enabled kprobe event cannot be deleted (EBUSY); disable the group first. +echo 0 > "$TR/events/kprobes/enable" 2>/dev/null || true + add_probe() { name=$1; sym=$2; fmt=$3 + echo "-:kprobes/$name" >> "$TR/kprobe_events" 2>/dev/null echo "-:$name" >> "$TR/kprobe_events" 2>/dev/null if ! grep -qw "$sym" /proc/kallsyms 2>/dev/null; then echo "FAIL $name: symbol $sym not in kallsyms" return 1 fi if echo "p:$name $sym $fmt" >> "$TR/kprobe_events" 2>/dev/null; then - if grep -q "p:$name" "$TR/kprobe_events" 2>/dev/null; then + if grep -qE "p:[^ ]*[ /]$name( |$)" "$TR/kprobe_events" 2>/dev/null; then echo "ok $name $sym" return 0 fi fi + echo "-:kprobes/$name" >> "$TR/kprobe_events" 2>/dev/null echo "-:$name" >> "$TR/kprobe_events" 2>/dev/null if echo "p:$name nvidia_uvm:$sym $fmt" >> "$TR/kprobe_events" 2>/dev/null; then - if grep -q "p:$name" "$TR/kprobe_events" 2>/dev/null; then + if grep -qE "p:[^ ]*[ /]$name( |$)" "$TR/kprobe_events" 2>/dev/null; then echo "ok-module $name $sym" return 0 fi fi + echo "-:kprobes/$name" >> "$TR/kprobe_events" 2>/dev/null echo "-:$name" >> "$TR/kprobe_events" 2>/dev/null if echo "p:$name $sym" >> "$TR/kprobe_events" 2>/dev/null; then - if grep -q "p:$name" "$TR/kprobe_events" 2>/dev/null; then + if grep -qE "p:[^ ]*[ /]$name( |$)" "$TR/kprobe_events" 2>/dev/null; then echo "ok-bare $name $sym" return 0 fi diff --git a/tools/guest-diag/track_waits.sh b/tools/guest-diag/track_waits.sh index 4c9cd1a..45a9a66 100644 --- a/tools/guest-diag/track_waits.sh +++ b/tools/guest-diag/track_waits.sh @@ -52,7 +52,7 @@ for i in $(seq 1 "$N"); do TS=$(date +%T.%3N) kill -0 "$PID" 2>/dev/null || { echo "PROCESS GONE at $TS (sample $i)" >> /tmp/trk.log; break; } - if [ $((i % 2)) -eq 1 ]; then + if [ $((i % 2)) -eq 1 ] && [ "${GDB:-1}" = "1" ]; then echo "=== $TS sample $i ===" >> /tmp/trk.log timeout 8 gdb -p "$PID" -batch -x /tmp/trkinfo.py 2>&1 \ | grep -vE "^\[|Reading symbols|^0x[0-9a-f]+ in |warning:" >> /tmp/trk.log |
