diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-06 19:57:20 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-06 19:57:20 -0400 |
| commit | 23d8683f3012ac579db424735d1418814bb7ff01 (patch) | |
| tree | 384cca6be54d7a5936544ff40e7715cf7751a4a9 /debug/risc-v-gsp-rm-vm/tests/test_libos_port.py | |
| parent | 9de2a2a9899236b62b413ceb78946778fc6ada39 (diff) | |
| download | open-iov-23d8683f3012ac579db424735d1418814bb7ff01.tar.gz open-iov-23d8683f3012ac579db424735d1418814bb7ff01.zip | |
risc-v-gsp-rm-debugger: B5 two-task attach + aggregate-state decode + docs 51/53 status
- sim/libos_port.py: port-role model + two-task network (extintr handoff,
cross-task send/reply, TASK_STATE_QUERY trap state)
- sim/msgq_attach.py: image task discovery, bounded two-task schedule,
second-task-frontier divergence fields (first ring poll still blocked on
post-init resume + GSP_ARGUMENTS_CACHED handoff; no fabricated polls)
- harness/state_decode.py: aggregate 'group: key=value' state parsing
(20-gsp-phys-state-* dumps) + kv diff classification
- tests: 127 green across the five observability modules
- docs/51/53: status and changelog updates
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_libos_port.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/tests/test_libos_port.py | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_libos_port.py b/debug/risc-v-gsp-rm-vm/tests/test_libos_port.py index 0f01c01..befa14f 100644 --- a/debug/risc-v-gsp-rm-vm/tests/test_libos_port.py +++ b/debug/risc-v-gsp-rm-vm/tests/test_libos_port.py @@ -29,11 +29,15 @@ if TOOL_ROOT not in sys.path: from sim.libos import INTERRUPT_ARM_EXTERNAL, TASK_PORT_SEND_RECV_WAIT # noqa: E402 from sim.libos_port import ( # noqa: E402 EXTINTR_WAKE_CONTRACT, + EXTERNAL_INTERRUPT_XCAUSE, HANDSHAKE_CONTRACT, + NETWORK_REPORT_FORMAT, REPORT_FORMAT, SHUTTLE_ANY, + SPURIOUS_XEPC, TIMEOUT_INFINITE, LibosPortModel, + LibosPortNetwork, role_for, ) @@ -50,6 +54,26 @@ class FakeMachine: self.x[index] = value & 0xFFFFFFFFFFFFFFFF +class FakeMemoryMachine(FakeMachine): + """Register file plus flat memory for the routing model tests.""" + + def __init__(self, size: int = 0x10000): + super().__init__() + self.ram = bytearray(size) + self.ram_base = 0 + self.ram_size = size + + def read_mem(self, addr: int, size: int) -> bytes: + if addr < 0 or addr + size > self.ram_size: + raise ValueError("out of range") + return bytes(self.ram[addr:addr + size]) + + def write_mem(self, addr: int, data: bytes) -> None: + if addr < 0 or addr + len(data) > self.ram_size: + raise ValueError("out of range") + self.ram[addr:addr + len(data)] = data + + class FakeBase: """Minimal base syscall model: handles arm, records and stops the rest.""" @@ -287,5 +311,167 @@ class StateTests(unittest.TestCase): self.assertNotIn(str(key), ("t", "ts", "timestamp", "time")) +class SyncPortTests(unittest.TestCase): + """Synchronous receive semantics (waitId != 0 must block, not fake OK).""" + + def test_sync_recv_without_completion_blocks(self): + model = LibosPortModel(base=FakeBase()) + machine = FakeMachine() + _port_call(machine, recv=(9, 8, 0x100, 0x10), wait=9) + handled, result = model.handle(machine, pc=0x4019B00) + self.assertFalse(handled) + self.assertIsNone(result) + self.assertEqual(model.stopped["reason"], "sync-recv-blocks") + self.assertEqual(model.waits[-1]["reason"], "sync-recv-blocks") + self.assertEqual(model.waits[-1]["shuttle"], 9) + + def test_sync_recv_returns_a_queued_completion(self): + model = LibosPortModel(base=FakeBase()) + machine = FakeMachine() + model.host_kick() + _port_call(machine, recv=(10, 6, 0, 0)) + model.handle(machine, pc=0x4018038) + machine.set(5, INTERRUPT_ARM_EXTERNAL) + machine.set(10, 0xFF) + model.handle(machine, pc=0x4018054) + _port_call(machine, recv=(9, 8, 0x200, 0x10), wait=9) + handled, result = model.handle(machine, pc=0x4019B00) + self.assertTrue(handled) + self.assertEqual(result, 0) + self.assertEqual(machine.x[16], 10) + self.assertEqual(model.waits[-1]["completed"], 10) + + +class NetworkModelTests(unittest.TestCase): + """The two-task routing model (opt-in, additive).""" + + def _network(self): + return LibosPortNetwork(base=FakeBase(), tasks=("init", "rm"), + current="init") + + def test_state_format_is_the_network_schema(self): + model = self._network() + self.assertEqual(model.state()["format"], NETWORK_REPORT_FORMAT) + self.assertEqual(model.state()["tasks"], ["init", "rm"]) + + def test_cross_task_send_copies_the_firmware_payload(self): + model = self._network() + machine = FakeMemoryMachine() + model.switch("init", pc=0) + _port_call(machine, recv=(9, 8, 0x1000, 0x808)) + model.handle(machine, pc=0x4019B00) + machine.write_mem(0x2000, b"\x11\x22\x33\x44") + model.switch("rm", pc=0) + _port_call(machine, send=(1, 8, 0x2000, 4), recv=(2, 11, 0x3000, 0x100), + wait=2) + handled, _ = model.handle(machine, pc=0x5AD507C) + self.assertFalse(handled) # the sync receive blocks + self.assertEqual(model.stopped["reason"], "sync-recv-blocks") + completions = model.task_completions["init"] + self.assertEqual(len(completions), 1) + self.assertEqual(completions[0]["payload_size"], 4) + self.assertEqual(completions[0]["origin_task"], 1) + self.assertEqual(machine.read_mem(0x1000, 4), b"\x11\x22\x33\x44") + self.assertEqual(model.cross_task_payloads, + [{"port": 8, "size": 4, "from": "rm", "to": "init", + "kind": "send"}]) + + def test_reply_completes_the_sender_receive_and_resume_is_idempotent(self): + model = self._network() + machine = FakeMemoryMachine() + model.switch("init", pc=0) + _port_call(machine, recv=(9, 8, 0x1000, 0x808)) + model.handle(machine, pc=0x4019B00) + machine.write_mem(0x2000, b"\xab\xcd\xef\x01") + model.switch("rm", pc=0) + _port_call(machine, send=(1, 8, 0x2000, 4), recv=(2, 11, 0x3000, 0x100), + wait=2) + model.handle(machine, pc=0x5AD507C) + model.stopped = None + model.switch("init", pc=0) + _port_call(machine, wait=SHUTTLE_ANY) + handled, _ = model.handle(machine, pc=0x4019DF4) + self.assertTrue(handled) + self.assertEqual(machine.x[16], 9) + machine.write_mem(0x1000, b"\x7f") + _port_call(machine, send=(9, 0, 0x1000, 1), wait=9) + handled, _ = model.handle(machine, pc=0x401829C) + self.assertTrue(handled) + model.stopped = None + model.switch("rm", pc=0) + _port_call(machine, send=(1, 8, 0x2000, 4), recv=(2, 11, 0x3000, 0x100), + wait=2) + handled, _ = model.handle(machine, pc=0x5AD507C) + self.assertTrue(handled) + self.assertEqual(machine.x[16], 2) + self.assertEqual(machine.read_mem(0x3000, 1), b"\x7f") + # the resumed receive did not re-post a stale entry + self.assertNotIn(2, model.task_recvs["rm"]) + self.assertEqual(model.cross_task_payloads[-1], + {"port": 0, "size": 1, "from": "init", "to": "rm", + "kind": "reply"}) + + def test_task_state_query_serves_the_modelled_trap_state(self): + model = self._network() + machine = FakeMemoryMachine() + model.switch("init", pc=0) + model.host_kick() + _port_call(machine, recv=(10, 6, 0, 0)) + model.handle(machine, pc=0x4018038) + machine.set(5, INTERRUPT_ARM_EXTERNAL) + machine.set(10, 0xFF) + model.handle(machine, pc=0x4018054) + _port_call(machine, wait=SHUTTLE_ANY) + model.handle(machine, pc=0x4019DF4) + machine.set(5, 6) + machine.set(10, 1) + machine.set(11, 0x4000) + handled, result = model.handle(machine, pc=0x401A46C) + self.assertTrue(handled) + self.assertEqual(result, 0) + state = machine.read_mem(0x4000, 288) + self.assertEqual(int.from_bytes(state[256:264], "little"), SPURIOUS_XEPC) + self.assertEqual(int.from_bytes(state[264:272], "little"), + EXTERNAL_INTERRUPT_XCAUSE) + self.assertEqual(model.queries[-1]["task_index"], 1) + + def test_memory_query_and_shuttle_reset_are_services(self): + model = self._network() + machine = FakeMemoryMachine() + machine.set(5, 5) + machine.set(10, 0xFF) + machine.set(11, 0x1000) + handled, result = model.handle(machine, pc=0x5AC44FC) + self.assertTrue(handled) + self.assertEqual(result, 0) + self.assertEqual(machine.x[12], 0x1000) + self.assertEqual(machine.x[14], 1) + machine.set(5, 7) + machine.set(10, 1) + handled, result = model.handle(machine, pc=0x5AB7D8C) + self.assertTrue(handled) + self.assertEqual(result, 0) + self.assertEqual([s["service"] for s in model.services], + ["memory_query", "shuttle_reset"]) + + def test_network_state_is_byte_identical_across_runs(self): + def scenario(): + model = self._network() + machine = FakeMemoryMachine() + model.switch("init", pc=0) + _port_call(machine, recv=(9, 8, 0x1000, 0x808)) + model.handle(machine, pc=0x4019B00) + model.switch("rm", pc=0) + _port_call(machine, send=(1, 8, 0x2000, 4), + recv=(2, 11, 0x3000, 0x100), wait=2) + model.handle(machine, pc=0x5AD507C) + return model.state() + + first = scenario() + second = scenario() + self.assertEqual(json.dumps(first, sort_keys=True), + json.dumps(second, sort_keys=True)) + + if __name__ == "__main__": unittest.main() |
