summaryrefslogtreecommitdiffstats
path: root/firmware/57X-GSP/570.144/analyze_firmware.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/analyze_firmware.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/analyze_firmware.py')
-rw-r--r--firmware/57X-GSP/570.144/analyze_firmware.py253
1 files changed, 253 insertions, 0 deletions
diff --git a/firmware/57X-GSP/570.144/analyze_firmware.py b/firmware/57X-GSP/570.144/analyze_firmware.py
new file mode 100644
index 0000000..8a61673
--- /dev/null
+++ b/firmware/57X-GSP/570.144/analyze_firmware.py
@@ -0,0 +1,253 @@
+#!/usr/bin/env python3
+"""
+Comprehensive GSP firmware analysis using call targets and pattern matching.
+"""
+
+import struct
+import json
+import sys
+import os
+
+FWIMAGE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "gsp-570.144.fwimage.bin")
+CALL_TARGETS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "all_call_targets.txt")
+OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))
+BASE_ADDR = 0x04000000
+
+EXEC_SECTIONS = [
+ (0x04005000, 0x04006413),
+ (0x04007000, 0x04008413),
+ (0x04009000, 0x04009073),
+ (0x0400a000, 0x0400cfff),
+ (0x0400e000, 0x04010fff),
+ (0x04018000, 0x0401cfff),
+ (0x04c58000, 0x05b21fff),
+]
+
+RPC_TYPES = {
+ 0x01: "ALLOC", 0x02: "FREE", 0x03: "MAP", 0x04: "UNMAP",
+ 0x05: "PIN", 0x06: "UNPIN", 0x07: "SCHEDULE", 0x08: "YIELD",
+ 0x09: "NOTIFY", 0x0A: "BAR_TRAP", 0x0B: "MIGRATE", 0xFF: "DEBUG",
+}
+
+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 load_call_targets(path):
+ targets = {}
+ with open(path, 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line or line.startswith('#'):
+ continue
+ parts = line.split()
+ if len(parts) >= 1:
+ addr = int(parts[0], 16)
+ count = int(parts[1]) if len(parts) > 1 else 0
+ targets[addr] = count
+ return targets
+
+def decode_instruction(data, offset):
+ 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, 'size': 2}
+ 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, 'size': 2}
+ if (inst & 0xF07F) == 0x8067:
+ rs1 = (inst >> 7) & 0x1F
+ return {'mnemonic': 'c.jalr', 'rs1': rs1, 'size': 2}
+ return {'mnemonic': 'unknown', 'raw': inst, 'size': 2}
+
+def decode_32bit(inst, offset):
+ opcode = inst & 0x7F
+ funct3 = (inst >> 12) & 0x7
+ rd = (inst >> 7) & 0x1F
+ rs1 = (inst >> 15) & 0x1F
+ rs2 = (inst >> 20) & 0x1F
+ if opcode == 0x13 and funct3 == 0:
+ imm = (inst >> 20) & 0xFFF
+ if imm >= 0x800: imm -= 0x1000
+ return {'mnemonic': 'addi', 'rd': rd, 'rs1': rs1, 'imm': imm, 'size': 4}
+ if opcode == 0x23 and funct3 == 3:
+ offset_val = ((inst >> 7) & 0x1F) | ((inst >> 25) & 0x7F) << 5
+ return {'mnemonic': 'sd', 'rs1': rs1, 'rs2': rs2, 'offset': offset_val, 'size': 4}
+ if opcode == 0x03 and funct3 == 3:
+ offset_val = (inst >> 20) & 0xFFF
+ if offset_val >= 0x800: offset_val -= 0x1000
+ return {'mnemonic': 'ld', 'rd': rd, 'rs1': rs1, 'offset': offset_val, 'size': 4}
+ if opcode == 0x6F:
+ imm = ((inst >> 31) & 0x1) << 20 | ((inst >> 12) & 0xFF) << 12 | ((inst >> 20) & 0x1) << 11 | ((inst >> 21) & 0x3FF) << 1
+ if imm & 0x100000: imm -= 0x200000
+ return {'mnemonic': 'jal', 'rd': rd, 'imm': imm, 'size': 4}
+ if opcode == 0x67 and funct3 == 0:
+ imm = (inst >> 20) & 0xFFF
+ if imm >= 0x800: imm -= 0x1000
+ return {'mnemonic': 'jalr', 'rd': rd, 'rs1': rs1, 'imm': imm, 'size': 4}
+ if opcode == 0x63:
+ imm = ((inst >> 31) & 0x1) << 12 | ((inst >> 7) & 0x1) << 11 | ((inst >> 25) & 0x3F) << 5 | ((inst >> 8) & 0xF) << 1
+ if imm & 0x1000: imm -= 0x2000
+ return {'mnemonic': 'branch', 'funct3': funct3, 'rs1': rs1, 'rs2': rs2, 'imm': imm, 'size': 4}
+ if opcode == 0x17:
+ imm = (inst >> 12) & 0xFFFFF
+ return {'mnemonic': 'auipc', 'rd': rd, 'imm': imm, 'size': 4}
+ if opcode == 0x37:
+ imm = (inst >> 12) & 0xFFFFF
+ return {'mnemonic': 'lui', 'rd': rd, 'imm': imm, 'size': 4}
+ return {'mnemonic': 'unknown', 'raw': inst, 'size': 4}
+
+def scan_function(data, start_offset, max_bytes=2000, call_count=0):
+ va = BASE_ADDR + start_offset
+ results = {
+ 'address': va,
+ 'address_str': f"0x{va:08x}",
+ 'call_count': call_count,
+ 'stack_size': None,
+ 'saves_ra': False,
+ 'ra_save_reg': None,
+ 'ra_restore_reg': None,
+ 'saves_s_regs': [],
+ 'calls': [],
+ 'rpc_type_refs': [],
+ 'has_ret': False,
+ 'ret_offsets': [],
+ }
+ i = 0
+ in_prologue = True
+ 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
+ mnemonic = inst_info.get('mnemonic')
+ if in_prologue and mnemonic == 'addi' and inst_info['rd'] == 2 and inst_info['rs1'] == 2 and inst_info['imm'] < -16:
+ results['stack_size'] = -inst_info['imm']
+ in_prologue = False
+ if mnemonic == 'sd' and inst_info['rs1'] == 2 and 9 <= inst_info['rs2'] <= 18:
+ results['saves_s_regs'].append(inst_info['rs2'])
+ if mnemonic == 'addi' and inst_info['rs1'] == 1 and inst_info['imm'] == 0 and 9 <= inst_info['rd'] <= 18:
+ if results['ra_save_reg'] is None:
+ results['ra_save_reg'] = inst_info['rd']; results['saves_ra'] = True
+ if mnemonic == 'c.mv' and inst_info['rs2'] == 1 and 8 <= inst_info['rd'] <= 15:
+ if results['ra_save_reg'] is None:
+ results['ra_save_reg'] = inst_info['rd']; results['saves_ra'] = True
+ if mnemonic == 'addi' and inst_info['rd'] == 1 and inst_info['imm'] == 0 and 9 <= inst_info['rs1'] <= 18:
+ results['ra_restore_reg'] = inst_info['rs1']
+ if mnemonic == 'c.mv' and inst_info['rd'] == 1 and 8 <= inst_info['rs2'] <= 15:
+ results['ra_restore_reg'] = inst_info['rs2']
+ if mnemonic in ('jal', 'jalr'):
+ if mnemonic == 'jal':
+ results['calls'].append(va + i + inst_info['imm'])
+ if mnemonic in ('addi', 'andi', 'ori') and 'imm' in inst_info:
+ imm = inst_info['imm']
+ if imm in RPC_TYPES:
+ results['rpc_type_refs'].append((i, imm, RPC_TYPES[imm]))
+ if 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:
+ results['has_ret'] = True; results['ret_offsets'].append(i)
+ i += size
+ return results
+
+def scan_for_strings_in_range(data, start_offset, max_len=500):
+ strings = []
+ i = start_offset; end = min(start_offset + max_len, len(data))
+ while i < end:
+ if 32 <= data[i] <= 126:
+ start = i
+ while i < end and 32 <= data[i] <= 126: i += 1
+ if i - start >= 4:
+ s = data[start:i].decode('ascii', errors='ignore')
+ strings.append((BASE_ADDR + start, s))
+ else: i += 1
+ return strings
+
+def convert_func(f):
+ return {
+ 'address': f['address_str'],
+ 'call_count': f.get('call_count', 0),
+ 'stack_size': f['stack_size'],
+ 'saves_ra': f['saves_ra'],
+ 'ra_save_reg': f['ra_save_reg'],
+ 'ra_restore_reg': f['ra_restore_reg'],
+ 'saves_s_regs': f['saves_s_regs'],
+ 'calls': [f"0x{c:08x}" for c in f['calls']],
+ 'rpc_type_refs': [(RPC_TYPES[r[1]], r[0]) for r in f['rpc_type_refs']],
+ 'has_ret': f['has_ret'],
+ }
+
+def main():
+ print("Reading firmware...")
+ data = read_binary(FWIMAGE_PATH)
+ print(f"Binary size: {len(data)} bytes")
+
+ print("Loading call targets...")
+ call_targets = load_call_targets(CALL_TARGETS_FILE)
+ print(f"Loaded {len(call_targets)} call targets")
+
+ sorted_targets = sorted(call_targets.items(), key=lambda x: x[1], reverse=True)
+
+ print("\nScanning top 500 call targets...")
+ functions = []
+ for addr, count in sorted_targets[:500]:
+ if not is_in_exec(addr): continue
+ offset = addr - BASE_ADDR
+ if offset < 0 or offset >= len(data): continue
+ func_info = scan_function(data, offset, max_bytes=1000, call_count=count)
+ functions.append(func_info)
+
+ print(f"Analyzed {len(functions)} functions")
+
+ rpc_handlers = [f for f in functions if f['rpc_type_refs']]
+ other_funcs = [f for f in functions if not f['rpc_type_refs']]
+
+ print(f"\nRPC handler candidates: {len(rpc_handlers)}")
+ print(f"Other functions: {len(other_funcs)}")
+
+ print("\n=== Top 20 RPC Handler Candidates ===")
+ for f in rpc_handlers[:20]:
+ rpc_refs = [(RPC_TYPES[r[1]], r[0]) for r in f['rpc_type_refs']]
+ print(f" {f['address_str']}: calls={f['call_count']}, stack={f['stack_size']}, rpc_refs={rpc_refs[:10]}")
+
+ # Save results
+ results = {
+ 'total_functions_analyzed': len(functions),
+ 'rpc_handlers': [convert_func(f) for f in rpc_handlers[:200]],
+ 'other_functions': [convert_func(f) for f in other_funcs[:100]],
+ }
+ with open(f"{OUTPUT_DIR}/analysis_results.json", "w") as f:
+ json.dump(results, f, indent=2)
+ print(f"\nResults saved to {OUTPUT_DIR}/analysis_results.json")
+
+ # Detailed scans
+ print("\n=== Entry Point (0x04009000) ===")
+ print(json.dumps(convert_func(scan_function(data, 0x9000, max_bytes=500)), indent=2))
+
+ print("\n=== RPC_BAR_TRAP Handler (0x04cb8189) ===")
+ print(json.dumps(convert_func(scan_function(data, 0xcb8189, max_bytes=500)), indent=2))
+
+ print("\n=== Top 3 Functions Detail ===")
+ for f in functions[:3]:
+ print(f"\n--- {f['address_str']} (calls={f['call_count']}) ---")
+ print(json.dumps(convert_func(f), indent=2))
+
+if __name__ == "__main__":
+ main()