diff options
| author | Open-IOV <dev@open-iov.org> | 2026-09-23 05:44:08 -0400 |
|---|---|---|
| committer | Open-IOV <dev@open-iov.org> | 2026-09-23 05:46:43 -0400 |
| commit | 68cdd7728a7cc6715bbbff7decfe2b11c3bdf558 (patch) | |
| tree | 3dc8ce2e6af313a97ec6852f5c663a9a93665d2e /firmware/57X-GSP/570.144/decompile_functions.py | |
| download | open-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/decompile_functions.py')
| -rw-r--r-- | firmware/57X-GSP/570.144/decompile_functions.py | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/firmware/57X-GSP/570.144/decompile_functions.py b/firmware/57X-GSP/570.144/decompile_functions.py new file mode 100644 index 0000000..6e27d05 --- /dev/null +++ b/firmware/57X-GSP/570.144/decompile_functions.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +""" +Python-based decompilation of key GSP firmware functions. +Uses our decoded instruction stream to generate pseudo-C. +""" + +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 + +# Key functions to decompile +TARGET_FUNCTIONS = [ + (0x057a1ac0, "rpc_dispatcher_primary"), + (0x04ed39dc, "rpc_dispatcher_secondary"), + (0x04cc58f8, "rpc_dispatcher_tertiary"), + (0x05723ef4, "rpc_dispatcher_quaternary"), + (0x04cb8189, "rpc_bar_trap_handler"), + (0x04009000, "entry_point"), + (0x057e94ec, "rpc_alloc_handler"), + (0x04d00a18, "rpc_alloc_handler_2"), + (0x051efbf4, "rpc_schedule_handler"), + (0x04efeac8, "top_call_target"), +] + +def read_binary(path): + with open(path, 'rb') as f: + return f.read() + +def decode_instruction(data, offset): + """Decode instruction at offset, return (inst_dict, size)""" + if offset + 4 > len(data): + return None, 0 + first16 = struct.unpack('<H', data[offset:offset+2])[0] + if (first16 & 0x3) != 0x3: + return decode_compressed(first16, offset), 2 + if offset + 4 <= len(data): + inst = struct.unpack('<I', data[offset:offset+4])[0] + return decode_32bit(inst, offset), 4 + return None, 0 + +def decode_compressed(inst, offset): + if (inst & 0xF003) == 0x8002: + rd = (inst >> 7) & 0x7; rs2 = (inst >> 10) & 0x7 + return {'mnemonic': 'c.mv', 'rd': rd+8, 'rs2': rs2+8, 'str': f'c.mv s{rd}, s{rs2}'} + if (inst & 0xE003) == 0x6001: + rd = (inst >> 7) & 0x7 + imm = ((inst >> 2) & 0x3F) | ((inst >> 12) & 0x1) << 6 + if imm & 0x40: imm -= 0x80 + return {'mnemonic': 'c.addi', 'rd': rd+8, 'imm': imm, 'str': f'c.addi s{rd}, {imm}'} + if (inst & 0xF07F) == 0x8067: + rs1 = (inst >> 7) & 0x1F + return {'mnemonic': 'c.jalr', 'rs1': rs1, 'str': f'c.jalr x{rs1}'} + if (inst & 0xE003) == 0x6003: + rd = (inst >> 10) & 0x7; rs1 = (inst >> 7) & 0x7 + off = ((inst >> 2) & 0x1F) | ((inst >> 13) & 0x3) << 5 + return {'mnemonic': 'c.ld', 'rd': rd+8, 'rs1': rs1+8, 'offset': off*8, 'str': f'c.ld s{rd}, {off*8}(s{rs1})'} + if (inst & 0xE003) == 0xE000: + rs1 = (inst >> 7) & 0x7; rs2 = (inst >> 10) & 0x7 + off = ((inst >> 2) & 0x1F) | ((inst >> 13) & 0x3) << 5 + return {'mnemonic': 'c.sd', 'rs1': rs1+8, 'rs2': rs2+8, 'offset': off*8, 'str': f'c.sd s{rs2}, {off*8}(s{rs1})'} + return {'mnemonic': 'c.unknown', 'raw': inst, 'str': f'c.0x{inst:04x}'} + +def decode_32bit(inst, offset): + opcode = inst & 0x7F + funct3 = (inst >> 12) & 0x7 + rd = (inst >> 7) & 0x1F + rs1 = (inst >> 15) & 0x1F + rs2 = (inst >> 20) & 0x1F + reg_name = lambda r: ['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][r] if r < 32 else f'x{r}' + + def fmt_imm(imm): return str(imm) if imm >= 0 else str(imm) + + if opcode == 0x13 and funct3 == 0: # addi + imm = (inst >> 20) & 0xFFF + if imm >= 0x800: imm -= 0x1000 + return {'mnemonic': 'addi', 'rd': rd, 'rs1': rs1, 'imm': imm, 'str': f'addi {reg_name(rd)}, {reg_name(rs1)}, {imm}'} + + if opcode == 0x23 and funct3 == 3: # sd + off = ((inst >> 7) & 0x1F) | ((inst >> 25) & 0x7F) << 5 + return {'mnemonic': 'sd', 'rs1': rs1, 'rs2': rs2, 'offset': off, 'str': f'sd {reg_name(rs2)}, {off}({reg_name(rs1)})'} + + if opcode == 0x03 and funct3 == 3: # ld + off = (inst >> 20) & 0xFFF + if off >= 0x800: off -= 0x1000 + return {'mnemonic': 'ld', 'rd': rd, 'rs1': rs1, 'offset': off, 'str': f'ld {reg_name(rd)}, {off}({reg_name(rs1)})'} + + 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 + target = BASE_ADDR + offset + imm + return {'mnemonic': 'jal', 'rd': rd, 'imm': imm, 'target': target, 'str': f'jal {reg_name(rd)}, 0x{target:08x}'} + + if opcode == 0x67 and funct3 == 0: # jalr + imm = (inst >> 20) & 0xFFF + if imm >= 0x800: imm -= 0x1000 + return {'mnemonic': 'jalr', 'rd': rd, 'rs1': rs1, 'imm': imm, 'str': f'jalr {reg_name(rd)}, {imm}({reg_name(rs1)})'} + + if opcode == 0x63: # branch + imm = ((inst >> 31) & 0x1) << 12 | ((inst >> 7) & 0x1) << 11 | ((inst >> 25) & 0x3F) << 5 | ((inst >> 8) & 0xF) << 1 + if imm & 0x1000: imm -= 0x2000 + target = BASE_ADDR + offset + imm + btype = ['beq','bne','?','?','blt','bge','bltu','bgeu'][funct3] + return {'mnemonic': btype, 'rs1': rs1, 'rs2': rs2, 'imm': imm, 'target': target, 'str': f'{btype} {reg_name(rs1)}, {reg_name(rs2)}, 0x{target:08x}'} + + if opcode == 0x17: # auipc + imm = (inst >> 12) & 0xFFFFF + return {'mnemonic': 'auipc', 'rd': rd, 'imm': imm, 'str': f'auipc {reg_name(rd)}, 0x{imm:05x}'} + + if opcode == 0x37: # lui + imm = (inst >> 12) & 0xFFFFF + return {'mnemonic': 'lui', 'rd': rd, 'imm': imm, 'str': f'lui {reg_name(rd)}, 0x{imm:05x}'} + + if opcode == 0x33: # arithmetic + funct7 = (inst >> 25) & 0x7F + ops = {0:'add', 0x20:'sub', 1:'sll', 4:'xor', 5:'srl', 0x25:'sra', 6:'or', 7:'and'} + op = ops.get(funct7, f'arith{funct7}') + return {'mnemonic': op, 'rd': rd, 'rs1': rs1, 'rs2': rs2, 'str': f'{op} {reg_name(rd)}, {reg_name(rs1)}, {reg_name(rs2)}'} + + if opcode == 0x0F: # fence + return {'mnemonic': 'fence', 'str': 'fence'} + + if opcode == 0x73: # system + return {'mnemonic': 'ecall/ebreak', 'str': 'ecall/ebreak'} + + return {'mnemonic': 'unknown', 'raw': inst, 'str': f'0x{inst:08x}'} + +def trace_function(data, start_offset, max_bytes=2000): + """Trace a function and return instruction list""" + instructions = [] + i = 0 + while i < max_bytes: + offset = start_offset + i + if offset >= len(data): break + inst_info, size = decode_instruction(data, offset) + if inst_info is None: + i += 2; continue + inst_info['offset'] = BASE_ADDR + offset + inst_info['rel_offset'] = i + instructions.append(inst_info) + i += size + + # Stop at return + if inst_info['mnemonic'] in ('jalr', 'c.jalr') and inst_info.get('rd', 1) == 0 and inst_info.get('imm', 0) == 0 and inst_info.get('rs1', 1) == 1: + break + return instructions + +def analyze_function(instructions, func_name): + """Analyze instructions for patterns""" + analysis = { + 'name': func_name, + 'stack_size': None, + 'ra_save_reg': None, + 'ra_restore_reg': None, + 'calls': [], + 'rpc_refs': [], + 'strings': [], + } + + RPC_TYPES = {1:'ALLOC',2:'FREE',3:'MAP',4:'UNMAP',5:'PIN',6:'UNPIN',7:'SCHEDULE',8:'YIELD',9:'NOTIFY',10:'BAR_TRAP',11:'MIGRATE',0xFF:'DEBUG'} + + for inst in instructions: + mnemonic = inst.get('mnemonic', '') + + # Prologue + if mnemonic == 'addi' and inst.get('rd') == 2 and inst.get('rs1') == 2 and inst.get('imm', 0) < -16: + analysis['stack_size'] = -inst['imm'] + + # Save ra + if mnemonic == 'addi' and inst.get('rs1') == 1 and inst.get('imm') == 0 and 9 <= inst.get('rd', 0) <= 18: + if analysis['ra_save_reg'] is None: + analysis['ra_save_reg'] = inst['rd'] + if mnemonic == 'c.mv' and inst.get('rs2') == 1 and 8 <= inst.get('rd', 0) <= 15: + if analysis['ra_save_reg'] is None: + analysis['ra_save_reg'] = inst['rd'] + + # Restore ra + if mnemonic == 'addi' and inst.get('rd') == 1 and inst.get('imm') == 0 and 9 <= inst.get('rs1', 0) <= 18: + analysis['ra_restore_reg'] = inst['rs1'] + if mnemonic == 'c.mv' and inst.get('rd') == 1 and 8 <= inst.get('rs2', 0) <= 15: + analysis['ra_restore_reg'] = inst['rs2'] + + # Calls + if mnemonic == 'jal': + analysis['calls'].append(inst.get('target')) + + # RPC type refs + if mnemonic in ('addi', 'andi', 'ori') and inst.get('imm') in RPC_TYPES: + analysis['rpc_refs'].append((inst['rel_offset'], RPC_TYPES[inst['imm']])) + + if mnemonic in ('beq','bne','blt','bge','bltu','bgeu') and inst.get('rs1') != 0: + # Could be comparing register with RPC type + pass + + return analysis + +def generate_pseudo_c(instructions, func_name, analysis): + """Generate pseudo-C from instructions""" + lines = [f"// {func_name}", f"// Stack: {analysis['stack_size'] or 'unknown'} bytes", f"// RA save: s{analysis['ra_save_reg']-8 if analysis['ra_save_reg'] else '?'}", ""] + + # Track stack slots + stack_vars = {} + var_counter = 0 + + for inst in instructions: + mnemonic = inst.get('mnemonic', '') + rel = inst['rel_offset'] + addr = inst['offset'] + + # Add label for branch targets + lines.append(f"L_{rel:04x}: // 0x{addr:08x}") + + if mnemonic == 'addi': + rd = inst['rd']; rs1 = inst['rs1']; imm = inst['imm'] + if rd == 2 and rs1 == 2 and imm < 0: + lines.append(f" sp = sp + {imm}; // allocate stack frame ({-imm} bytes)") + elif rd == 8 and rs1 == 2: + lines.append(f" s0 = sp + {imm}; // frame pointer") + elif rs1 == 1 and imm == 0 and rd >= 9: + lines.append(f" s{rd-8} = ra; // save return address") + elif rd == 1 and imm == 0 and rs1 >= 9: + lines.append(f" ra = s{rs1-8}; // restore return address") + else: + regs = ['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'] + lines.append(f" {regs[rd]} = {regs[rs1]} + {imm};") + + elif mnemonic == 'sd': + lines.append(f" *(uint64_t*)({inst['rs1']} + {inst['offset']}) = {['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][inst['rs2']]};") + + elif mnemonic == 'ld': + lines.append(f" {['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][inst['rd']]} = *(uint64_t*)({['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][inst['rs1']]} + {inst['offset']});") + + elif mnemonic == 'jal': + target = inst.get('target', 0) + lines.append(f" ra = 0x{addr+4:08x}; // return address") + lines.append(f" goto 0x{target:08x}; // call FUNC_{target:08X}") + + elif mnemonic == 'jalr': + if inst.get('rd') == 0 and inst.get('imm') == 0 and inst.get('rs1') == 1: + lines.append(f" return; // ret") + else: + lines.append(f" ra = 0x{addr+4:08x}; goto *({['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][inst['rs1']]} + {inst['imm']});") + + elif mnemonic in ('beq','bne','blt','bge','bltu','bgeu'): + target = inst.get('target', 0) + regs = ['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'] + lines.append(f" if ({regs[inst['rs1']]} {mnemonic} {regs[inst['rs2']]}) goto 0x{target:08x};") + + elif mnemonic in ('auipc', 'lui'): + lines.append(f" {['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'][inst['rd']]} = {mnemonic} 0x{inst['imm']:05x};") + + elif mnemonic in ('add','sub','sll','xor','srl','sra','or','and'): + regs = ['x0','ra','sp','gp','tp','t0','t1','t2','s0','s1','a0','a1','a2','a3','a4','a5','a6','a7','s2','s3','s4','s5','s6','s7','s8','s9','s10','s11','t3','t4','t5','t6'] + lines.append(f" {regs[inst['rd']]} = {regs[inst['rs1']]} {mnemonic} {regs[inst['rs2']]};") + + elif mnemonic.startswith('c.'): + lines.append(f" {inst['str']};") + + else: + lines.append(f" {inst['str']};") + + return '\n'.join(lines) + +def main(): + print("Reading firmware...") + data = read_binary(FWIMAGE_PATH) + print(f"Binary size: {len(data)} bytes") + + all_results = {} + + for addr, name in TARGET_FUNCTIONS: + offset = addr - BASE_ADDR + if offset < 0 or offset >= len(data): + print(f"Skipping {name} at 0x{addr:08x} - out of bounds") + continue + + print(f"\nDecompiling {name} at 0x{addr:08x}...") + instructions = trace_function(data, offset, max_bytes=2000) + analysis = analyze_function(instructions, name) + pseudo_c = generate_pseudo_c(instructions, name, analysis) + + # Save + filename = f"{OUTPUT_DIR}/decompiled_{name}.c" + with open(filename, 'w') as f: + f.write(pseudo_c) + print(f" Saved to {filename}") + + # Print summary + print(f" Instructions: {len(instructions)}") + print(f" Stack: {analysis['stack_size']}") + print(f" RA save: s{analysis['ra_save_reg']-8 if analysis['ra_save_reg'] else '?'}") + print(f" RA restore: s{analysis['ra_restore_reg']-8 if analysis['ra_restore_reg'] else '?'}") + print(f" Calls: {len(analysis['calls'])}") + print(f" RPC refs: {analysis['rpc_refs'][:10]}") + + all_results[name] = { + 'address': f"0x{addr:08x}", + 'instruction_count': len(instructions), + 'analysis': analysis, + } + + # Save summary + with open(f"{OUTPUT_DIR}/decompilation_summary.json", 'w') as f: + json.dump(all_results, f, indent=2) + print(f"\nSummary saved to {OUTPUT_DIR}/decompilation_summary.json") + +if __name__ == "__main__": + main() |
