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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
"""MMU (NVMPU) model tests: CSR semantics, translation, machine integration.
The model is exercised directly and through the machine's guest paths; the
real-firmware relocation-copy success is covered by
``tests/test_sim_devices.py`` (boot ladder). All fixtures are synthetic; no
hardware, module, guest or network is touched.
"""
from __future__ import annotations
import unittest
from sim.machine import Machine, R_EBREAK
from sim.mpu import (
MSTATUS_VM_NVMPU,
MMPUATTR,
MMPUIDX,
MMPUPA,
MMPURNG,
MMPUVA,
RiscvMpu,
)
KB = 1024
def enable(mpu: RiscvMpu) -> None:
mpu.on_csr_write(0x300, MSTATUS_VM_NVMPU << 24)
def entry(mpu: RiscvMpu, index: int, va: int, pa: int, rng: int, attr: int = 0x600) -> None:
mpu.on_csr_write(MMPUIDX, index)
mpu.on_csr_write(MMPUPA, pa)
mpu.on_csr_write(MMPURNG, rng)
mpu.on_csr_write(MMPUATTR, attr)
mpu.on_csr_write(MMPUVA, va | 1) # VLD
class RiscvMpuTests(unittest.TestCase):
def test_disabled_does_not_translate(self):
mpu = RiscvMpu()
entry(mpu, 0, 0x80000000, 0x1000, 0x1000)
self.assertFalse(mpu.enabled)
self.assertIsNone(mpu.translate(0x80000000))
def test_enable_is_the_nvmpu_vm_mode(self):
mpu = RiscvMpu()
entry(mpu, 0, 0x80000000, 0x1000, 0x1000)
mpu.on_csr_write(0x300, 0x1E000000)
self.assertTrue(mpu.enabled)
# Bare mode disables translation again.
mpu.on_csr_write(0x300, 0)
self.assertFalse(mpu.enabled)
self.assertIsNone(mpu.translate(0x80000000))
def test_translate_range_and_offset(self):
mpu = RiscvMpu()
entry(mpu, 2, 0x400E000, 0x1000, 0x3000)
enable(mpu)
self.assertEqual(mpu.translate(0x400E000), 0x1000)
self.assertEqual(mpu.translate(0x400E800), 0x1800)
self.assertEqual(mpu.translate(0x4010FFF), 0x3FFF)
self.assertIsNone(mpu.translate(0x400DFFF))
self.assertIsNone(mpu.translate(0x4011000))
def test_lowest_index_precedence(self):
mpu = RiscvMpu()
entry(mpu, 3, 0x1000, 0x9000, 0x1000)
entry(mpu, 0, 0x1000, 0x5000, 0x1000)
entry(mpu, 1, 0x1000, 0x7000, 0x1000)
enable(mpu)
self.assertEqual(mpu.translate(0x1000), 0x5000)
def test_vld_clear_disables_entry(self):
mpu = RiscvMpu()
entry(mpu, 0, 0x80000000, 0x1000, 0x1000)
enable(mpu)
self.assertIsNotNone(mpu.translate(0x80000000))
mpu.on_csr_write(MMPUIDX, 0)
mpu.on_csr_write(MMPUVA, 0) # VLD clear
self.assertIsNone(mpu.translate(0x80000000))
def test_index_field_is_six_bits(self):
mpu = RiscvMpu()
mpu.on_csr_write(MMPUIDX, 0x140) # 0x40 | 0x00 -> index 0
entry(mpu, 0, 0x1000, 0x2000, 0x1000)
enable(mpu)
self.assertEqual(mpu.translate(0x1000), 0x2000)
def test_state_and_reset(self):
mpu = RiscvMpu()
entry(mpu, 0, 0x80000000, 0x1000, 0x1000)
enable(mpu)
state = mpu.state()
self.assertTrue(state["enabled"])
self.assertEqual(state["entries"][0]["index"], 0)
self.assertEqual(state["entries"][0]["pa"], 0x1000)
mpu.reset()
self.assertFalse(mpu.enabled)
self.assertIsNone(mpu.translate(0x80000000))
class MachineMpuTests(unittest.TestCase):
def test_guest_load_store_through_mapping(self):
m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault")
m.write_mem(0x1000, b"\x11\x22\x33\x44\x55\x66\x77\x88")
entry(m.mpu, 0, 0x80000000, 0x1000, 0x1000)
enable(m.mpu)
self.assertEqual(m._guest_load(0x80000000, 8),
int.from_bytes(b"\x11\x22\x33\x44\x55\x66\x77\x88", "little"))
m._guest_store(0x80000004, 8, 0xAABBCCDD)
self.assertEqual(m.read_mem(0x1004, 4), b"\xdd\xcc\xbb\xaa")
# The debugger surface stays raw/strict: the mapped VA has no backing.
with self.assertRaises(Exception):
m.read_mem(0x80000000, 4)
def test_instruction_fetch_is_translated(self):
m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault")
program = bytes.fromhex("13057000" "73001000") # addi a0,zero,7; ebreak
m.write_mem(0x2000, program)
entry(m.mpu, 0, 0x80002000, 0x2000, 0x1000)
enable(m.mpu)
m.pc = 0x80002000 # virtual entry; the backing lives at PA 0x2000
reason, pc, _, _ = m.run(10)
self.assertEqual(reason, R_EBREAK)
self.assertEqual(m.x[10], 7)
self.assertEqual(pc, 0x80002004)
def test_unmapped_fetch_still_faults(self):
m = Machine(memory_mb=1, ram_base=0, unknown_mmio="fault")
entry(m.mpu, 0, 0x80002000, 0x2000, 0x1000)
enable(m.mpu)
m.pc = 0x90000000 # no entry covers it, no backing RAM
reason, _, _, _ = m.run(1)
self.assertNotEqual(reason, R_EBREAK)
class Wpr2RelativeOffsetTests(unittest.TestCase):
def test_window_relative_offsets_read_the_same_pair(self):
from sim.devices.fwsec import Wpr2Block
dev = Wpr2Block()
dev.set_range(0x2000000, 0x6000000)
self.assertEqual(dev.read(0x824, 4), dev.read(0x1FA824, 4))
self.assertEqual(dev.read(0x828, 4), dev.read(0x1FA828, 4))
self.assertIsNone(dev.read(0x820, 4))
class BootShimArgsTests(unittest.TestCase):
def test_bootstrap_args_written_and_image_mirror_absent(self):
from sim.gsp_boot import _apply_boot_shim, DEFAULT_IMAGE, DEFAULT_PROFILE
from greeneye.elf import load_elf
from sim.devices import load_profile
profile = load_profile(DEFAULT_PROFILE)
m = Machine(memory_mb=16, ram_base=0, unknown_mmio="lenient")
shim = dict(profile.get("boot_shim", {}))
shim["_image_path"] = DEFAULT_IMAGE
meta = {"enabled": True, "assumptions": []}
_apply_boot_shim(m, shim, load_elf(DEFAULT_IMAGE), meta)
args = profile["boot_shim"]["bootstrap_args"]
pa = int(args["pa"], 0)
import struct
fields = struct.unpack("<6Q", m.read_mem(pa, 48))
self.assertEqual(fields[0], int(args["imem_base_pa"], 0))
self.assertEqual(fields[1], int(args["imem_size"], 0))
self.assertEqual(fields[2], int(args["imem_bs_size"], 0))
self.assertEqual(fields[3], int(args["dmem_base_pa"], 0))
self.assertEqual(fields[4], int(args["dmem_size"], 0))
self.assertEqual(fields[5], int(args["dmem_bs_size"], 0))
# The DMEM image mirror hack is gone from this profile.
self.assertNotIn("image_at", shim)
self.assertNotIn("image_at", meta)
if __name__ == "__main__":
unittest.main()
|