summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_libos_port.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:57:20 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-06 19:57:20 -0400
commit23d8683f3012ac579db424735d1418814bb7ff01 (patch)
tree384cca6be54d7a5936544ff40e7715cf7751a4a9 /debug/risc-v-gsp-rm-vm/tests/test_libos_port.py
parent9de2a2a9899236b62b413ceb78946778fc6ada39 (diff)
downloadopen-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.py186
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()