From 2302fc09080bad51c73f293294e19fd4db464eb0 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. --- tools/ghidra/diff_gsp_firmware.py | 220 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 tools/ghidra/diff_gsp_firmware.py (limited to 'tools/ghidra/diff_gsp_firmware.py') diff --git a/tools/ghidra/diff_gsp_firmware.py b/tools/ghidra/diff_gsp_firmware.py new file mode 100644 index 0000000..8145982 --- /dev/null +++ b/tools/ghidra/diff_gsp_firmware.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +Diff two GSP firmware versions. +Usage: python3 diff_gsp_firmware.py +""" + +import sys +import os +import subprocess +import json +import hashlib + +def extract_fwimage(firmware_path, output_path): + """Extract .fwimage section from GSP firmware ELF.""" + cmd = ["readelf", "-x", ".fwimage", firmware_path] + result = subprocess.run(cmd, capture_output=True, text=True) + # This is a hex dump, we need to parse it + # Better to use objcopy + cmd = ["objcopy", "-O", "binary", "-j", ".fwimage", firmware_path, output_path] + result = subprocess.run(cmd, capture_output=True) + return result.returncode == 0 + +def extract_version(firmware_path): + """Extract version from .fwversion section.""" + cmd = ["readelf", "-x", ".fwversion", firmware_path] + result = subprocess.run(cmd, capture_output=True, text=True) + # Parse hex dump + for line in result.stdout.split('\n'): + if '0x' in line and len(line.strip()) > 10: + parts = line.split() + if len(parts) >= 5: + hex_str = ''.join(parts[1:5]) + try: + version = bytes.fromhex(hex_str).decode('ascii').strip('\x00') + return version + except: + pass + return "unknown" + +def run_ghidra_analysis(fwimage_path, project_dir, project_name): + """Run Ghidra analysis on fwimage.""" + analyze_headless = "/snap/ghidra/47/ghidra/support/analyzeHeadless" + if not os.path.exists(analyze_headless): + analyze_headless = "/opt/ghidra/support/analyzeHeadless" + + script_path = "/home/rmcore/Git/integdev_gpu_drv/open-iov/tools/ghidra/gsp-tu104-570.144/scripts" + + cmd = [ + analyze_headless, + project_dir, + project_name, + "-import", fwimage_path, + "-processor", "RISCV:LE:64:default", + "-cspec", "gcc", + "-overwrite", + "-scriptPath", script_path, + "-postScript", "analyze_gsp_fwimage.py" + ] + + result = subprocess.run(cmd, capture_output=True, text=True, timeout=7200) + return result.returncode == 0 + +def load_analysis_results(output_dir): + """Load analysis results from JSON files.""" + results = {} + for fname in ["functions.json", "rpc_functions.json", "vgpu_strings.json", + "wpr_functions.json", "mem_functions.json", "irq_functions.json", + "analysis_summary.json"]: + path = os.path.join(output_dir, fname) + if os.path.exists(path): + with open(path, 'r') as f: + results[fname.replace('.json', '')] = json.load(f) + return results + +def diff_functions(old_funcs, new_funcs): + """Compare function lists.""" + old_names = {f['name']: f for f in old_funcs} + new_names = {f['name']: f for f in new_funcs} + + added = [f for name, f in new_names.items() if name not in old_names] + removed = [f for name, f in old_names.items() if name not in new_names] + modified = [] + + for name in set(old_names.keys()) & set(new_names.keys()): + if old_names[name]['size'] != new_names[name]['size'] or \ + old_names[name]['entry'] != new_names[name]['entry']: + modified.append({ + 'name': name, + 'old': old_names[name], + 'new': new_names[name] + }) + + return added, removed, modified + +def generate_diff_report(old_firmware, new_firmware, old_results, new_results, output_path): + """Generate markdown diff report.""" + old_version = extract_version(old_firmware) + new_version = extract_version(new_firmware) + + old_funcs = old_results.get('functions', []) + new_funcs = new_results.get('functions', []) + + added, removed, modified = diff_functions(old_funcs, new_funcs) + + old_rpc = {f['name'] for f in old_results.get('rpc_functions', [])} + new_rpc = {f['name'] for f in new_results.get('rpc_functions', [])} + + rpc_added = new_rpc - old_rpc + rpc_removed = old_rpc - new_rpc + + report = f"""# GSP Firmware Diff Report + +## Versions +- **Old**: {old_version} ({os.path.basename(old_firmware)}) +- **New**: {new_version} ({os.path.basename(new_firmware)}) + +## Function Changes +- **Total Functions**: {len(old_funcs)} → {len(new_funcs)} ({len(new_funcs) - len(old_funcs):+d}) +- **Added**: {len(added)} +- **Removed**: {len(removed)} +- **Modified**: {len(modified)} + +## RPC Handler Changes +- **Added RPC Functions**: {len(rpc_added)} +- **Removed RPC Functions**: {len(rpc_removed)} + +## Added Functions +""" + for f in added[:50]: + report += f"- `{f['name']}` at {f['entry']} (size: {f['size']})\n" + + if len(added) > 50: + report += f"\n... and {len(added) - 50} more\n" + + report += "\n## Removed Functions\n" + for f in removed[:50]: + report += f"- `{f['name']}` at {f['entry']} (size: {f['size']})\n" + + if len(removed) > 50: + report += f"\n... and {len(removed) - 50} more\n" + + report += "\n## Modified Functions\n" + for f in modified[:50]: + report += f"- `{f['name']}`: {f['old']['entry']}→{f['new']['entry']}, size {f['old']['size']}→{f['new']['size']}\n" + + if len(modified) > 50: + report += f"\n... and {len(modified) - 50} more\n" + + report += "\n## Added RPC Functions\n" + for name in sorted(rpc_added): + report += f"- `{name}`\n" + + report += "\n## Removed RPC Functions\n" + for name in sorted(rpc_removed): + report += f"- `{name}`\n" + + # vGPU strings comparison + old_strings = {s['value'] for s in old_results.get('vgpu_strings', [])} + new_strings = {s['value'] for s in new_results.get('vgpu_strings', [])} + + report += "\n## vGPU String Changes\n" + report += f"- **Added**: {len(new_strings - old_strings)}\n" + report += f"- **Removed**: {len(old_strings - new_strings)}\n" + + for s in sorted(new_strings - old_strings)[:20]: + report += f" + `{s}`\n" + for s in sorted(old_strings - new_strings)[:20]: + report += f" - `{s}`\n" + + with open(output_path, 'w') as f: + f.write(report) + + print(f"Diff report saved to {output_path}") + +def main(): + if len(sys.argv) < 4: + print("Usage: diff_gsp_firmware.py ") + sys.exit(1) + + old_firmware = sys.argv[1] + new_firmware = sys.argv[2] + output_report = sys.argv[3] + + # Extract .fwimage sections + old_fwimage = "/tmp/old_fwimage.bin" + new_fwimage = "/tmp/new_fwimage.bin" + + print("Extracting .fwimage sections...") + if not extract_fwimage(old_firmware, old_fwimage): + print("Failed to extract old .fwimage") + sys.exit(1) + if not extract_fwimage(new_firmware, new_fwimage): + print("Failed to extract new .fwimage") + sys.exit(1) + + # Run Ghidra analysis on both + print("Analyzing old firmware...") + old_project_dir = "/tmp/ghidra_old" + if not run_ghidra_analysis(old_fwimage, old_project_dir, "gsp_old"): + print("Failed to analyze old firmware") + sys.exit(1) + + print("Analyzing new firmware...") + new_project_dir = "/tmp/ghidra_new" + if not run_ghidra_analysis(new_fwimage, new_project_dir, "gsp_new"): + print("Failed to analyze new firmware") + sys.exit(1) + + # Load results + old_results = load_analysis_results("/home/rmcore/Git/integdev_gpu_drv/open-iov/firmware/570-GSP") + # For new firmware, we'd need a separate output dir + new_results = load_analysis_results("/home/rmcore/Git/integdev_gpu_drv/open-iov/firmware/NEW-GSP") + + # Generate diff report + generate_diff_report(old_firmware, new_firmware, old_results, new_results, output_report) + + print("Done!") + +if __name__ == "__main__": + main() \ No newline at end of file -- cgit v1.2.3