summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s02_counter_publication.py
blob: 690f60f8d7c63bf6291bdd728b18053c0f988c0c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
"""s02 - counter publication: which of N slots advanced, and when?

Real scenario (Handoff-vGPU-IO.md PART 46-64, 2026-09-24..2026-09-29):
``cuCtxCreate`` waits on 20 GR per-channel completion counters.  The host saw
the client's releases but could not translate the counter page; for many boots
only 4 of 20 client-visible slots ever moved (PART 49).  The exact slot layout
(page VA ``0x20400f000``, slots at ``+0xec0``, 16-byte stride) was only proven
by a raw live page dump (PART 64, 2026-09-29 ~16:55), and the host raced a
~2-3 s pin path against the client's ~2 s GR-phase deadline.  Static reading
could not tell which slots were published: the page's GPA changed every boot
and stale translations pointed at the previous client's page (PART 51).

Analog: post 20 sequential commands and watch the demo's published result
field; prove every slot was observed, with the publish PC recorded per slot.
"""
from __future__ import annotations

import os

try:  # package import (tests) or direct script execution (run_all)
    from . import _common as C
except ImportError:
    import sys as _sys

    _sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    import _common as C

SLOTS = 20


def run() -> dict:
    import greeneye as ge

    backend = C.choose_backend("native")
    out = C.artifact_dir("s02_counter_publication")
    commands = [
        'ge.launch(kernel=DEMO_ELF, backend="%s")' % backend,
        "s.watch(gsp_ctrl+0x40, size=8, kind='w', callback=record pc)  # publication",
        "for slot in 1..20: write mailbox cmd=5 (STATE); run(duration=0.05)",
        "read gsp_ctrl.result after every slot; compare against the expected stage value",
    ]
    scenario = C.scenario(
        ids=[
            "Handoff-vGPU-IO.md PART 49/50 (2026-09-29): only 4/20 counters moved; all 20 reached 1 after the queue fix",
            "PART 64 (2026-09-29 ~16:55): raw page dump proves 20 slots at 16-byte stride",
            "PART 46/48 (2026-09-29): rescue/sweep latency and tracker waits",
        ],
        baseline=(
            "PARTs 46->64: ~5 days of sessions; PART 49 'only 4 of 20 counters "
            "ever moved'; each boot re-derived the slot layout because the "
            "counter page GPA changed (PART 51/53)"
        ),
        projection=(
            "on real silicon the 20 slots are per-channel memory pages; the "
            "analog publishes 20 logical slots through the same watchpoint "
            "shape, one store each"
        ),
        blocked=True,
        device="H2.2 device model + real GSP-RM counter pages",
    )
    C.render_banner(
        "s02_counter_publication",
        "Of the N client-visible slots, which ones advanced, and where was each published?",
        "20 GR per-channel completion counters: 4/20 published for days until a live page dump",
        scenario["static_baseline"],
        commands,
    )

    session = ge.launch(kernel=C.DEMO_ELF, backend=backend, name="vd-s02")
    slots = []
    try:
        ctrl = session.symbols.lookup("gsp_ctrl")
        publishes = []
        session.watch(ctrl + C.CTRL_RESULT, size=8, kind="w", callback=lambda hit: publishes.append(hit.pc))
        session.run(duration=0.25)
        before = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT)
        for slot in range(SLOTS):
            session.memory.write_u64(ctrl + C.CTRL_MAILBOX, 5)  # STATE
            session.run(duration=0.05)
            value = session.memory.read_u64(ctrl + C.CTRL_RESULT)
            slots.append(
                {
                    "slot": slot + 1,
                    "result": value,
                    "result_hex": f"{value:#x}",
                    "published": value in (0x10, 0x20, 0x30, 0x40),
                }
            )
        after = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT)
        published = [s for s in slots if s["published"]]
        payload = {
            "success": len(published) == SLOTS and (after - before) == SLOTS,
            "backend": backend,
            "slots_requested": SLOTS,
            "slots_published": len(published),
            "rpc_count_delta": after - before,
            "watch_stores": len(publishes),
            "distinct_values": sorted({s["result_hex"] for s in published}),
            "per_slot": slots,
            "publish_pcs": sorted(set(publishes)),
            "question": f"did all {SLOTS} slots publish, and can each publication be attributed?",
            "answer": (
                f"{len(published)}/{SLOTS} slots published ({after - before} dispatcher "
                f"completions, {len(publishes)} stores to gsp_ctrl.result, values "
                f"{sorted({s['result_hex'] for s in published})}); every store PC is captured, "
                "exactly the evidence class that took a live 512-byte page dump on the real path"
            ),
            "success_criteria": {
                "pass": f"{SLOTS}/{SLOTS} slots observed with rpc_count advancing once per slot",
                "fail": "any slot does not publish or the count delta disagrees",
            },
            "real_case": scenario,
        }
        C.write_json(os.path.join(out, "counters.json"), payload)
    finally:
        session.close()
    return payload


def main() -> int:
    return 0 if C.timed_demo(run, C.artifact_dir("s02_counter_publication"))["success"] else 1


if __name__ == "__main__":
    raise SystemExit(main())