diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-03 14:05:38 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-03 14:05:38 -0400 |
| commit | cc79b478843f0230fe6aa8ecd822de7ba71c891e (patch) | |
| tree | 5de760ddec4e45cc23d39ce7502635f5c0711217 /debug/risc-v-gsp-rm-vm/sim/devices/dma.py | |
| parent | 40351167dd209012ab485e6164ce94f0b8965422 (diff) | |
| download | open-iov-cc79b478843f0230fe6aa8ecd822de7ba71c891e.tar.gz open-iov-cc79b478843f0230fe6aa8ecd822de7ba71c891e.zip | |
vgsp: host CUDA bridge + sema release recovery; greeneye simulator wave
- kernel: gsp_cuda_bridge (QMD launch capture, debugfs job/out/done, param+output resolution cached from inline writes), sema drain fixes (never hold/starve a channel), GR ring autopin + counter autoreg, per-page push reads, counter-derive gate; module builds clean
- tools/cuda-bridge: CUDA driver-API daemon executing mail/vadd kernels on the physical GPU, CUBB2/CBD2 v2 protocol, layout check + self-tests
- tools/guest-diag + tools/cuda-ab: scanner/pin fixes, victory push-only mode
- debug/risc-v-gsp-rm-vm: Phase S simulator wave (GSP device model, boot ladder, triage/re-demo harnesses, docs 30-41), GPGPU-sim scaffolding
- STATE-CURRENT/docs updates
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/devices/dma.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/sim/devices/dma.py | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/devices/dma.py b/debug/risc-v-gsp-rm-vm/sim/devices/dma.py new file mode 100644 index 0000000..45d03e5 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/sim/devices/dma.py @@ -0,0 +1,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() |
