"""Golden-vector tests for the clean-room TU104 GMMU PTE/PDE decoder. Every vector is constructed field-by-field from the bit positions documented in ``sim/gmmu_pte.py`` (see ``docs/43-GMMU-PTE-DECODER.md``), then decoded and asserted field-by-field. The tests also pin the explicit failure behaviour: reserved bits are reported, unsupported encodings/levels error instead of guessing, empty and truncated blobs are handled deterministically, and decode output is repeatable. """ import contextlib import io import json import os import sys import tempfile import unittest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from sim.gmmu_pte import ( # noqa: E402 APERTURE_NAMES_PDE, APERTURE_NAMES_PTE, E_BAD_VALUE, E_TRUNCATED, E_UNSUPPORTED, GmmuDecodeError, KIND_VER2, KIND_VER2_DUAL, LEVELS, PTE_KIND_NAMES, SUPPORTED_LEVELS, decode_dual_pde, decode_entry, decode_pde, decode_pte, decode_table, main, ) def make_pte(*, valid=1, aperture=2, volatile=1, encrypted=0, privilege=0, read_only=0, atomic_disable=1, address=0, comptagline=0, peer_id=0, kind=0x06): value = 0 value |= (valid & 1) << 0 value |= (aperture & 3) << 1 value |= (volatile & 1) << 3 value |= (encrypted & 1) << 4 value |= (privilege & 1) << 5 value |= (read_only & 1) << 6 value |= (atomic_disable & 1) << 7 if aperture in (2, 3): value |= ((address >> 12) & ((1 << 46) - 1)) << 8 else: value |= ((address >> 12) & ((1 << 25) - 1)) << 8 value |= (peer_id & 7) << 33 value |= (comptagline & ((1 << 20) - 1)) << 36 value |= (kind & 0xFF) << 56 return value def make_pde(*, aperture=1, volatile=1, no_ats=0, address=0, peer_id=0): value = 0 value |= (aperture & 3) << 1 value |= (volatile & 1) << 3 value |= (no_ats & 1) << 5 value |= ((address >> 12) & ((1 << 46) - 1)) << 8 value |= (peer_id & 7) << 33 return value def make_dual(*, aperture_big=1, volatile_big=1, aperture_small=1, volatile_small=1, address_big=0, address_small=0, peer_big=0, peer_small=0, no_ats=0): value = 0 value |= (aperture_big & 3) << 1 value |= (volatile_big & 1) << 3 value |= ((address_big >> 8) & ((1 << 50) - 1)) << 4 value |= (peer_big & 7) << 33 value |= (aperture_small & 3) << 65 value |= (volatile_small & 1) << 67 value |= ((address_small >> 12) & ((1 << 46) - 1)) << 72 value |= (peer_small & 7) << 97 value |= (no_ats & 1) << 5 return value class GoldenVectorTests(unittest.TestCase): """Field-by-field constructed vectors must round-trip exactly.""" def test_pte_system_coherent_round_trip(self): pa = 0x123456789000 value = make_pte(valid=1, aperture=2, volatile=1, encrypted=0, privilege=1, read_only=1, atomic_disable=0, address=pa, kind=0x06) d = decode_pte(value) self.assertEqual(d["role"], "pte") self.assertTrue(d["valid"]) self.assertEqual(d["aperture"], 2) self.assertEqual(d["aperture_name"], "system_coherent_memory") self.assertTrue(d["volatile"]) self.assertFalse(d["encrypted"]) self.assertTrue(d["privilege"]) self.assertTrue(d["read_only"]) self.assertFalse(d["writable"]) self.assertFalse(d["atomic_disable"]) self.assertEqual(d["address"], pa) self.assertTrue(d["address_complete"]) self.assertEqual(d["kind"], 0x06) self.assertEqual(d["kind_name"], "GENERIC_MEMORY") self.assertTrue(d["kind_known"]) def test_pte_system_non_coherent_round_trip(self): pa = 0x2AAAAA0000 value = make_pte(aperture=3, address=pa, read_only=0, atomic_disable=1, kind=0x00) d = decode_pte(value) self.assertEqual(d["aperture_name"], "system_non_coherent_memory") self.assertEqual(d["address"], pa) self.assertTrue(d["writable"]) self.assertTrue(d["atomic_disable"]) self.assertEqual(d["kind_name"], "PITCH") def test_pte_video_aperture_round_trip(self): pa_low = 0x0ABCDEF000 value = make_pte(aperture=0, address=pa_low, comptagline=0x12345, kind=0x06) d = decode_pte(value) self.assertEqual(d["aperture_name"], "video_memory") self.assertEqual(d["address"], pa_low) self.assertFalse(d["address_complete"]) self.assertEqual(d["comptagline"], 0x12345) self.assertEqual(d["peer_id"], 0) def test_pte_peer_aperture_round_trip(self): pa_low = 0x0100000000 value = make_pte(aperture=1, address=pa_low, peer_id=5, kind=0x06) d = decode_pte(value) self.assertEqual(d["aperture_name"], "peer_memory") self.assertEqual(d["address"], pa_low) self.assertFalse(d["address_complete"]) self.assertEqual(d["peer_id"], 5) def test_pte_aperture_names_are_the_proven_four(self): self.assertEqual( APERTURE_NAMES_PTE, {0: "video_memory", 1: "peer_memory", 2: "system_coherent_memory", 3: "system_non_coherent_memory"}) for aperture in range(4): d = decode_pte(make_pte(aperture=aperture)) self.assertEqual(d["aperture_name"], APERTURE_NAMES_PTE[aperture]) def test_pde_video_pointer_round_trip(self): pa = 0x00000123456000 value = make_pde(aperture=1, volatile=1, no_ats=0, address=pa) d = decode_pde(value) self.assertEqual(d["role"], "pde") self.assertTrue(d["is_pde_pointer"]) self.assertFalse(d["is_leaf"]) self.assertEqual(d["aperture_name"], "video_memory") self.assertTrue(d["volatile"]) self.assertFalse(d["no_ats"]) self.assertEqual(d["address"], pa) self.assertFalse(d["address_complete"]) def test_pde_system_pointer_round_trip(self): pa = 0x1FFFF0000000 value = make_pde(aperture=2, volatile=0, no_ats=1, address=pa) d = decode_pde(value) self.assertEqual(d["aperture_name"], "system_coherent_memory") self.assertFalse(d["volatile"]) self.assertTrue(d["no_ats"]) self.assertEqual(d["address"], pa) self.assertTrue(d["address_complete"]) def test_pde_invalid_aperture_has_no_address(self): d = decode_pde(make_pde(aperture=0, volatile=0)) self.assertEqual(d["aperture_name"], "invalid") self.assertIsNone(d["address"]) self.assertFalse(d["address_complete"]) def test_pde_leaf_bit_is_rejected(self): with self.assertRaises(GmmuDecodeError) as ctx: decode_pde(1) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_dual_pde_halves_round_trip(self): addr_big = 0x0000001234560000 addr_small = 0x0000000FEDCBA000 value = make_dual(aperture_big=1, volatile_big=1, aperture_small=1, volatile_small=1, address_big=addr_big, address_small=addr_small) d = decode_dual_pde(value) self.assertEqual(d["role"], "dual_pde") self.assertTrue(d["is_pde_pointer"]) self.assertFalse(d["is_leaf"]) self.assertFalse(d["no_ats"]) self.assertEqual(d["big"]["aperture_name"], "video_memory") self.assertTrue(d["big"]["volatile"]) self.assertEqual(d["big"]["address"], addr_big) self.assertFalse(d["big"]["address_complete"]) self.assertEqual(d["small"]["aperture_name"], "video_memory") self.assertTrue(d["small"]["volatile"]) self.assertEqual(d["small"]["address"], addr_small) self.assertFalse(d["small"]["address_complete"]) def test_dual_pde_no_ats_bit_does_not_perturb_big_address(self): # The format names bit 5 NO_ATS inside the BIG address field; valid # depth-3 allocations are 4 KB aligned, so address bit 9 is zero and # the flag must decode without changing the reconstructed address. addr_big = 0x0000001234560000 d = decode_dual_pde(make_dual(address_big=addr_big, no_ats=1)) self.assertTrue(d["no_ats"]) self.assertEqual(d["big"]["address"], addr_big) self.assertEqual(d["big"]["address_vid"] & (1 << 1), 1 << 1) def test_dual_pde_system_halves_are_complete(self): value = make_dual(aperture_big=2, aperture_small=3, address_big=0x1000000000, address_small=0x2000000000) d = decode_dual_pde(value) self.assertEqual(d["big"]["aperture_name"], "system_coherent_memory") self.assertTrue(d["big"]["address_complete"]) self.assertEqual(d["big"]["address"], 0x1000000000) self.assertEqual(d["small"]["aperture_name"], "system_non_coherent_memory") self.assertTrue(d["small"]["address_complete"]) self.assertEqual(d["small"]["address"], 0x2000000000) def test_dual_pde_peer_ids_round_trip(self): value = make_dual(aperture_big=1, aperture_small=1, peer_big=3, peer_small=6) d = decode_dual_pde(value) self.assertEqual(d["big"]["peer_id"], 3) self.assertEqual(d["small"]["peer_id"], 6) def test_pde_aperture_names_are_the_proven_four(self): self.assertEqual( APERTURE_NAMES_PDE, {0: "invalid", 1: "video_memory", 2: "system_coherent_memory", 3: "system_non_coherent_memory"}) for aperture in range(4): d = decode_pde(make_pde(aperture=aperture)) self.assertEqual(d["aperture_name"], APERTURE_NAMES_PDE[aperture]) def test_known_kind_names_spot_values(self): self.assertEqual(PTE_KIND_NAMES[0x06], "GENERIC_MEMORY") self.assertEqual(PTE_KIND_NAMES[0x00], "PITCH") self.assertEqual(PTE_KIND_NAMES[0x07], "INVALID") self.assertEqual(PTE_KIND_NAMES[0x0F], "SMSKED_MESSAGE") self.assertEqual(PTE_KIND_NAMES[0xFE], "GENERIC_16BX2") self.assertEqual(len(PTE_KIND_NAMES), 243) def test_unknown_kind_field_is_reported_not_guessed(self): value = make_pte(kind=0xFF) d = decode_pte(value) self.assertEqual(d["kind"], 0xFF) self.assertIsNone(d["kind_name"]) self.assertFalse(d["kind_known"]) def test_level_metadata(self): self.assertEqual([lv["depth"] for lv in LEVELS], list(SUPPORTED_LEVELS)) self.assertEqual(decode_pte(make_pte(), level=3)["page_size"], "2m") self.assertEqual(decode_pte(make_pte(), level=4)["page_size"], "4k_or_64k") self.assertEqual(decode_pte(make_pte())["level_candidates"], [3, 4]) class ReservedBitTests(unittest.TestCase): """Set bits with no named field must be reported, never dropped.""" def test_pde_all_bits_set_reports_exact_reserved_set(self): d = decode_pde(((1 << 64) - 1) & ~1) # bit 0 clear = directory entry self.assertEqual(d["reserved_bits"], [4, 6, 7] + list(range(54, 64))) self.assertEqual(d["reserved_mask"], sum(1 << b for b in d["reserved_bits"])) def test_pde_named_bits_are_not_reported(self): d = decode_pde(make_pde(aperture=2, volatile=1, no_ats=1, address=0x12345000)) self.assertEqual(d["reserved_bits"], []) def test_dual_pde_all_bits_set_reports_exact_reserved_set(self): d = decode_dual_pde((1 << 128) - 1) expected = list(range(54, 65)) + list(range(68, 72)) + \ list(range(118, 128)) self.assertEqual(d["reserved_bits"], expected) self.assertEqual(d["reserved_mask"], sum(1 << b for b in expected)) def test_dual_pde_unnamed_small_half_bit_64_is_reported(self): d = decode_dual_pde(1 << 64) self.assertEqual(d["reserved_bits"], [64]) def test_pte_has_no_reserved_bits(self): d = decode_pte((1 << 64) - 1) self.assertEqual(d["reserved_bits"], []) self.assertEqual(d["reserved_mask"], 0) class ErrorBehaviourTests(unittest.TestCase): """Unsupported requests error; they never guess.""" def test_unknown_encoding_kind_errors(self): with self.assertRaises(GmmuDecodeError) as ctx: decode_pte(0, pte_kind="legacy") self.assertEqual(ctx.exception.code, E_UNSUPPORTED) with self.assertRaises(GmmuDecodeError) as ctx: decode_pde(0, pde_kind="ver1") self.assertEqual(ctx.exception.code, E_UNSUPPORTED) with self.assertRaises(GmmuDecodeError) as ctx: decode_table(b"", base_pa=0, kind="nope") self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_wrong_role_for_encoding_errors(self): with self.assertRaises(GmmuDecodeError) as ctx: decode_pte(0, pte_kind=KIND_VER2_DUAL) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) with self.assertRaises(GmmuDecodeError) as ctx: decode_dual_pde(0, pde_kind=KIND_VER2) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_unsupported_levels_error(self): value = make_pde().to_bytes(8, "little") for bad in (5, 99, -1, (0, 7)): with self.assertRaises(GmmuDecodeError) as ctx: decode_table(value, base_pa=0, levels=bad) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) with self.assertRaises(GmmuDecodeError) as ctx: decode_pte(0, level=0) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_entry_size_must_match_kind(self): with self.assertRaises(GmmuDecodeError) as ctx: decode_table(b"\x00" * 8, base_pa=0, kind=KIND_VER2_DUAL) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) with self.assertRaises(GmmuDecodeError) as ctx: decode_table(b"\x00" * 16, base_pa=0, entry_size=16) self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_bad_values_error(self): with self.assertRaises(GmmuDecodeError) as ctx: decode_pte(1 << 64) self.assertEqual(ctx.exception.code, E_BAD_VALUE) with self.assertRaises(GmmuDecodeError) as ctx: decode_dual_pde(-1) self.assertEqual(ctx.exception.code, E_BAD_VALUE) with self.assertRaises(GmmuDecodeError) as ctx: decode_table(b"", base_pa=-1) self.assertEqual(ctx.exception.code, E_BAD_VALUE) class TableDecodeTests(unittest.TestCase): def test_empty_blob_is_empty_list(self): self.assertEqual(decode_table(b"", base_pa=0x1000), []) self.assertEqual(decode_table(b"", base_pa=0x1000, kind=KIND_VER2_DUAL, entry_size=16), []) def test_short_blob_errors(self): for blob in (b"\x01", b"\x01\x02\x03", b"\x00" * 4, b"\x00" * 12): with self.assertRaises(GmmuDecodeError) as ctx: decode_table(blob, base_pa=0) self.assertEqual(ctx.exception.code, E_TRUNCATED) with self.assertRaises(GmmuDecodeError) as ctx: decode_table(b"\x00" * 24, base_pa=0, kind=KIND_VER2_DUAL, entry_size=16) self.assertEqual(ctx.exception.code, E_TRUNCATED) def test_two_entry_table_round_trip_and_addresses(self): pte = make_pte(aperture=2, address=0x40000000) pde = make_pde(aperture=1, address=0x50000000) blob = pte.to_bytes(8, "little") + pde.to_bytes(8, "little") entries = decode_table(blob, base_pa=0x1000) self.assertEqual(len(entries), 2) self.assertEqual(entries[0]["index"], 0) self.assertEqual(entries[0]["entry_pa"], 0x1000) self.assertEqual(entries[0]["role"], "pte") self.assertEqual(entries[0]["address"], 0x40000000) self.assertEqual(entries[1]["index"], 1) self.assertEqual(entries[1]["entry_pa"], 0x1008) self.assertEqual(entries[1]["role"], "pde") self.assertEqual(entries[1]["address"], 0x50000000) def test_auto_role_uses_bit_zero_and_reports_inference(self): entries = decode_table(make_pte().to_bytes(8, "little"), base_pa=0) self.assertEqual(entries[0]["role"], "pte") self.assertTrue(entries[0]["role_inferred"]) self.assertIsNone(entries[0]["level"]) self.assertEqual(entries[0]["level_candidates"], [3, 4]) entries = decode_table(make_pde().to_bytes(8, "little"), base_pa=0) self.assertEqual(entries[0]["role"], "pde") self.assertTrue(entries[0]["role_inferred"]) self.assertEqual(entries[0]["level_candidates"], [0, 1, 2, 3]) def test_pinned_level_selects_role(self): pte = make_pte() pde = make_pde() leaf = decode_table(pte.to_bytes(8, "little"), base_pa=0, levels=3) self.assertEqual((leaf[0]["role"], leaf[0]["level"]), ("pte", 3)) self.assertEqual(leaf[0]["page_size"], "2m") ptr = decode_table(pde.to_bytes(8, "little"), base_pa=0, levels=3) self.assertEqual((ptr[0]["role"], ptr[0]["level"]), ("pde", 3)) upper = decode_table(pde.to_bytes(8, "little"), base_pa=0, levels=1) self.assertEqual((upper[0]["role"], upper[0]["level"]), ("pde", 1)) page = decode_table(pte.to_bytes(8, "little"), base_pa=0, levels=4) self.assertEqual((page[0]["role"], page[0]["level"]), ("pte", 4)) def test_level_sequence_narrows_candidates(self): entries = decode_table(make_pte().to_bytes(8, "little"), base_pa=0, levels=(3, 4)) self.assertEqual(entries[0]["level_candidates"], [3, 4]) entries = decode_table(make_pde().to_bytes(8, "little"), base_pa=0, levels=(3, 4)) self.assertEqual(entries[0]["level_candidates"], [3]) def test_dual_table_entry(self): value = make_dual(address_big=0x1000, address_small=0x2000) entries = decode_table(value.to_bytes(16, "little"), base_pa=0x2000, kind=KIND_VER2_DUAL, entry_size=16) self.assertEqual(len(entries), 1) self.assertEqual(entries[0]["role"], "dual_pde") self.assertEqual(entries[0]["entry_pa"], 0x2000) self.assertEqual(entries[0]["encoding"], KIND_VER2_DUAL) self.assertFalse(entries[0]["role_inferred"]) self.assertEqual(entries[0]["big"]["address"], 0x1000) self.assertEqual(entries[0]["small"]["address"], 0x2000) def test_decode_entry_explicit_roles(self): pte = decode_entry(make_pte(), role="pte") self.assertEqual(pte["role"], "pte") pde = decode_entry(make_pde(), role="pde") self.assertEqual(pde["role"], "pde") dual = decode_entry(make_dual(), kind=KIND_VER2_DUAL, role="dual_pde") self.assertEqual(dual["role"], "dual_pde") with self.assertRaises(GmmuDecodeError) as ctx: decode_entry(0, role="bogus") self.assertEqual(ctx.exception.code, E_UNSUPPORTED) def test_output_is_deterministic(self): blob = (make_pte(aperture=2, address=0x1000).to_bytes(8, "little") + make_pde(aperture=1, address=0x2000).to_bytes(8, "little") + make_dual(address_big=0x3000, address_small=0x4000).to_bytes(16, "little")) first = decode_table(blob, base_pa=0x1000) second = decode_table(blob, base_pa=0x1000) self.assertEqual(first, second) self.assertEqual(json.dumps(first, sort_keys=True), json.dumps(second, sort_keys=True)) def test_blob_accepts_bytearray_and_memoryview(self): pte = make_pte(aperture=2, address=0x1000).to_bytes(8, "little") for blob in (bytearray(pte), memoryview(pte)): entries = decode_table(blob, base_pa=0) self.assertEqual(entries[0]["address"], 0x1000) class CliTests(unittest.TestCase): def test_value_json(self): buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = main(["--value", hex(make_pte(aperture=2, address=0x4000)), "--role", "pte", "--json"]) self.assertEqual(rc, 0) data = json.loads(buf.getvalue()) self.assertEqual(len(data), 1) self.assertEqual(data[0]["role"], "pte") self.assertEqual(data[0]["address"], 0x4000) def test_value_level_is_honoured(self): buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = main(["--value", hex(make_pte()), "--role", "pte", "--levels", "3", "--json"]) self.assertEqual(rc, 0) data = json.loads(buf.getvalue()) self.assertEqual(data[0]["level"], 3) self.assertEqual(data[0]["page_size"], "2m") err = io.StringIO() with contextlib.redirect_stderr(err): rc = main(["--value", hex(make_pte()), "--role", "pte", "--levels", "3,4"]) self.assertEqual(rc, 2) self.assertIn(E_BAD_VALUE, err.getvalue()) def test_blob_json(self): blob = (make_pte(aperture=2, address=0x1000).to_bytes(8, "little") + make_pde(aperture=1, address=0x2000).to_bytes(8, "little")) with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "table.bin") with open(path, "wb") as handle: handle.write(blob) buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = main(["--blob", path, "--base-pa", "0x1000", "--json"]) self.assertEqual(rc, 0) data = json.loads(buf.getvalue()) self.assertEqual([e["role"] for e in data], ["pte", "pde"]) self.assertEqual(data[1]["entry_pa"], 0x1008) def test_text_output_is_deterministic(self): value = hex(make_pte(aperture=2, address=0x4000)) outputs = [] for _ in range(2): buf = io.StringIO() with contextlib.redirect_stdout(buf): rc = main(["--value", value, "--role", "pte"]) self.assertEqual(rc, 0) outputs.append(buf.getvalue()) self.assertEqual(outputs[0], outputs[1]) self.assertIn("role=pte", outputs[0]) def test_truncated_blob_exits_two(self): with tempfile.TemporaryDirectory() as tmp: path = os.path.join(tmp, "short.bin") with open(path, "wb") as handle: handle.write(b"\x01\x02\x03") err = io.StringIO() with contextlib.redirect_stderr(err): rc = main(["--blob", path, "--base-pa", "0x1000"]) self.assertEqual(rc, 2) self.assertIn(E_TRUNCATED, err.getvalue()) def test_unsupported_level_exits_two(self): err = io.StringIO() with contextlib.redirect_stderr(err): rc = main(["--value", "0x1", "--role", "pde", "--levels", "9"]) self.assertEqual(rc, 2) self.assertIn(E_UNSUPPORTED, err.getvalue()) if __name__ == "__main__": unittest.main()