summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/dma.py
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-03 14:05:38 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-03 14:05:38 -0400
commitcc79b478843f0230fe6aa8ecd822de7ba71c891e (patch)
tree5de760ddec4e45cc23d39ce7502635f5c0711217 /debug/risc-v-gsp-rm-vm/sim/devices/dma.py
parent40351167dd209012ab485e6164ce94f0b8965422 (diff)
downloadopen-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.py117
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()