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
|
"""DMA / address-aperture translation seam.
Evidence kind: **hardware address-map header + firmware use + observed
access pattern (through the falcon DMA registers).**
The GSP RISC-V address map (Turing) defines apertures the firmware and its
DMA engine address in GPU physical space:
* ``IMEM`` ``0x0_0000_0000`` (4 GiB window) -- local instruction memory
* ``DMEM`` ``0x1_0000_0000`` (4 GiB window) -- local data memory
* ``EMEM`` ``0x2_0000_0000`` (4 GiB window) -- external memory window
* ``FBGPA`` ``0x6180_0000_0000_0000`` -- framebuffer aperture
* ``SYSGPA````0x8180_0000_0000_0000`` -- system-memory aperture
* ``PRIV`` ``0x2000_0000_0000_0000`` -- register aperture
The simulator keeps local memory in one flat RAM region; framebuffer and
system memory only exist as *profile-declared windows* that map a GPU
physical aperture onto a simulator RAM address. A window not declared in the
profile is not modelled: the transfer is refused and recorded (never
silently zero-filled). This class is the single place where that mapping
lives, so a future IOMMU model can replace it without touching devices.
"""
from __future__ import annotations
class DmaTranslator:
"""Translate GPU physical addresses to simulator RAM through windows."""
def __init__(self, *, ram, ram_base: int, ram_size: int, windows=None):
self.ram = ram
self.ram_base = ram_base
self.ram_size = ram_size
self.windows: list[tuple[int, int, int]] = [] # (pa_base, sim_base, size)
self.transfers = 0
self.refused = 0
self.events: list[dict] = []
for win in windows or ():
self.add_window(int(win["pa"]), int(win["sim"]), int(win["size"]))
def add_window(self, pa_base: int, sim_base: int, size: int) -> None:
if size <= 0:
raise ValueError("DMA window size must be positive")
self.windows.append((pa_base, sim_base, size))
def translate(self, pa: int) -> int | None:
"""GPU physical address -> simulator address, or None if unmodelled."""
for pa_base, sim_base, size in reversed(self.windows):
if pa_base <= pa < pa_base + size:
return sim_base + (pa - pa_base)
return None
def read(self, pa: int, size: int) -> bytes | None:
sim = self.translate(pa)
if sim is None:
self._refuse(pa, size, "r")
return None
return self._read_sim(sim, size)
def write(self, pa: int, data: bytes) -> bool:
sim = self.translate(pa)
if sim is None:
self._refuse(pa, len(data), "w")
return False
self._write_sim(sim, data)
return True
def _in_ram(self, addr: int, size: int) -> bool:
off = addr - self.ram_base
return 0 <= off and off + size <= self.ram_size
def _read_sim(self, sim: int, size: int) -> bytes | None:
if not self._in_ram(sim, size):
self._refuse(sim, size, "r-sim")
return None
off = sim - self.ram_base
return bytes(self.ram[off:off + size])
def _write_sim(self, sim: int, data: bytes) -> bool:
if not self._in_ram(sim, len(data)):
self._refuse(sim, len(data), "w-sim")
return False
off = sim - self.ram_base
self.ram[off:off + len(data)] = data
return True
def copy(self, src_pa: int, dst_pa: int, size: int) -> bool:
"""Copy ``size`` bytes between GPU physical addresses.
A transfer is counted only when both the source read and the
destination write complete; a refused destination write leaves the
transfer in :attr:`refused` and not in :attr:`transfers`.
"""
data = self.read(src_pa, size)
if data is None:
return False
ok = self.write(dst_pa, data)
if ok:
self.transfers += 1
return ok
def _refuse(self, pa: int, size: int, kind: str) -> None:
self.refused += 1
if len(self.events) < 4096:
self.events.append({"pa": pa, "size": size, "kind": kind})
def state(self) -> dict:
return {
"windows": [{"pa": p, "sim": s, "size": n} for p, s, n in self.windows],
"transfers": self.transfers,
"refused": self.refused,
"events": list(self.events),
}
def reset(self) -> None:
self.transfers = 0
self.refused = 0
self.events.clear()
|