summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s06_inject_service.py
blob: dc5949e2691e844129591f7b538d3f78888af812 (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
#!/usr/bin/env python3
"""s06 - inject a service: add an RM engine without rebuilding the firmware.

Real scenario (Handoff-vGPU-IO.md PART 92 ADDENDUM 5-11, 2026-10-02): the P6
work wired guest-originated CE copies through the real engine; every change to
the service/wiring path cost a kernel rebuild plus a supervised physical boot
cycle (P6a/b/c, ~5-8 min each, one device-write experiment per cycle) and was
only accepted after guest smoke ``ALL OK 64/64``.  docs/12-INJECTION-SDK.md
defines the in-simulator equivalent: load a position-independent service blob
and register it with the firmware's dispatch table (commands 16..23).

Analog: load ``sdk/build/injected_demo.elf`` into slot 6, invoke it directly
(Frida NativeFunction) and through the firmware mailbox, and record the
``service_injected`` evidence event.  No kernel rebuild, no hardware.
"""
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


def run() -> dict:
    import greeneye as ge
    from greeneye.inject import invoke_remote, invoke_via_firmware, load_service

    backend = C.choose_backend("native")
    out = C.artifact_dir("s06_inject_service")
    commands = [
        'load_service(s, "sdk/build/injected_demo.elf", slot=6)',
        "invoke_remote(s, svc, cmd=1, a0=0x41)        # debugger-side call",
        "invoke_via_firmware(s, svc, cmd=1, a0=0x41)  # mailbox cmd=16",
        "read gsp_ctrl.result / handlers[6]",
    ]
    scenario = C.scenario(
        ids=[
            "Handoff-vGPU-IO.md PART 92 ADDENDUM 5-11 (2026-10-02): P6 service/wiring rungs",
            "PART 92 ADDENDUM 6 (2026-10-02): p6run7 first real engine copy after service wiring",
            "docs/12-INJECTION-SDK.md: blob/ABI contract for injecting RM services",
        ],
        baseline=(
            "each P6 rung was a kernel rebuild plus a supervised physical cycle "
            "(~5-8 min boots; P6a->P6c spanned ~02:20-08:30) with fail-safe "
            "release discipline after every run"
        ),
        projection=(
            "on real GSP-RM the registry is the RM service table (M5), gated on "
            "H2.2; the load/register/invoke/observe shape is identical to the "
            "demo handler-table slot"
        ),
        blocked=True,
        device="H2.2 device model + M5 RM service registry",
    )
    C.render_banner(
        "s06_inject_service",
        "Can a new service be loaded, registered and invoked without rebuilding the firmware?",
        "guest-originated CE service wiring (P6a-P6c): kernel rebuild + supervised boot per rung",
        scenario["static_baseline"],
        commands,
    )

    if not os.path.isfile(C.SDK_ELF):
        payload = {
            "success": False,
            "skipped": True,
            "failure": "sdk/build/injected_demo.elf not built (run: make sdk)",
            "backend": backend,
            "real_case": scenario,
        }
        C.write_json(os.path.join(out, "inject.json"), payload)
        return payload

    session_dir = C.session_dir(out)
    session = ge.launch(kernel=C.DEMO_ELF, backend=backend, record=session_dir, name="vd-s06")
    try:
        ctrl = session.symbols.lookup("gsp_ctrl")
        session.run(duration=0.25)
        service = load_service(session, C.SDK_ELF, slot=C.SERVICE_SLOT)
        remote_ping = invoke_remote(session, service, 1, 0x41)
        remote_sum = invoke_remote(session, service, 2, 0x10, 0x20)
        via_fw_ping = invoke_via_firmware(session, service, 1, 0x41)
        via_fw_sum = invoke_via_firmware(session, service, 2, 0x10, 0x20)
        handlers7 = session.memory.read_u64(ctrl + 0x150 + 8 * C.SERVICE_SLOT)
        payload = {
            "success": remote_ping == (0x41 ^ C.PING_XOR) and remote_sum == 0x30
            and via_fw_ping == remote_ping and via_fw_sum == remote_sum,
            "backend": backend,
            "service": service.summary(),
            "service_sha256": C.sha256_file(C.SDK_ELF),
            "handlers_slot6": f"{handlers7:#x}",
            "remote": {"ping": remote_ping, "sum": remote_sum},
            "via_firmware": {"ping": via_fw_ping, "sum": via_fw_sum},
            "mailbox_command": C.INJECT_CMD_BASE + C.SERVICE_SLOT,
            "session": os.path.relpath(session_dir, C.TOOL_ROOT),
            "question": "load/register/invoke a service blob end-to-end, both routes",
            "answer": (
                f"service loaded at {service.load_addr:#x}, slot {service.slot} "
                f"(handlers[{service.slot}]={handlers7:#x}); remote ping={remote_ping:#x} "
                f"sum={remote_sum:#x}; via mailbox cmd={C.INJECT_CMD_BASE + C.SERVICE_SLOT} "
                f"ping={via_fw_ping:#x} sum={via_fw_sum:#x} - both paths agree, one session"
            ),
            "success_criteria": {
                "pass": "remote and firmware-mailbox invocations return the SDK ABI values and agree",
                "fail": "a route disagrees, the slot is not registered, or the blob cannot load",
            },
            "real_case": scenario,
        }
        C.write_json(os.path.join(out, "inject.json"), payload)
    finally:
        session.close()
    return payload


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


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