#!/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())