diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-14 06:43:11 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-09-14 06:43:11 -0400 |
| commit | 5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60 (patch) | |
| tree | 7523e29d8044d4d5b6dcd7c1c888a3491137fcd1 /tools | |
| parent | 9a7263fce4e3156349a526631805fce66b87fb1b (diff) | |
| download | open-iov-5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60.tar.gz open-iov-5dd8165f1754e156a0f9fb16c7a6bfa90e99ed60.zip | |
VFIO device registration fix + IOMMUFD callbacks + VFIO ioctl handlers
- Removed IOMMU group addition before vfio_register_emulated_iommu_dev() in nv_vgpu_mdev_create()
- Added IOMMUFD callbacks (bind_iommufd, unbind_iommufd, attach_ioas, detach_ioas) using kernel's vfio_iommufd_emulated_* functions
- Implemented VFIO ioctl handlers: GET_INFO, GET_REGION_INFO, GET_IRQ_INFO, SET_IRQS, MMAP
- Added config space read/write handlers at offset 0x70000000000
- Mmap handler for config space, BAR0 (emulation shadow), BAR1-5 (physical VRAM/registers)
- Interrupt handler for VFIO_DEVICE_SET_IRQS (DATA_EVENTFD, DATA_NONE, DATA_BOOL)
- Added udev rules for vfio group permissions
- Created stack_manager.sh unified tool
- Fixed mdev remove crash with deferred cleanup in vfio_device_release callback
- Kernel 6.8+ IOMMUFD compatibility
- QEMU launches successfully with vGPU passthrough
Diffstat (limited to 'tools')
| -rwxr-xr-x | tools/benchmark_infrastructure.sh | 217 | ||||
| -rw-r--r-- | tools/failure_analyzer.py | 312 | ||||
| -rw-r--r-- | tools/gdb_scripts/nvidia_init.gdb | 179 | ||||
| -rw-r--r-- | tools/guest_agent/guest_test_agent.py | 332 | ||||
| -rw-r--r-- | tools/guest_init_monitor.py | 207 |
5 files changed, 1247 insertions, 0 deletions
diff --git a/tools/benchmark_infrastructure.sh b/tools/benchmark_infrastructure.sh new file mode 100755 index 0000000..606aa10 --- /dev/null +++ b/tools/benchmark_infrastructure.sh @@ -0,0 +1,217 @@ +#!/bin/bash +# benchmark_infrastructure.sh - Benchmark runner for VM vs bare metal comparison + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BASE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +RESULTS_DIR="/tmp/gsp_emu_benchmarks/$(date +%Y%m%d_%H%M%S)" +mkdir -p "$RESULTS_DIR" + +log_info() { echo -e "\033[0;34m[INFO]\033[0m $1"; } +log_ok() { echo -e "\033[0;32m[OK]\033[0m $1"; } +log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; } + +run_bare_metal() { + local test_name=$1 + shift + log_info "Running $test_name on bare metal..." + local output="$RESULTS_DIR/bare_metal_${test_name}.json" + "$@" --benchmark --output "$output" 2>&1 | tee "$RESULTS_DIR/bare_metal_${test_name}.log" +} + +run_in_vm() { + local test_name=$1 + shift + log_info "Running $test_name in VM..." + local output="$RESULTS_DIR/vm_${test_name}.json" + # This would run inside the guest via guest_test_agent + # For now, placeholder + echo '{"test": "'$test_name'", "status": "not_implemented"}' > "$output" +} + +compare_results() { + local test_name=$1 + local bare_file="$RESULTS_DIR/bare_metal_${test_name}.json" + local vm_file="$RESULTS_DIR/vm_${test_name}.json" + + if [[ -f "$bare_file" && -f "$vm_file" ]]; then + python3 -c " +import json, sys +with open('$bare_file') as f: bare = json.load(f) +with open('$vm_file') as f: vm = json.load(f) +print(f'Test: $test_name') +for key in bare: + if key in vm and isinstance(bare[key], (int, float)) and isinstance(vm[key], (int, float)): + ratio = vm[key] / bare[key] * 100 + print(f' {key}: bare={bare[key]:.2f} vm={vm[key]:.2f} ratio={ratio:.1f}%') +" + fi +} + +# CUDA benchmarks +benchmark_cuda() { + log_info "=== CUDA Benchmarks ===" + + # vectorAdd + run_bare_metal "vectorAdd" /usr/local/cuda/samples/1_Utilities/vectorAdd/vectorAdd + run_in_vm "vectorAdd" + compare_results "vectorAdd" + + # matrixMul + run_bare_metal "matrixMul" /usr/local/cuda/samples/0_Simple/matrixMul/matrixMul + run_in_vm "matrixMul" + compare_results "matrixMul" + + # deviceQuery (bandwidth) + run_bare_metal "deviceQuery" /usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery + run_in_vm "deviceQuery" + compare_results "deviceQuery" + + # bandwidthTest + run_bare_metal "bandwidthTest" /usr/local/cuda/samples/1_Utilities/bandwidthTest/bandwidthTest + run_in_vm "bandwidthTest" + compare_results "bandwidthTest" +} + +# cuBLAS benchmarks +benchmark_cublas() { + log_info "=== cuBLAS Benchmarks ===" + run_bare_metal "cublas_sgemm" /usr/local/cuda/samples/7_CUDALibraries/simpleCUBLAS/simpleCUBLAS + run_in_vm "cublas_sgemm" + compare_results "cublas_sgemm" +} + +# Graphics benchmarks +benchmark_graphics() { + log_info "=== Graphics Benchmarks ===" + + # vkcube (frame time) + run_bare_metal "vkcube" vkcube --duration 10 --benchmark + run_in_vm "vkcube" + compare_results "vkcube" + + # glmark2 + run_bare_metal "glmark2" glmark2 --benchmark + run_in_vm "glmark2" + compare_results "glmark2" +} + +# Video benchmarks +benchmark_video() { + log_info "=== Video Benchmarks ===" + + # NVENC encode + run_bare_metal "nvenc_encode" ffmpeg -i test_1080p.yuv -c:v h264_nvenc -f null - + run_in_vm "nvenc_encode" + compare_results "nvenc_encode" + + # NVDEC decode + run_bare_metal "nvdec_decode" ffmpeg -i test_1080p.h264 -c:v h264_nvdec -f null - + run_in_vm "nvdec_decode" + compare_results "nvdec_decode" +} + +# PyTorch benchmarks +benchmark_pytorch() { + log_info "=== PyTorch Benchmarks ===" + + cat > /tmp/bench_pytorch.py <<'EOF' +import torch, time, json, sys +device = 'cuda' if torch.cuda.is_available() else 'cpu' +results = {} + +# Matmul benchmark +for size in [512, 1024, 2048, 4096]: + x = torch.randn(size, size, device=device) + y = torch.randn(size, size, device=device) + torch.cuda.synchronize() if device == 'cuda' else None + start = time.time() + for _ in range(10): + z = torch.mm(x, y) + torch.cuda.synchronize() if device == 'cuda' else None + elapsed = (time.time() - start) / 10 + gflops = 2 * size**3 / elapsed / 1e9 + results[f'matmul_{size}'] = {'time': elapsed, 'gflops': gflops} + print(f"Matmul {size}x{size}: {elapsed:.4f}s ({gflops:.1f} GFLOPS)") + +# ResNet-50 forward +import torchvision.models as models +model = models.resnet50().to(device) +model.eval() +x = torch.randn(32, 3, 224, 224).to(device) +torch.cuda.synchronize() if device == 'cuda' else None +start = time.time() +for _ in range(10): + with torch.no_grad(): + _ = model(x) +torch.cuda.synchronize() if device == 'cuda' else None +elapsed = (time.time() - start) / 10 +results['resnet50_forward'] = {'time': elapsed, 'fps': 32/elapsed} +print(f"ResNet-50 forward: {elapsed:.4f}s ({32/elapsed:.1f} FPS)") + +with open(sys.argv[1], 'w') as f: + json.dump(results, f, indent=2) +EOF + + run_bare_metal "pytorch" python3 /tmp/bench_pytorch.py + run_in_vm "pytorch" + compare_results "pytorch" +} + +# Generate summary report +generate_summary() { + log_info "Generating summary report..." + cat > "$RESULTS_DIR/summary.md" <<EOF +# GSP Emulation Benchmark Summary +Date: $(date) +Host: $(hostname) +Kernel: $(uname -r) + +## Results + +| Test | Bare Metal | VM | Ratio | Status | +|------|------------|-----|-------|--------| +EOF + + for f in "$RESULTS_DIR"/bare_metal_*.json; do + test_name=$(basename "$f" .json | sed 's/bare_metal_//') + vm_f="$RESULTS_DIR/vm_${test_name}.json" + if [[ -f "$vm_f" ]]; then + python3 -c " +import json +with open('$f') as bf: bare = json.load(bf) +with open('$vm_f') as vf: vm = json.load(vf) +for k in bare: + if k in vm and isinstance(bare[k], (int, float)): + ratio = vm[k] / bare[k] * 100 + status = '✅' if ratio > 85 else '⚠️' if ratio > 70 else '❌' + print(f'| {test_name} / {k} | {bare[k]:.2f} | {vm[k]:.2f} | {ratio:.1f}% | {status} |') +" >> "$RESULTS_DIR/summary.md" + fi + done + + log_ok "Summary: $RESULTS_DIR/summary.md" +} + +main() { + case "${1:-all}" in + cuda) benchmark_cuda ;; + cublas) benchmark_cublas ;; + graphics) benchmark_graphics ;; + video) benchmark_video ;; + pytorch) benchmark_pytorch ;; + all) + benchmark_cuda + benchmark_cublas + benchmark_graphics + benchmark_video + benchmark_pytorch + ;; + *) echo "Usage: $0 {cuda|cublas|graphics|video|pytorch|all}"; exit 1 ;; + esac + generate_summary +} + +main "$@"
\ No newline at end of file diff --git a/tools/failure_analyzer.py b/tools/failure_analyzer.py new file mode 100644 index 0000000..7ffb776 --- /dev/null +++ b/tools/failure_analyzer.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +""" +failure_analyzer.py - Analyze and classify guest initialization failures +Correlates host tracepoints, guest logs, and QEMU state +""" + +import sys +import os +import re +import json +import argparse +from datetime import datetime +from pathlib import Path +from collections import defaultdict +from typing import Dict, List, Optional, Any + +class FailureAnalyzer: + def __init__(self): + self.failure_signatures = self._load_signatures() + self.recommendations = self._load_recommendations() + + def _load_signatures(self) -> Dict: + return { + "PCI_PROBE_FAILURE": { + "guest_patterns": [ + r"nvidia: probe of .* failed with error -?\d+", + r"pci .*: can't claim BAR \d+", + r"pci .*: BAR \d+: no space for \[mem size", + r"pci .*: BAR \d+: failed to assign", + ], + "host_trace_patterns": [ + r"gsp_emu_rpc_received.*rpc_type=0x1.*status=-?\d+", + r"gsp_emu_fallback_used.*rpc_type=0x1", + ], + "severity": "critical", + "category": "pci_config" + }, + "GSP_FIRMWARE_LOAD_FAILURE": { + "guest_patterns": [ + r"GSP.*firmware.*load.*failed", + r"GSP.*bootstrap.*failed", + r"nv_gsp_firmware_load.*error", + r"GSP.*RPC.*timeout", + r"GSP.*communication.*failed", + ], + "host_trace_patterns": [ + r"gsp_emu_firmware_analysis.*matched=0", + r"gsp_emu_dispatcher_select.*emulation=1", + r"gsp_emu_rpc_received.*rpc_type=0x1.*seq_id=1", + r"gsp_emu_rpc_processed.*status=-110", + ], + "severity": "critical", + "category": "gsp_firmware" + }, + "MSIX_SETUP_FAILURE": { + "guest_patterns": [ + r"MSI-X.*failed", + r"pci_enable_msix.*failed", + r"request_threaded_irq.*failed", + r"irq.*allocation.*failed", + ], + "host_trace_patterns": [ + r"gsp_emu_msix_injected.*vector=0", + r"gsp_emu_rpc_processed.*rpc_type=0x7.*status=-?\d+", + ], + "severity": "high", + "category": "interrupts" + }, + "BAR_MAPPING_FAILURE": { + "guest_patterns": [ + r"ioremap.*failed", + r"pci_iomap.*failed", + r"BAR.*mapping.*failed", + r"VFIO.*region.*mmap.*failed", + ], + "host_trace_patterns": [ + r"gsp_emu_shared_mem_access.*is_write=1", + r"gsp_emu_bar_emulation.*", + ], + "severity": "high", + "category": "memory_mapping" + }, + "DMA_MAPPING_FAILURE": { + "guest_patterns": [ + r"dma_map.*failed", + r"IOMMU.*fault", + r"DMAR.*error", + r"iommu.*domain.*failed", + ], + "host_trace_patterns": [ + r"gsp_emu_rpc_processed.*rpc_type=0x5.*status=-?\d+", + r"gsp_emu_fallback_used.*rpc_type=0x5", + ], + "severity": "high", + "category": "dma" + }, + "VGPU_VFIO_FAILURE": { + "guest_patterns": [ + r"vGPU VFIO.*failed", + r"nvidia_vgpu_vfio_get_ops.*failed", + r"mdev.*register.*failed", + r"VFIO.*device.*not found", + ], + "host_trace_patterns": [ + r"gsp_emu_thread_state.*state=0", + ], + "severity": "critical", + "category": "vfio" + }, + "GSP_RPC_TIMEOUT": { + "guest_patterns": [ + r"GSP.*RPC.*timeout", + r"doorbell.*timeout", + r"GSP.*response.*timeout", + ], + "host_trace_patterns": [ + r"gsp_emu_doorbell_poll.*", + r"gsp_emu_rpc_received.*", + r"gsp_emu_rpc_processed.*latency_ns=\d{9,}", + ], + "severity": "medium", + "category": "rpc_timeout" + }, + } + + def _load_recommendations(self) -> Dict: + return { + "PCI_PROBE_FAILURE": [ + "Check PCI config space emulation in open-iov-vfio.ko (main.c)", + "Verify BAR sizes match physical GPU (16MB/256MB/32MB/32MB)", + "Check MSI-X capability pointer (0x70) and table/PBA offsets", + "Ensure mdev parent registered with NVIDIA host driver", + ], + "GSP_FIRMWARE_LOAD_FAILURE": [ + "Verify firmware file exists at /lib/firmware/nvidia/tu102/gsp/", + "Check firmware CRC32/SHA256 matches known signature", + "Ensure GSP emulation thread started (gsp_emu_thread_state)", + "Check doorbell register offset (BAR0+0x110C00)", + "Verify shared memory mapping (BAR0+0x1000, 4KB)", + ], + "MSIX_SETUP_FAILURE": [ + "Verify MSI-X table at BAR0+0x2000 (32 entries × 16 bytes)", + "Verify MSI-X PBA at BAR0+0x3000 (64-bit)", + "Check eventfd signaling for interrupt injection", + "Ensure guest MSI-X enable bit set in Message Control", + ], + "BAR_MAPPING_FAILURE": [ + "Check VFIO region mmap handlers (bar_emulation.c)", + "Verify BAR0 includes doorbell (0x110C00) and shared mem (0x1000)", + "Check MSI-X region (VFIO_PCI_MSIX_REGION_INDEX)", + "Ensure WC (write-combine) mapping for BAR0", + ], + "DMA_MAPPING_FAILURE": [ + "Verify IOMMU domain attached to VFIO group", + "Check DMA mapping in memory.c (nv_vgpu_dma_map)", + "Ensure physical pages pinned before mapping", + "Check for IOMMU faults in dmesg", + ], + "VGPU_VFIO_FAILURE": [ + "Verify nvidia.ko loaded before open-iov-vfio.ko", + "Check KBUILD_EXTRA_SYMBOLS points to nvidia.ko Module.symvers", + "Verify nvidia_vgpu_vfio_get_ops exported by nvidia.ko", + "Check mdev parent registration callback", + ], + "GSP_RPC_TIMEOUT": [ + "Check GSP emulation thread CPU usage (should be polling)", + "Verify doorbell polling adaptive (busy-wait 100us then usleep)", + "Check shared memory coherence (cache flush/invalidate)", + "Trace RPC sequence IDs for gaps/duplicates", + ], + } + + def analyze(self, guest_log_file: str, host_trace_file: str, qemu_log_file: str = None) -> Dict: + """Analyze failure from log files""" + + guest_logs = self._read_logs(guest_log_file) + host_traces = self._read_logs(host_trace_file) + qemu_logs = self._read_logs(qemu_log_file) if qemu_log_file else [] + + # Classify failure + failure_type = self._classify_failure(guest_logs, host_traces) + + # Get matching evidence + evidence = self._collect_evidence(failure_type, guest_logs, host_traces) + + # Generate report + report = { + "timestamp": datetime.now().isoformat(), + "failure_type": failure_type, + "severity": self.failure_signatures.get(failure_type, {}).get("severity", "unknown"), + "category": self.failure_signatures.get(failure_type, {}).get("category", "unknown"), + "evidence": evidence, + "recommendations": self.recommendations.get(failure_type, []), + "guest_log_summary": self._summarize_logs(guest_logs), + "host_trace_summary": self._summarize_logs(host_traces), + } + + return report + + def _read_logs(self, filepath: str) -> List[str]: + if not filepath or not os.path.exists(filepath): + return [] + with open(filepath, 'r') as f: + return [line.strip() for line in f.readlines()] + + def _classify_failure(self, guest_logs: List[str], host_traces: List[str]) -> str: + """Classify failure based on patterns""" + scores = defaultdict(int) + + for failure_type, sig in self.failure_signatures.items(): + # Check guest patterns + for pattern in sig.get("guest_patterns", []): + for line in guest_logs: + if re.search(pattern, line, re.IGNORECASE): + scores[failure_type] += 3 + + # Check host trace patterns + for pattern in sig.get("host_trace_patterns", []): + for line in host_traces: + if re.search(pattern, line): + scores[failure_type] += 2 + + if not scores: + return "UNKNOWN" + + # Return highest scoring failure type + return max(scores, key=scores.get) + + def _collect_evidence(self, failure_type: str, guest_logs: List[str], host_traces: List[str]) -> Dict: + """Collect matching evidence for failure type""" + sig = self.failure_signatures.get(failure_type, {}) + evidence = {"guest": [], "host": []} + + for pattern in sig.get("guest_patterns", []): + for line in guest_logs: + if re.search(pattern, line, re.IGNORECASE): + evidence["guest"].append({"pattern": pattern, "match": line}) + + for pattern in sig.get("host_trace_patterns", []): + for line in host_traces: + if re.search(pattern, line): + evidence["host"].append({"pattern": pattern, "match": line}) + + return evidence + + def _summarize_logs(self, logs: List[str]) -> Dict: + """Summarize log content""" + return { + "total_lines": len(logs), + "error_lines": sum(1 for l in logs if "error" in l.lower() or "fail" in l.lower()), + "nvidia_lines": sum(1 for l in logs if "nvidia" in l.lower()), + "gsp_lines": sum(1 for l in logs if "gsp" in l.lower()), + "last_20": logs[-20:] if logs else [], + } + + def print_report(self, report: Dict): + """Print formatted failure report""" + print("\n" + "="*70) + print(f"FAILURE ANALYSIS REPORT") + print("="*70) + print(f"Timestamp: {report['timestamp']}") + print(f"Failure Type: {report['failure_type']}") + print(f"Severity: {report['severity']}") + print(f"Category: {report['category']}") + print() + + print("EVIDENCE:") + print("-"*70) + for src in ["guest", "host"]: + ev = report['evidence'].get(src, []) + if ev: + print(f" {src.upper()} ({len(ev)} matches):") + for e in ev[:5]: + print(f" Pattern: {e['pattern']}") + print(f" Match: {e['match'][:120]}") + print() + + print("RECOMMENDATIONS:") + print("-"*70) + for i, rec in enumerate(report['recommendations'], 1): + print(f" {i}. {rec}") + print() + + print("LOG SUMMARIES:") + print("-"*70) + for src in ["guest", "host"]: + summ = report.get(f'{src}_log_summary', {}) + print(f" {src.upper()}: {summ.get('total_lines', 0)} lines, " + f"{summ.get('error_lines', 0)} errors, " + f"{summ.get('nvidia_lines', 0)} nvidia, " + f"{summ.get('gsp_lines', 0)} gsp") + print("="*70) + +def main(): + parser = argparse.ArgumentParser(description="Analyze guest NVIDIA driver initialization failures") + parser.add_argument("--guest-log", required=True, help="Guest serial log file") + parser.add_argument("--host-trace", required=True, help="Host trace_pipe log file") + parser.add_argument("--qemu-log", help="QEMU log file (optional)") + parser.add_argument("--output", help="Output JSON report file") + args = parser.parse_args() + + analyzer = FailureAnalyzer() + report = analyzer.analyze(args.guest_log, args.host_trace, args.qemu_log) + analyzer.print_report(report) + + if args.output: + with open(args.output, 'w') as f: + json.dump(report, f, indent=2) + print(f"\nReport saved to: {args.output}") + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/tools/gdb_scripts/nvidia_init.gdb b/tools/gdb_scripts/nvidia_init.gdb new file mode 100644 index 0000000..c217419 --- /dev/null +++ b/tools/gdb_scripts/nvidia_init.gdb @@ -0,0 +1,179 @@ +# nvidia_init.gdb - GDB script for debugging NVIDIA driver initialization in guest +# Usage: gdb -x nvidia_init.gdb ./vmlinux +# Then: (gdb) target remote localhost:1234 + +# ============================================================ +# CONFIGURATION +# ============================================================ +# Path to guest kernel vmlinux with debug symbols +# set pagination off +# set confirm off + +# ============================================================ +# BREAKPOINTS - NVIDIA Driver Probe/Init +# ============================================================ + +# Core probe entry points +break nvidia_probe +break nvidia_init_module +break nvidia_exit_module + +# GSP/RM initialization +break nv_init_gsp +break nv_gsp_init +break nv_gsp_firmware_load +break nv_gsp_bootstrap +break nv_gsp_rpc_init + +# VFIO/mdev integration +break nvidia_vgpu_vfio_get_ops +break nvidia_vgpu_vfio_set_ops +break nv_vgpu_mdev_register_parent +break nv_vgpu_mdev_unregister_parent +break nv_vgpu_create_callback +break nv_vgpu_delete_callback +break nv_vgpu_bar_info_callback +break nv_vgpu_start_callback + +# PCI config space +break nvidia_pci_probe +break nvidia_pci_remove +break pci_enable_device +break pci_request_regions + +# MSI/MSI-X +break pci_enable_msi_block +break pci_enable_msix_range +break nvidia_setup_msi +break nvidia_setup_msix + +# BAR mapping +break pci_iomap +break pci_iounmap +break nvidia_map_bars + +# Interrupt handling +break request_threaded_irq +break free_irq +break nvidia_isr +break nvidia_msi_isr +break nvidia_msix_isr + +# DMA mapping +break dma_map_page +break dma_unmap_page +break dma_map_sg +break dma_unmap_sg + +# GSP RPC communication (OpenRM internal) +break nv_gsp_rpc_send +break nv_gsp_rpc_recv +break nv_gsp_doorbell_ring +break nv_gsp_doorbell_poll + +# ============================================================ +# WATCHPOINTS - Key Data Structures +# ============================================================ + +# Watch for GSP firmware load completion +# watch *(int*)nv_gsp_fw_loaded + +# Watch for MSI-X table setup +# watch *(int*)nv_msix_table_configured + +# ============================================================ +# COMMANDS - Helper Functions +# ============================================================ + +define nvidia_backtrace + bt + printf "\n=== NVIDIA Driver Stack ===\n" + info registers +end +document nvidia_backtrace + Show backtrace with register state for NVIDIA driver +end + +define nvidia_pci_state + printf "=== PCI Config Space ===\n" + # Requires access to pci_dev structure + # p/x ((struct pci_dev*)$rdi)->vendor + # p/x ((struct pci_dev*)$rdi)->device + # p/x ((struct pci_dev*)$rdi)->command + # p/x ((struct pci_dev*)$rdi)->status +end +document nvidia_pci_state + Print PCI device state +end + +define nvidia_gsp_state + printf "=== GSP State ===\n" + # p/x nv_gsp_fw_version + # p/x nv_gsp_fw_loaded + # p/x nv_gsp_rpc_initialized +end +document nvidia_gsp_state + Print GSP firmware state +end + +define nvidia_vfio_state + printf "=== VFIO/mdev State ===\n" + # p/x nv_vgpu_mdev_registered + # p/x nv_vgpu_vfio_ops +end +document nvidia_vfio_state + Print VFIO/mdev registration state +end + +# ============================================================ +# TRACEPOINTS (if kernel has tracepoints enabled) +# ============================================================ + +# trace nvidia:nvidia_probe_entry +# trace nvidia:nvidia_probe_exit +# trace nvidia:gsp_firmware_load +# trace nvidia:gsp_rpc_send +# trace nvidia:gsp_rpc_recv + +# ============================================================ +# AUTOMATION - Continue and Log +# ============================================================ + +# Log all breakpoint hits to file +set logging file nvidia_init_debug.log +set logging on + +# Auto-continue after logging (uncomment for automated runs) +# set confirm off +# commands 1 +# nvidia_backtrace +# continue +# end + +# ============================================================ +# CONVENIENCE ALIASES +# ============================================================ + +define hook-stop + # Print current function on every stop + printf "\n=== STOPPED at %s ===\n", __func__ + # Print key state + # nvidia_gsp_state +end + +# ============================================================ +# USAGE NOTES +# ============================================================ +# 1. Compile guest kernel with CONFIG_DEBUG_INFO=y CONFIG_GDB_SCRIPTS=y +# 2. Copy vmlinux from guest to host +# 3. Launch QEMU: ./scripts/qemu_gdb_attach.sh +# 4. In another terminal: gdb -x tools/gdb_scripts/nvidia_init.gdb ./vmlinux +# 5. (gdb) target remote localhost:1234 +# 6. (gdb) continue +# +# For specific debugging: +# - PCI probe issues: break nvidia_probe +# - GSP firmware load: break nv_gsp_firmware_load +# - MSI-X setup: break pci_enable_msix_range +# - BAR mapping: break pci_iomap +# - RPC communication: break nv_gsp_rpc_send
\ No newline at end of file diff --git a/tools/guest_agent/guest_test_agent.py b/tools/guest_agent/guest_test_agent.py new file mode 100644 index 0000000..15a578d --- /dev/null +++ b/tools/guest_agent/guest_test_agent.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +""" +guest_test_agent.py - Runs inside guest VM, executes tests, reports results +Communication via vsock, serial, or HTTP +""" + +import sys +import os +import json +import time +import subprocess +import threading +import socket +import argparse +from datetime import datetime +from pathlib import Path + +class GuestTestAgent: + def __init__(self, listen_port=9999, report_url=None, vsock_cid=3, vsock_port=9999): + self.listen_port = listen_port + self.report_url = report_url + self.vsock_cid = vsock_cid + self.vsock_port = vsock_port + self.running = False + self.results = [] + + def start_server(self): + """Start command listener (TCP or vsock)""" + self.running = True + + # Try vsock first (preferred for VM-host communication) + try: + import socket + if hasattr(socket, 'AF_VSOCK'): + self.server = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) + self.server.bind((self.vsock_cid, self.vsock_port)) + self.server.listen(5) + print(f"[{datetime.now()}] Guest agent listening on vsock CID:{self.vsock_cid} PORT:{self.vsock_port}") + else: + raise ImportError("vsock not available") + except Exception: + # Fallback to TCP + self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.server.bind(('0.0.0.0', self.listen_port)) + self.server.listen(5) + print(f"[{datetime.now()}] Guest agent listening on TCP port {self.listen_port}") + + while self.running: + try: + conn, addr = self.server.accept() + threading.Thread(target=self._handle_connection, args=(conn,), daemon=True).start() + except Exception as e: + if self.running: + print(f"[{datetime.now()}] Server error: {e}") + + def _handle_connection(self, conn): + """Handle incoming test commands""" + try: + data = conn.recv(4096).decode('utf-8') + if not data: + return + + cmd = json.loads(data) + action = cmd.get('action') + test_id = cmd.get('test_id', 'unknown') + + print(f"[{datetime.now()}] Received command: {action} (test_id: {test_id})") + + if action == 'ping': + result = {'status': 'ok', 'agent': 'guest_test_agent', 'version': '1.0'} + elif action == 'run_test': + result = self._run_test(cmd.get('test'), cmd.get('args', {})) + elif action == 'run_suite': + result = self._run_suite(cmd.get('suite')) + elif action == 'get_results': + result = {'results': self.results} + elif action == 'shutdown': + self.running = False + result = {'status': 'shutting_down'} + else: + result = {'error': f'Unknown action: {action}'} + + result['test_id'] = test_id + conn.send(json.dumps(result).encode('utf-8')) + + except Exception as e: + conn.send(json.dumps({'error': str(e)}).encode('utf-8')) + finally: + conn.close() + + def _run_test(self, test_name, args): + """Run a single test""" + test_method = getattr(self, f'test_{test_name}', None) + if not test_method: + return {'status': 'error', 'message': f'Unknown test: {test_name}'} + + start = time.time() + try: + result = test_method(**args) + result['duration'] = time.time() - start + result['timestamp'] = datetime.now().isoformat() + self.results.append(result) + return result + except Exception as e: + return {'status': 'error', 'message': str(e), 'duration': time.time() - start} + + def _run_suite(self, suite_name): + """Run a test suite""" + suite_method = getattr(self, f'suite_{suite_name}', None) + if not suite_method: + return {'status': 'error', 'message': f'Unknown suite: {suite_name}'} + + return suite_method() + + # ============================================================ + # INDIVIDUAL TESTS + # ============================================================ + + def test_nvidia_smi(self): + """Run nvidia-smi and verify GPU detection""" + try: + out = subprocess.check_output(['nvidia-smi'], text=True, timeout=30) + return { + 'test': 'nvidia_smi', + 'status': 'pass' if 'NVIDIA' in out else 'fail', + 'output': out[:2000] + } + except subprocess.CalledProcessError as e: + return {'test': 'nvidia_smi', 'status': 'fail', 'error': str(e), 'output': e.output} + except Exception as e: + return {'test': 'nvidia_smi', 'status': 'error', 'message': str(e)} + + def test_cuda_vectoradd(self): + """Compile and run CUDA vectorAdd sample""" + try: + # Find CUDA samples + cuda_samples = Path('/usr/local/cuda/samples') + if not cuda_samples.exists(): + cuda_samples = Path('/opt/cuda/samples') + + vecadd_dir = cuda_samples / '1_Utilities' / 'vectorAdd' + if not vecadd_dir.exists(): + return {'test': 'cuda_vectoradd', 'status': 'skip', 'message': 'vectorAdd sample not found'} + + # Build + subprocess.check_call(['make'], cwd=vecadd_dir, timeout=60) + + # Run + out = subprocess.check_output(['./vectorAdd'], cwd=vecadd_dir, text=True, timeout=30) + + return { + 'test': 'cuda_vectoradd', + 'status': 'pass' if 'Test passed' in out else 'fail', + 'output': out[:2000] + } + except subprocess.CalledProcessError as e: + return {'test': 'cuda_vectoradd', 'status': 'fail', 'error': str(e), 'output': e.output} + except Exception as e: + return {'test': 'cuda_vectoradd', 'status': 'error', 'message': str(e)} + + def test_cuda_devquery(self): + """Run CUDA deviceQuery""" + try: + cuda_samples = Path('/usr/local/cuda/samples') + if not cuda_samples.exists(): + cuda_samples = Path('/opt/cuda/samples') + + devq_dir = cuda_samples / '1_Utilities' / 'deviceQuery' + if not devq_dir.exists(): + return {'test': 'cuda_devquery', 'status': 'skip', 'message': 'deviceQuery sample not found'} + + subprocess.check_call(['make'], cwd=devq_dir, timeout=60) + out = subprocess.check_output(['./deviceQuery'], cwd=devq_dir, text=True, timeout=30) + + return { + 'test': 'cuda_devquery', + 'status': 'pass' if 'Result = PASS' in out else 'fail', + 'output': out[:3000] + } + except Exception as e: + return {'test': 'cuda_devquery', 'status': 'error', 'message': str(e)} + + def test_vulkan_info(self): + """Run vulkaninfo""" + try: + out = subprocess.check_output(['vulkaninfo'], text=True, timeout=30) + return { + 'test': 'vulkan_info', + 'status': 'pass' if 'GPU id' in out else 'fail', + 'output': out[:3000] + } + except Exception as e: + return {'test': 'vulkan_info', 'status': 'error', 'message': str(e)} + + def test_vkcube(self): + """Run vkcube (headless)""" + try: + # vkcube may need display; try with xvfb or skip + out = subprocess.check_output(['vkcube', '--duration', '2'], text=True, timeout=30) + return {'test': 'vkcube', 'status': 'pass', 'output': out[:2000]} + except FileNotFoundError: + return {'test': 'vkcube', 'status': 'skip', 'message': 'vkcube not installed'} + except Exception as e: + return {'test': 'vkcube', 'status': 'error', 'message': str(e)} + + def test_glxgears(self): + """Run glxgears (brief)""" + try: + out = subprocess.check_output(['timeout', '5', 'glxgears'], text=True, timeout=15) + return {'test': 'glxgears', 'status': 'pass', 'output': out[:1000]} + except Exception as e: + return {'test': 'glxgears', 'status': 'error', 'message': str(e)} + + def test_ffmpeg_nvenc(self): + """Test FFmpeg NVENC""" + try: + out = subprocess.check_output(['ffmpeg', '-encoders'], text=True, timeout=10) + has_nvenc = 'h264_nvenc' in out or 'hevc_nvenc' in out + return { + 'test': 'ffmpeg_nvenc', + 'status': 'pass' if has_nvenc else 'fail', + 'output': out[:2000] + } + except Exception as e: + return {'test': 'ffmpeg_nvenc', 'status': 'error', 'message': str(e)} + + def test_pytorch(self): + """Test PyTorch CUDA""" + try: + code = """ +import torch +print(f'CUDA available: {torch.cuda.is_available()}') +if torch.cuda.is_available(): + print(f'Device count: {torch.cuda.device_count()}') + print(f'Device name: {torch.cuda.get_device_name(0)}') + x = torch.randn(100, 100).cuda() + y = torch.randn(100, 100).cuda() + z = torch.mm(x, y) + print(f'Matmul result shape: {z.shape}') + print('PyTorch CUDA test: PASS') +else: + print('PyTorch CUDA test: FAIL - CUDA not available') +""" + out = subprocess.check_output([sys.executable, '-c', code], text=True, timeout=60) + return { + 'test': 'pytorch', + 'status': 'pass' if 'PASS' in out else 'fail', + 'output': out + } + except Exception as e: + return {'test': 'pytorch', 'status': 'error', 'message': str(e)} + + # ============================================================ + # TEST SUITES + # ============================================================ + + def suite_smoke(self): + """Quick smoke test suite""" + tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd'] + return self._run_test_list(tests) + + def suite_compute(self): + """Compute-focused test suite""" + tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd', 'pytorch'] + return self._run_test_list(tests) + + def suite_graphics(self): + """Graphics-focused test suite""" + tests = ['nvidia_smi', 'vulkan_info', 'vkcube', 'glxgears'] + return self._run_test_list(tests) + + def suite_video(self): + """Video encode/decode test suite""" + tests = ['nvidia_smi', 'ffmpeg_nvenc'] + return self._run_test_list(tests) + + def suite_full(self): + """Full test suite""" + tests = ['nvidia_smi', 'cuda_devquery', 'cuda_vectoradd', 'vulkan_info', 'vkcube', 'glxgears', 'ffmpeg_nvenc', 'pytorch'] + return self._run_test_list(tests) + + def _run_test_list(self, tests): + """Run a list of tests""" + results = [] + for test in tests: + print(f"[{datetime.now()}] Running suite test: {test}") + result = self._run_test(test, {}) + results.append(result) + print(f" -> {result.get('status', 'unknown')}") + + passed = sum(1 for r in results if r.get('status') == 'pass') + failed = sum(1 for r in results if r.get('status') in ('fail', 'error')) + skipped = sum(1 for r in results if r.get('status') == 'skip') + + return { + 'suite': 'custom', + 'total': len(tests), + 'passed': passed, + 'failed': failed, + 'skipped': skipped, + 'results': results + } + +def main(): + parser = argparse.ArgumentParser(description="Guest Test Agent") + parser.add_argument('--port', type=int, default=9999, help='TCP listen port') + parser.add_argument('--vsock-cid', type=int, default=3, help='VSOCK CID') + parser.add_argument('--vsock-port', type=int, default=9999, help='VSOCK port') + parser.add_argument('--run', help='Run single test and exit (for standalone mode)') + parser.add_argument('--suite', help='Run test suite and exit') + args = parser.parse_args() + + agent = GuestTestAgent(listen_port=args.port, vsock_cid=args.vsock_cid, vsock_port=args.vsock_port) + + if args.run: + # Standalone mode + result = agent._run_test(args.run, {}) + print(json.dumps(result, indent=2)) + elif args.suite: + result = getattr(agent, f'suite_{args.suite}')() + print(json.dumps(result, indent=2)) + else: + # Server mode + try: + agent.start_server() + except KeyboardInterrupt: + print("\nShutting down...") + agent.running = False + +if __name__ == "__main__": + main()
\ No newline at end of file diff --git a/tools/guest_init_monitor.py b/tools/guest_init_monitor.py new file mode 100644 index 0000000..bc34a08 --- /dev/null +++ b/tools/guest_init_monitor.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +""" +guest_init_monitor.py - Monitor guest kernel initialization for NVIDIA driver +Correlates host tracepoints with guest dmesg/serial output +""" + +import sys +import os +import re +import time +import subprocess +import threading +import json +from datetime import datetime +from collections import deque +from pathlib import Path + +class GuestInitMonitor: + def __init__(self, guest_serial="/dev/ttyS0", host_trace_pipe="/sys/kernel/debug/tracing/trace_pipe"): + self.guest_serial = guest_serial + self.host_trace_pipe = host_trace_pipe + self.guest_log = deque(maxlen=1000) + self.host_events = deque(maxlen=1000) + self.failure_patterns = self._load_failure_patterns() + self.running = False + + def _load_failure_patterns(self): + return { + "pci_probe_fail": [ + r"nvidia: probe of .* failed with error -?\d+", + r"pci .*: can't claim BAR \d+", + r"pci .*: BAR \d+: no space for", + ], + "gsp_firmware_fail": [ + r"GSP.*firmware.*load.*failed", + r"GSP.*bootstrap.*failed", + r"GSP.*RPC.*timeout", + r"nv_gsp_firmware_load.*error", + ], + "msix_fail": [ + r"MSI-X.*failed", + r"pci_enable_msix.*failed", + r"request_threaded_irq.*failed", + ], + "bar_map_fail": [ + r"ioremap.*failed", + r"pci_iomap.*failed", + r"BAR.*mapping.*failed", + ], + "dma_fail": [ + r"dma_map.*failed", + r"IOMMU.*fault", + r"DMAR.*error", + ], + "vgpu_vfio_fail": [ + r"vGPU VFIO.*failed", + r"nvidia_vgpu_vfio_get_ops.*failed", + r"mdev.*register.*failed", + ], + } + + def start(self): + self.running = True + self.guest_thread = threading.Thread(target=self._monitor_guest_serial, daemon=True) + self.host_thread = threading.Thread(target=self._monitor_host_traces, daemon=True) + self.guest_thread.start() + self.host_thread.start() + print(f"[{datetime.now()}] Guest init monitor started") + print(f" Guest serial: {self.guest_serial}") + print(f" Host trace pipe: {self.host_trace_pipe}") + + def stop(self): + self.running = False + if hasattr(self, 'guest_thread'): + self.guest_thread.join(timeout=2) + if hasattr(self, 'host_thread'): + self.host_thread.join(timeout=2) + print(f"[{datetime.now()}] Guest init monitor stopped") + + def _monitor_guest_serial(self): + """Monitor guest kernel log via serial console""" + try: + # Try to open serial device or use virsh console + proc = subprocess.Popen( + ["timeout", "300", "cat", self.guest_serial], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + for line in proc.stdout: + if not self.running: + break + self._process_guest_line(line.strip()) + except Exception as e: + print(f"[{datetime.now()}] Guest serial monitor error: {e}") + + def _monitor_host_traces(self): + """Monitor host kernel tracepoints""" + try: + proc = subprocess.Popen( + ["cat", self.host_trace_pipe], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + for line in proc.stdout: + if not self.running: + break + self._process_host_trace(line.strip()) + except Exception as e: + print(f"[{datetime.now()}] Host trace monitor error: {e}") + + def _process_guest_line(self, line): + """Process a line from guest serial console""" + timestamp = datetime.now().isoformat() + entry = {"timestamp": timestamp, "source": "guest", "line": line} + self.guest_log.append(entry) + + # Check for failure patterns + failure = self._classify_failure(line) + if failure: + self._report_failure(failure, line, "guest") + + # Print important lines + if any(kw in line.lower() for kw in ["nvidia", "gsp", "vfio", "mdev", "error", "fail", "timeout"]): + print(f"[GUEST] {line}") + + def _process_host_trace(self, line): + """Process a line from host trace_pipe""" + timestamp = datetime.now().isoformat() + entry = {"timestamp": timestamp, "source": "host_trace", "line": line} + self.host_events.append(entry) + + # Look for GSP emulation tracepoints + if "gsp_emu" in line: + print(f"[HOST TRACE] {line}") + # Correlate with recent guest logs + self._correlate_events(line) + + def _classify_failure(self, line): + """Classify failure type based on patterns""" + for failure_type, patterns in self.failure_patterns.items(): + for pattern in patterns: + if re.search(pattern, line, re.IGNORECASE): + return failure_type + return None + + def _report_failure(self, failure_type, line, source): + """Report a detected failure""" + report = { + "timestamp": datetime.now().isoformat(), + "failure_type": failure_type, + "source": source, + "line": line, + "recent_guest_logs": list(self.guest_log)[-20:], + "recent_host_events": list(self.host_events)[-20:], + } + print(f"\n{'='*60}") + print(f"FAILURE DETECTED: {failure_type}") + print(f"Source: {source}") + print(f"Line: {line}") + print(f"{'='*60}\n") + + # Save failure report + report_dir = Path("/tmp/gsp_emu_failures") + report_dir.mkdir(exist_ok=True) + report_file = report_dir / f"failure_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json" + with open(report_file, 'w') as f: + json.dump(report, f, indent=2) + print(f"Failure report saved: {report_file}") + + def _correlate_events(self, host_event): + """Correlate host tracepoint with recent guest activity""" + # Look for matching sequence IDs, RPC types, etc. + pass + + def get_summary(self): + """Get monitoring summary""" + return { + "guest_log_entries": len(self.guest_log), + "host_events": len(self.host_events), + "recent_guest": list(self.guest_log)[-10:], + "recent_host": list(self.host_events)[-10:], + } + +def main(): + import argparse + parser = argparse.ArgumentParser(description="Monitor guest NVIDIA driver initialization") + parser.add_argument("--guest-serial", default="/dev/ttyS0", help="Guest serial device") + parser.add_argument("--host-trace", default="/sys/kernel/debug/tracing/trace_pipe", help="Host trace pipe") + parser.add_argument("--duration", type=int, default=300, help="Monitor duration (seconds)") + args = parser.parse_args() + + monitor = GuestInitMonitor(args.guest_serial, args.host_trace) + monitor.start() + + try: + time.sleep(args.duration) + except KeyboardInterrupt: + pass + finally: + monitor.stop() + summary = monitor.get_summary() + print(json.dumps(summary, indent=2)) + +if __name__ == "__main__": + main()
\ No newline at end of file |
