summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py114
1 files changed, 103 insertions, 11 deletions
diff --git a/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py b/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py
index 749157e..90643e9 100644
--- a/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py
+++ b/debug/risc-v-gsp-rm-vm/tests/test_msgq_attach.py
@@ -43,7 +43,9 @@ from sim.msgq_attach import ( # noqa: E402
RingGeometry,
assemble_report,
classify_ring_access,
+ image_tasks,
missing_semantic_after_handoff,
+ missing_semantic_after_second_task,
ring_geometry_from_host,
run_attach,
)
@@ -236,6 +238,69 @@ class ReportTests(unittest.TestCase):
max_cycles=MAX_RUN_CYCLES + 1)
with self.assertRaises(MsgqAttachError):
run_attach(image="/nonexistent/gsp-image.bin", max_cycles=True)
+ with self.assertRaises(MsgqAttachError):
+ run_attach(image="/nonexistent/gsp-image.bin",
+ second_task_budget=-1)
+ with self.assertRaises(MsgqAttachError):
+ run_attach(image="/nonexistent/gsp-image.bin",
+ second_task_budget=MAX_RUN_CYCLES + 1)
+
+
+class TaskTableTests(unittest.TestCase):
+ """Data-driven task discovery from the real image section table."""
+
+ @unittest.skipUnless(os.path.isfile(DEFAULT_IMAGE), "real GSP firmware absent")
+ def test_two_tasks_are_discovered_with_entries_and_stacks(self):
+ doc = image_tasks(DEFAULT_IMAGE)
+ self.assertEqual(doc["format"], "greeneye-image-tasks/1")
+ names = [task["name"] for task in doc["tasks"]]
+ self.assertEqual(names, ["init", "rm"])
+ for task in doc["tasks"]:
+ self.assertGreaterEqual(task["entry"], task["text_base"])
+ self.assertLess(task["entry"],
+ task["text_base"] + task["text_size"])
+ self.assertGreater(task["stack_top"], task["stack_base"])
+ self.assertGreater(task["text_size"], 0)
+ rm = doc["tasks"][1]
+ self.assertGreater(rm["text_size"], doc["tasks"][0]["text_size"])
+
+ def test_absent_image_yields_an_empty_table(self):
+ doc = image_tasks("/nonexistent/image.bin")
+ self.assertEqual(doc, {"format": "greeneye-image-tasks/1", "tasks": []})
+
+ def test_second_task_missing_semantic_is_precise(self):
+ doc = image_tasks(DEFAULT_IMAGE) if os.path.isfile(DEFAULT_IMAGE) \
+ else {"format": "greeneye-image-tasks/1",
+ "tasks": [{"name": "init"}, {"name": "rm"}]}
+ missing = missing_semantic_after_second_task(
+ tasks_doc=doc,
+ schedule=[{"task": "init", "reason": "port-wait-blocks"},
+ {"task": "rm", "reason": "scheduled"}],
+ frontier={"task": "rm", "reason": "second-task-slice-budget"},
+ polls=[])
+ self.assertEqual(missing["stage"], "second-task-frontier")
+ self.assertEqual(missing["polls"], 0)
+ self.assertEqual(missing["second_task"], "rm")
+ self.assertGreaterEqual(len(missing["missing"]), 3)
+ self.assertTrue(missing["next_step"])
+
+ def test_report_carries_tasks_and_schedule_additively(self):
+ report = assemble_report(
+ image=_image(), profile=_profile(), boot=_boot(True), attach={},
+ doorbell={"requested": True}, polls=[], divergence=None,
+ missing_mmio={"count": 0, "first": None},
+ determinism={"instructions": 1},
+ tasks={"format": "greeneye-image-tasks/1",
+ "tasks": [{"name": "init"}, {"name": "rm"}]},
+ schedule=[{"task": "rm", "reason": "scheduled"}],
+ )
+ self.assertEqual([task["name"] for task in report["tasks"]["tasks"]],
+ ["init", "rm"])
+ self.assertEqual(report["schedule"][0]["task"], "rm")
+ # the schema tag and the original keys are untouched
+ self.assertEqual(report["format"], REPORT_FORMAT)
+ self.assertIn("first_poll", report)
+ self.assertIn("missing_semantic", report)
class PortModelReportTests(unittest.TestCase):
@@ -285,12 +350,12 @@ class ImageTests(unittest.TestCase):
"""Bounded real-firmware run: deterministic, honest outcome only.
Limitation: the budget is deliberately small (600k instructions; boot-to-B3
- needs ~474k). If the firmware needs more, the report is expected to take
- the honest ``not-reached`` path with a non-empty divergence reason -- this
- test accepts either outcome and asserts only determinism, never success.
- With the default port model the run advances past the host wake to the
- external-interrupt dispatch and reports the exact unmodelled semantic
- (``task-state-query``); a fabricated poll is never accepted.
+ needs ~474k). The scheduled second task (the image-discovered RM task)
+ completes its boot port handshake inside the bounded slice and then runs
+ its own start-up past the cap, so the report is expected to take the
+ honest ``wait-reached-no-poll`` frontier path -- this test accepts any of
+ the allowed outcomes and asserts determinism, never success. A fabricated
+ poll is never accepted.
"""
def test_bounded_run_is_deterministic_and_honest(self):
@@ -306,6 +371,12 @@ class ImageTests(unittest.TestCase):
self.assertGreater(report["image"]["size"], 0)
self.assertEqual(len(report["image"]["sha256"]), 64)
self.assertTrue(report["port_model"]["enabled"])
+ self.assertEqual(report["port_model"]["format"],
+ "greeneye-msgq-port-network/1")
+ self.assertEqual([task["name"] for task in report["tasks"]["tasks"]],
+ ["init", "rm"])
+ self.assertTrue(report["schedule"])
+ self.assertIn("rm", [step.get("task") for step in report["schedule"]])
if not report["boot"]["message_loop_wait_reached"]:
self.assertIsNotNone(report["divergence"])
self.assertTrue(report["divergence"]["reason"])
@@ -313,17 +384,22 @@ class ImageTests(unittest.TestCase):
self.assertTrue(report["first_poll"]["in_ring"])
self.assertTrue(report["first_poll"]["kind"] != "other")
divergence = report["divergence"]
- if divergence and divergence.get("kind") == "task-state-query":
- # The port model delivered the doorbell wake (extintr shuttle
- # 10) and stopped at the libos trap/report path; the missing
- # semantic is named, and no poll is claimed.
+ if divergence and divergence.get("kind") == "second-task-frontier":
+ # The doorbell wake was consumed by the init task and the
+ # discovered RM task completed the boot handshake before its
+ # start-up ran past the bounded slice; the exact frontier is
+ # named and no poll is claimed.
self.assertIsNone(report["first_poll"])
self.assertIsNotNone(report["missing_semantic"])
self.assertEqual(report["missing_semantic"]["stage"],
- "after-extintr-handoff")
+ "second-task-frontier")
completed = [wait.get("completed")
for wait in report["port_model"].get("waits", [])]
self.assertIn(10, completed)
+ payloads = report["port_model"].get("cross_task_payloads", [])
+ self.assertTrue(any(item.get("from") == "rm"
+ and item.get("to") == "init"
+ for item in payloads))
self.assertEqual(first["boot"]["message_loop_wait_reached"],
second["boot"]["message_loop_wait_reached"])
self.assertEqual(first["boot"]["instructions_executed"],
@@ -334,9 +410,25 @@ class ImageTests(unittest.TestCase):
self.assertEqual(first["continuation"], second["continuation"])
self.assertEqual(first["port_model"], second["port_model"])
self.assertEqual(first["missing_semantic"], second["missing_semantic"])
+ self.assertEqual(first["tasks"], second["tasks"])
+ self.assertEqual(first["schedule"], second["schedule"])
self.assertEqual(first["determinism"]["report_sha256"],
second["determinism"]["report_sha256"])
+ def test_single_task_mode_keeps_the_handoff_classification(self):
+ report = run_attach(image=DEFAULT_IMAGE, max_cycles=SMALL_BUDGET,
+ doorbell=True, second_task=False)
+ self.assertIn(report["outcome"],
+ ("not-reached", "wait-reached-no-poll",
+ "wait-reached-poll-classified"))
+ self.assertEqual(report["port_model"]["format"], "greeneye-msgq-port/1")
+ self.assertFalse(report["schedule"])
+ divergence = report["divergence"]
+ if divergence and divergence.get("kind") == "task-state-query":
+ self.assertIsNone(report["first_poll"])
+ self.assertEqual(report["missing_semantic"]["stage"],
+ "after-extintr-handoff")
+
if __name__ == "__main__":
unittest.main()