From 68cdd7728a7cc6715bbbff7decfe2b11c3bdf558 Mon Sep 17 00:00:00 2001 From: Open-IOV Date: Wed, 23 Sep 2026 05:44:08 -0400 Subject: 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. --- firmware/57X-GSP/570.144/find_functions_final.py | 236 +++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 firmware/57X-GSP/570.144/find_functions_final.py (limited to 'firmware/57X-GSP/570.144/find_functions_final.py') 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('> 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('> 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() -- cgit v1.2.3