summaryrefslogtreecommitdiffstats
path: root/firmware/57X-GSP/570.144/find_functions_final.py
diff options
context:
space:
mode:
authorOpen-IOV <dev@open-iov.org>2026-09-23 05:44:08 -0400
committerOpen-IOV <dev@open-iov.org>2026-09-23 05:46:43 -0400
commit68cdd7728a7cc6715bbbff7decfe2b11c3bdf558 (patch)
tree3dc8ce2e6af313a97ec6852f5c663a9a93665d2e /firmware/57X-GSP/570.144/find_functions_final.py
downloadopen-iov-68cdd7728a7cc6715bbbff7decfe2b11c3bdf558.tar.gz
open-iov-68cdd7728a7cc6715bbbff7decfe2b11c3bdf558.zip
Open-IOV: initial public release
Open-IOV virtualizes NVIDIA GPUs so that unmodified guest drivers run with real GPU functionality - CUDA first, 3D and video to follow - on unmodified hosts, without a proprietary vGPU license and without patching any driver, firmware or hypervisor. Core principle: emulate the interface, pass through the work. kernel/ open-iov-vfio.ko - host VFIO mdev driver: virtual GPU device (PCI config space, BAR shadows, MSI-X), GSP boot emulation, GSP-RM message-queue/RPC emulation, memory model (GPU-VA to guest-PA discovery) and the semaphore-completion service that advances guest UVM progress. userspace/ libiov.so management API and examples. tools/ guest diagnostics, firmware analysis wrappers, RM probes. scripts/ stack manager, VM harness, build/load/test automation. config/ hardware/version profiles (the portability surface). docs/ documentation tree - start at docs/README.md. wiki/ long-form firmware and protocol deep dives. Status: initialization works end to end for a stock 570.144 Linux guest on Turing (TU104): driver probe, nvidia-smi, the full GSP-RM control battery, object creation and the early CUDA/UVM start-up path. GPU work forwarding - the step that makes CUDA kernels execute on real hardware - is the active work item; see docs/04-implementation/01-known-knowns-and-unknowns.md. License: BSD-3-Clause for the project; GPL-2.0 for kernel modules that link GPL-only kernel interfaces. See LICENSE and docs/09-reference/legal-and-licensing.md.
Diffstat (limited to 'firmware/57X-GSP/570.144/find_functions_final.py')
-rw-r--r--firmware/57X-GSP/570.144/find_functions_final.py236
1 files changed, 236 insertions, 0 deletions
diff --git a/firmware/57X-GSP/570.144/find_functions_final.py b/firmware/57X-GSP/570.144/find_functions_final.py
new file mode 100644
index 0000000..aa073f7
--- /dev/null
+++ b/firmware/57X-GSP/570.144/find_functions_final.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python3
+"""
+Find GSP firmware functions using the actual calling convention:
+- Prologue: addi sp, sp, -N; sd s1-s11, offsets(sp); addi s0, sp, N
+- ra saved to callee-saved reg: mv sX, ra (or c.mv sX, ra)
+- Epilogue: mv ra, sX (or c.mv ra, sX); ret
+"""
+
+import struct
+import json
+import os
+
+FWIMAGE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gsp-570.144.fwimage.bin")
+OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))
+BASE_ADDR = 0x04000000
+
+# Executable sections
+EXEC_SECTIONS = [
+ (0x04005000, 0x04006413),
+ (0x04007000, 0x04008413),
+ (0x04009000, 0x04009073),
+ (0x0400a000, 0x0400cfff),
+ (0x0400e000, 0x04010fff),
+ (0x04018000, 0x0401cfff),
+ (0x04c58000, 0x05b21fff),
+]
+
+def read_binary(path):
+ with open(path, 'rb') as f:
+ return f.read()
+
+def is_in_exec(va):
+ for start, end in EXEC_SECTIONS:
+ if start <= va <= end:
+ return True
+ return False
+
+def next_inst(data, idx, max_bytes=1000):
+ """Yield (offset, instruction, is_32bit) for instructions starting at idx."""
+ end = min(idx + max_bytes, len(data))
+ i = idx
+ while i < end:
+ if i + 2 > len(data):
+ break
+ first16 = struct.unpack('<H', data[i:i+2])[0]
+ if (first16 & 0x3) != 0x3:
+ yield (i, first16, False)
+ i += 2
+ else:
+ if i + 4 <= len(data):
+ inst32 = struct.unpack('<I', data[i:i+4])[0]
+ yield (i, inst32, True)
+ i += 4
+ else:
+ break
+
+def decode_inst(inst, is_32bit):
+ """Return (mnemonic, operands_dict) or None."""
+ if not is_32bit:
+ # 16-bit compressed
+ if (inst & 0xF003) == 0x8002: # c.mv
+ rd = (inst >> 7) & 0x7
+ rs2 = (inst >> 10) & 0x7
+ return ('c.mv', {'rd': rd + 8, 'rs2': rs2 + 8})
+ if (inst & 0xE003) == 0x6001: # c.addi
+ rd = (inst >> 7) & 0x7
+ imm = ((inst >> 2) & 0x3F) | ((inst >> 12) & 0x1) << 6
+ if imm & 0x40: imm -= 0x80
+ return ('c.addi', {'rd': rd + 8, 'imm': imm})
+ return None
+ else:
+ opcode = inst & 0x7F
+ funct3 = (inst >> 12) & 0x7
+ rd = (inst >> 7) & 0x1F
+ rs1 = (inst >> 15) & 0x1F
+
+ if opcode == 0x13 and funct3 == 0: # addi
+ imm = (inst >> 20) & 0xFFF
+ if imm >= 0x800: imm -= 0x1000
+ return ('addi', {'rd': rd, 'rs1': rs1, 'imm': imm})
+
+ if opcode == 0x23 and funct3 == 3: # sd
+ rs2 = (inst >> 20) & 0x1F
+ offset = ((inst >> 7) & 0x1F) | ((inst >> 25) & 0x7F) << 5
+ return ('sd', {'rs1': rs1, 'rs2': rs2, 'offset': offset})
+
+ if opcode == 0x03 and funct3 == 3: # ld
+ offset = (inst >> 20) & 0xFFF
+ if offset >= 0x800: offset -= 0x1000
+ return ('ld', {'rd': rd, 'rs1': rs1, 'offset': offset})
+
+ if opcode == 0x6F: # jal
+ imm = ((inst >> 31) & 0x1) << 20 | ((inst >> 12) & 0xFF) << 12 | ((inst >> 20) & 0x1) << 11 | ((inst >> 21) & 0x3FF) << 1
+ if imm & 0x100000: imm -= 0x200000
+ return ('jal', {'rd': rd, 'imm': imm})
+
+ if opcode == 0x67 and funct3 == 0: # jalr
+ imm = (inst >> 20) & 0xFFF
+ if imm >= 0x800: imm -= 0x1000
+ return ('jalr', {'rd': rd, 'rs1': rs1, 'imm': imm})
+
+ if inst == 0x00008067: # ret
+ return ('ret', {})
+
+ return None
+
+def main():
+ print(f"Reading firmware...")
+ data = read_binary(FWIMAGE_PATH)
+ print(f"Binary size: {len(data)} bytes")
+
+ functions = []
+ i = 0
+ while i < len(data) - 4:
+ if not is_in_exec(BASE_ADDR + i):
+ i += 2
+ continue
+
+ # Look for function prologue: addi sp, sp, -N (N >= 32)
+ if i + 4 <= len(data):
+ inst32 = struct.unpack('<I', data[i:i+4])[0]
+ if (inst32 & 0x707F) == 0x0013:
+ rd = (inst32 >> 7) & 0x1F
+ rs1 = (inst32 >> 15) & 0x1F
+ if rd == 2 and rs1 == 2:
+ imm = (inst32 >> 20) & 0xFFF
+ if imm >= 0x800:
+ stack_size = -(imm - 0x1000)
+ if stack_size >= 32:
+ # Found potential prologue start
+ va = BASE_ADDR + i
+
+ # Scan forward to verify function structure
+ saves_s = 0
+ has_s0_setup = False
+ ra_save_reg = None
+ ra_restore_reg = None
+ has_ret = False
+ calls = 0
+
+ for offset, inst, is_32 in next_inst(data, i + 4, max_bytes=500):
+ dec = decode_inst(inst, is_32)
+ if not dec:
+ continue
+
+ mnemonic, ops = dec
+
+ # Count sd sX, offset(sp) - callee-saved saves
+ if mnemonic == 'sd' and ops['rs1'] == 2 and 9 <= ops['rs2'] <= 18:
+ saves_s += 1
+
+ # addi s0, sp, N (frame pointer setup)
+ if mnemonic == 'addi' and ops['rd'] == 8 and ops['rs1'] == 2:
+ if ops['imm'] == stack_size:
+ has_s0_setup = True
+
+ # mv sX, ra (save ra to callee-saved reg)
+ if mnemonic == 'addi' and ops['rd'] >= 9 and ops['rd'] <= 18 and ops['rs1'] == 1 and ops['imm'] == 0:
+ if ra_save_reg is None:
+ ra_save_reg = ops['rd']
+
+ # c.mv sX, ra
+ if mnemonic == 'c.mv' and ops['rs2'] == 1 and 8 <= ops['rd'] <= 15:
+ if ra_save_reg is None:
+ ra_save_reg = ops['rd']
+
+ # mv ra, sX (restore ra from callee-saved reg)
+ if mnemonic == 'addi' and ops['rd'] == 1 and ops['imm'] == 0 and 9 <= ops['rs1'] <= 18:
+ ra_restore_reg = ops['rs1']
+
+ # c.mv ra, sX
+ if mnemonic == 'c.mv' and ops['rd'] == 1 and 8 <= ops['rs2'] <= 15:
+ ra_restore_reg = ops['rs2']
+
+ # Function calls
+ if mnemonic in ('jal', 'jalr'):
+ calls += 1
+
+ # Return
+ if mnemonic == 'ret':
+ has_ret = True
+ break
+
+ # Validate: must have prologue elements and either:
+ # - saves ra to callee-saved reg (if it makes calls)
+ # - or is a leaf function (no calls)
+ if saves_s >= 2 and has_s0_setup and has_ret:
+ if calls == 0 or (ra_save_reg is not None and ra_restore_reg is not None):
+ functions.append({
+ "address": f"0x{va:08x}",
+ "stack_size": stack_size,
+ "saves": saves_s,
+ "ra_save_reg": ra_save_reg,
+ "ra_restore_reg": ra_restore_reg,
+ "calls": calls,
+ "type": "non_leaf" if calls > 0 else "leaf"
+ })
+ i += 4
+ continue
+
+ i += 2
+
+ print(f"Found {len(functions)} functions")
+
+ # Deduplicate
+ seen = set()
+ unique = []
+ for f in functions:
+ if f["address"] not in seen:
+ seen.add(f["address"])
+ unique.append(f)
+
+ unique.sort(key=lambda x: int(x["address"], 16))
+ print(f"Unique functions: {len(unique)}")
+
+ # Save
+ with open(f"{OUTPUT_DIR}/functions_final.json", "w") as f:
+ json.dump(unique, f, indent=2)
+
+ with open(f"{OUTPUT_DIR}/function_addresses_final.txt", "w") as f:
+ for func in unique:
+ f.write(f"{func['address']} stack={func['stack_size']} saves={func['saves']} calls={func['calls']} ra_save={func.get('ra_save_reg') or 'none'} ra_restore={func.get('ra_restore_reg') or 'none'} type={func['type']}\n")
+
+ # Stats
+ leaf = sum(1 for f in unique if f['type'] == 'leaf')
+ non_leaf = sum(1 for f in unique if f['type'] == 'non_leaf')
+ print(f"Leaf functions: {leaf}")
+ print(f"Non-leaf functions: {non_leaf}")
+
+ # Show first 50
+ print("\nFirst 50 functions:")
+ for f in unique[:50]:
+ print(f" {f['address']}: stack={f['stack_size']} saves={f['saves']} calls={f['calls']} ra_save={f.get('ra_save_reg') or 'none'} type={f['type']}")
+
+if __name__ == "__main__":
+ main()