PCILeechFWGenerator 0.5.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +17 -0
- cli/build_wrapper.py +41 -0
- cli/cli.py +222 -0
- cli/config.py +66 -0
- cli/container.py +322 -0
- cli/device_prioritizer.py +91 -0
- cli/flash.py +116 -0
- cli/vfio.py +87 -0
- cli/vfio_constants.py +160 -0
- cli/vfio_diagnostics.py +889 -0
- cli/vfio_handler.py +852 -0
- cli/vfio_helpers.py +524 -0
- device_clone/__init__.py +95 -0
- device_clone/behavior_profiler.py +1634 -0
- device_clone/board_config.py +310 -0
- device_clone/config_space_manager.py +1057 -0
- device_clone/constants.py +92 -0
- device_clone/device_config.py +504 -0
- device_clone/fallback_manager.py +182 -0
- device_clone/manufacturing_variance.py +761 -0
- device_clone/msix_capability.py +521 -0
- device_clone/pcileech_context.py +1986 -0
- device_clone/pcileech_generator.py +1047 -0
- device_clone/string_utils.py +33 -0
- device_clone/variance_manager.py +212 -0
- donor_dump/Makefile +99 -0
- donor_dump/donor_dump.c +416 -0
- file_management/__init__.py +23 -0
- file_management/donor_dump_manager.py +1295 -0
- file_management/file_manager.py +810 -0
- file_management/option_rom_manager.py +525 -0
- file_management/repo_manager.py +346 -0
- pci_capability/__init__.py +79 -0
- pci_capability/_pruning.py +382 -0
- pci_capability/compat.py +465 -0
- pci_capability/constants.py +131 -0
- pci_capability/core.py +469 -0
- pci_capability/msix.py +432 -0
- pci_capability/patches.py +536 -0
- pci_capability/processor.py +595 -0
- pci_capability/rules.py +482 -0
- pci_capability/types.py +134 -0
- pci_capability/utils.py +261 -0
- pcileechfwgenerator-0.5.7.dist-info/METADATA +21 -0
- pcileechfwgenerator-0.5.7.dist-info/RECORD +179 -0
- pcileechfwgenerator-0.5.7.dist-info/WHEEL +5 -0
- pcileechfwgenerator-0.5.7.dist-info/entry_points.txt +6 -0
- pcileechfwgenerator-0.5.7.dist-info/licenses/LICENSE.txt +21 -0
- pcileechfwgenerator-0.5.7.dist-info/top_level.txt +10 -0
- scripts/__init__.py +8 -0
- scripts/driver_scrape.py +682 -0
- scripts/kernel_utils.py +312 -0
- scripts/state_machine_extractor.py +851 -0
- templates/README.md +95 -0
- templates/sv/advanced_controller.sv.j2 +80 -0
- templates/sv/bar_controller.sv.j2 +211 -0
- templates/sv/basic_bar_controller.sv.j2 +52 -0
- templates/sv/cfg_shadow.sv.j2 +255 -0
- templates/sv/clock_crossing.sv.j2 +50 -0
- templates/sv/clock_domain_logic.sv.j2 +20 -0
- templates/sv/device_config.sv.j2 +31 -0
- templates/sv/device_specific_ports.sv.j2 +25 -0
- templates/sv/error_counters.sv.j2 +14 -0
- templates/sv/error_declarations.sv.j2 +26 -0
- templates/sv/error_detection.sv.j2 +64 -0
- templates/sv/error_handling.sv.j2 +128 -0
- templates/sv/error_handling_complete.sv.j2 +14 -0
- templates/sv/error_injection.sv.j2 +24 -0
- templates/sv/error_logging.sv.j2 +22 -0
- templates/sv/error_outputs.sv.j2 +14 -0
- templates/sv/error_state_machine.sv.j2 +83 -0
- templates/sv/interrupt_logic.sv.j2 +38 -0
- templates/sv/main_module.sv.j2 +257 -0
- templates/sv/msix_capability_registers.sv.j2 +120 -0
- templates/sv/msix_implementation.sv.j2 +130 -0
- templates/sv/msix_table.sv.j2 +242 -0
- templates/sv/option_rom_bar_window.sv.j2 +128 -0
- templates/sv/option_rom_spi_flash.sv.j2 +527 -0
- templates/sv/pcileech_cfgspace.coe.j2 +242 -0
- templates/sv/pcileech_fifo.sv.j2 +297 -0
- templates/sv/pcileech_tlps128_bar_controller.sv.j2 +867 -0
- templates/sv/performance_counters.sv.j2 +333 -0
- templates/sv/pmcsr_stub.sv.j2 +94 -0
- templates/sv/power_declarations.sv.j2 +19 -0
- templates/sv/power_integration.sv.j2 +56 -0
- templates/sv/power_management.sv.j2 +95 -0
- templates/sv/read_logic.sv.j2 +35 -0
- templates/sv/register_declarations.sv.j2 +97 -0
- templates/sv/register_logic.sv.j2 +59 -0
- templates/sv/top_level_wrapper.sv.j2 +200 -0
- templates/tcl/bitstream.j2 +161 -0
- templates/tcl/common/header.j2 +25 -0
- templates/tcl/constraints.j2 +130 -0
- templates/tcl/device_setup.j2 +229 -0
- templates/tcl/header.j2 +25 -0
- templates/tcl/implementation.j2 +48 -0
- templates/tcl/ip_config.j2 +93 -0
- templates/tcl/ip_config_axi_pcie.j2 +33 -0
- templates/tcl/ip_config_pcie7x.j2 +57 -0
- templates/tcl/ip_config_ultrascale.j2 +46 -0
- templates/tcl/master_build.j2 +329 -0
- templates/tcl/pcileech_build.j2 +271 -0
- templates/tcl/pcileech_build_script.tcl.j2 +90 -0
- templates/tcl/pcileech_constraints.j2 +109 -0
- templates/tcl/pcileech_generate_project.j2 +230 -0
- templates/tcl/pcileech_implementation.j2 +102 -0
- templates/tcl/pcileech_project_setup.j2 +68 -0
- templates/tcl/pcileech_sources.j2 +118 -0
- templates/tcl/project_setup.j2 +41 -0
- templates/tcl/sources.j2 +64 -0
- templates/tcl/synthesis.j2 +71 -0
- templates/template_mapping.py +116 -0
- templating/__init__.py +50 -0
- templating/advanced_sv_error.py +123 -0
- templating/advanced_sv_features.py +274 -0
- templating/advanced_sv_generator.py +276 -0
- templating/advanced_sv_perf.py +423 -0
- templating/advanced_sv_power.py +167 -0
- templating/systemverilog_generator.py +912 -0
- templating/tcl_builder.py +876 -0
- templating/template_renderer.py +411 -0
- templating/templates/python/build_integration.py.j2 +90 -0
- templating/templates/python/pcileech_build_integration.py.j2 +228 -0
- templating/templates/systemverilog/advanced/advanced_controller.sv.j2 +80 -0
- templating/templates/systemverilog/advanced/clock_crossing.sv.j2 +50 -0
- templating/templates/systemverilog/advanced/error_counters.sv.j2 +14 -0
- templating/templates/systemverilog/advanced/error_declarations.sv.j2 +26 -0
- templating/templates/systemverilog/advanced/error_detection.sv.j2 +64 -0
- templating/templates/systemverilog/advanced/error_handling.sv.j2 +128 -0
- templating/templates/systemverilog/advanced/error_handling_complete.sv.j2 +14 -0
- templating/templates/systemverilog/advanced/error_injection.sv.j2 +24 -0
- templating/templates/systemverilog/advanced/error_logging.sv.j2 +22 -0
- templating/templates/systemverilog/advanced/error_outputs.sv.j2 +14 -0
- templating/templates/systemverilog/advanced/error_state_machine.sv.j2 +83 -0
- templating/templates/systemverilog/advanced/main_module.sv.j2 +257 -0
- templating/templates/systemverilog/advanced/performance_counters.sv.j2 +333 -0
- templating/templates/systemverilog/advanced/power_management.sv.j2 +95 -0
- templating/templates/systemverilog/bar_controller.sv.j2 +211 -0
- templating/templates/systemverilog/basic_bar_controller.sv.j2 +52 -0
- templating/templates/systemverilog/cfg_shadow.sv.j2 +255 -0
- templating/templates/systemverilog/components/clock_domain_logic.sv.j2 +20 -0
- templating/templates/systemverilog/components/device_specific_ports.sv.j2 +25 -0
- templating/templates/systemverilog/components/interrupt_logic.sv.j2 +38 -0
- templating/templates/systemverilog/components/power_declarations.sv.j2 +19 -0
- templating/templates/systemverilog/components/power_integration.sv.j2 +56 -0
- templating/templates/systemverilog/components/read_logic.sv.j2 +35 -0
- templating/templates/systemverilog/components/register_declarations.sv.j2 +97 -0
- templating/templates/systemverilog/components/register_logic.sv.j2 +59 -0
- templating/templates/systemverilog/device_config.sv.j2 +31 -0
- templating/templates/systemverilog/modules/pmcsr_stub.sv.j2 +94 -0
- templating/templates/systemverilog/msix_capability_registers.sv.j2 +120 -0
- templating/templates/systemverilog/msix_implementation.sv.j2 +130 -0
- templating/templates/systemverilog/msix_table.sv.j2 +242 -0
- templating/templates/systemverilog/option_rom_bar_window.sv.j2 +128 -0
- templating/templates/systemverilog/option_rom_spi_flash.sv.j2 +527 -0
- templating/templates/systemverilog/pcileech_cfgspace.coe.j2 +242 -0
- templating/templates/systemverilog/pcileech_fifo.sv.j2 +297 -0
- templating/templates/systemverilog/pcileech_tlps128_bar_controller.sv.j2 +867 -0
- templating/templates/systemverilog/top_level_wrapper.sv.j2 +200 -0
- templating/templates/tcl/pcileech_build.j2 +271 -0
- templating/templates/tcl/pcileech_build_script.tcl.j2 +90 -0
- templating/templates/tcl/pcileech_generate_project.j2 +230 -0
- tui/__init__.py +13 -0
- tui/core/__init__.py +13 -0
- tui/core/build_orchestrator.py +1031 -0
- tui/core/config_manager.py +616 -0
- tui/core/device_manager.py +574 -0
- tui/core/status_monitor.py +404 -0
- tui/main.py +1233 -0
- tui/models/__init__.py +18 -0
- tui/models/config.py +268 -0
- tui/models/device.py +136 -0
- tui/models/error.py +190 -0
- tui/models/progress.py +126 -0
- tui/styles/main.tcss +290 -0
- vivado_handling/__init__.py +28 -0
- vivado_handling/vivado_build_with_errors.py +132 -0
- vivado_handling/vivado_error_reporter.py +916 -0
- vivado_handling/vivado_utils.py +293 -0
cli/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""CLI components for PCILeech FW Generator."""
|
|
2
|
+
|
|
3
|
+
from .cli import get_parser, main
|
|
4
|
+
from .config import BuildConfig
|
|
5
|
+
from .container import require_podman, run_build
|
|
6
|
+
from .flash import flash_firmware
|
|
7
|
+
from .vfio_handler import VFIOBinder
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"get_parser",
|
|
11
|
+
"main",
|
|
12
|
+
"BuildConfig",
|
|
13
|
+
"run_build",
|
|
14
|
+
"require_podman",
|
|
15
|
+
"flash_firmware",
|
|
16
|
+
"VFIOBinder",
|
|
17
|
+
]
|
cli/build_wrapper.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Wrapper script to properly invoke the build.py module with correct Python path setup.
|
|
4
|
+
This ensures that relative imports work correctly in the container environment.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Determine if we're in container or local environment
|
|
12
|
+
if Path("/app").exists():
|
|
13
|
+
# Container environment
|
|
14
|
+
app_dir = Path("/app")
|
|
15
|
+
src_dir = app_dir / "src"
|
|
16
|
+
else:
|
|
17
|
+
# Local environment - we're in src/cli, so go up two levels to get to project root
|
|
18
|
+
app_dir = Path(__file__).parent.parent.parent.absolute()
|
|
19
|
+
src_dir = app_dir / "src"
|
|
20
|
+
|
|
21
|
+
# Ensure both directories are in Python path
|
|
22
|
+
if str(app_dir) not in sys.path:
|
|
23
|
+
sys.path.insert(0, str(app_dir))
|
|
24
|
+
if str(src_dir) not in sys.path:
|
|
25
|
+
sys.path.insert(0, str(src_dir))
|
|
26
|
+
|
|
27
|
+
# Change to the src directory so relative imports work
|
|
28
|
+
if src_dir.exists():
|
|
29
|
+
os.chdir(str(src_dir))
|
|
30
|
+
else:
|
|
31
|
+
print(f"Error: Source directory {src_dir} does not exist")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
# Now import and run the build module
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
# Import the build module
|
|
37
|
+
import build
|
|
38
|
+
|
|
39
|
+
# Run the main function with the original arguments
|
|
40
|
+
sys.argv[0] = "build.py" # Fix the script name for argument parsing
|
|
41
|
+
build.main()
|
cli/cli.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""cli – one front‑door for the whole tool‑chain.
|
|
3
|
+
|
|
4
|
+
Usage examples
|
|
5
|
+
~~~~~~~~~~~~~~
|
|
6
|
+
# guided build flow (device & board pickers)
|
|
7
|
+
./cli build
|
|
8
|
+
|
|
9
|
+
# scripted build for CI (non‑interactive)
|
|
10
|
+
./cli build --bdf 0000:01:00.0 --board pcileech_75t484_x1 \
|
|
11
|
+
--device-type network --advanced-sv
|
|
12
|
+
|
|
13
|
+
# flash an already‑generated bitstream
|
|
14
|
+
./cli flash output/firmware.bin --board pcileech_75t484_x1
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import logging
|
|
20
|
+
import os
|
|
21
|
+
import re
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
from log_config import get_logger, setup_logging
|
|
28
|
+
from shell import Shell
|
|
29
|
+
|
|
30
|
+
from .container import BuildConfig, run_build # new unified runner
|
|
31
|
+
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
# Helpers – PCIe enumeration & interactive pickers
|
|
36
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
PCI_RE = re.compile(
|
|
38
|
+
r"(?P<bdf>[0-9a-fA-F:.]+) .*?\["
|
|
39
|
+
r"(?P<class>[0-9a-fA-F]{4})\]: .*?\["
|
|
40
|
+
r"(?P<ven>[0-9a-fA-F]{4}):(?P<dev>[0-9a-fA-F]{4})\]"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def list_pci_devices() -> List[Dict[str, str]]:
|
|
45
|
+
out = Shell().run("lspci -Dnn")
|
|
46
|
+
devs: list[dict[str, str]] = []
|
|
47
|
+
for line in out.splitlines():
|
|
48
|
+
m = PCI_RE.match(line)
|
|
49
|
+
if m:
|
|
50
|
+
d = m.groupdict()
|
|
51
|
+
d["pretty"] = line
|
|
52
|
+
devs.append(d)
|
|
53
|
+
return devs
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def pick(lst: list[str], prompt: str) -> str:
|
|
57
|
+
for i, item in enumerate(lst):
|
|
58
|
+
print(f" [{i}] {item}")
|
|
59
|
+
while True:
|
|
60
|
+
sel = input(prompt).strip()
|
|
61
|
+
if not sel and lst:
|
|
62
|
+
return lst[0]
|
|
63
|
+
try:
|
|
64
|
+
return lst[int(sel)]
|
|
65
|
+
except Exception:
|
|
66
|
+
print(" Invalid selection – try again.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def choose_device() -> Dict[str, str]:
|
|
70
|
+
devs = list_pci_devices()
|
|
71
|
+
if not devs:
|
|
72
|
+
raise RuntimeError("No PCIe devices found – are you root?")
|
|
73
|
+
for i, dev in enumerate(devs):
|
|
74
|
+
print(f" [{i}] {dev['pretty']}")
|
|
75
|
+
return devs[int(input("Select donor device #: "))]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
SUPPORTED_BOARDS = [
|
|
79
|
+
"pcileech_75t484_x1",
|
|
80
|
+
"pcileech_35t484_x1",
|
|
81
|
+
"pcileech_35t325_x4",
|
|
82
|
+
"pcileech_35t325_x1",
|
|
83
|
+
"pcileech_100t484_x1",
|
|
84
|
+
"pcileech_enigma_x1",
|
|
85
|
+
"pcileech_squirrel",
|
|
86
|
+
"pcileech_pciescreamer_xc7a35",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
# CLI setup
|
|
92
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_sub(parser: argparse._SubParsersAction):
|
|
96
|
+
p = parser.add_parser("build", help="Build firmware (guided or scripted)")
|
|
97
|
+
p.add_argument("--bdf", help="PCI BDF (skip for interactive picker)")
|
|
98
|
+
p.add_argument("--board", choices=SUPPORTED_BOARDS, help="FPGA board")
|
|
99
|
+
p.add_argument(
|
|
100
|
+
"--device-type",
|
|
101
|
+
default="generic",
|
|
102
|
+
choices=["generic", "network", "storage", "graphics", "audio"],
|
|
103
|
+
help="Type of device being cloned",
|
|
104
|
+
)
|
|
105
|
+
p.add_argument(
|
|
106
|
+
"--advanced-sv", action="store_true", help="Enable advanced SV features"
|
|
107
|
+
)
|
|
108
|
+
p.add_argument("--enable-variance", action="store_true", help="Enable variance")
|
|
109
|
+
p.add_argument(
|
|
110
|
+
"--auto-fix", action="store_true", help="Let VFIOBinder auto-remediate issues"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Add fallback control group
|
|
114
|
+
fallback_group = p.add_argument_group("Fallback Control")
|
|
115
|
+
fallback_group.add_argument(
|
|
116
|
+
"--fallback-mode",
|
|
117
|
+
choices=["none", "prompt", "auto"],
|
|
118
|
+
default="none",
|
|
119
|
+
help="Control fallback behavior (none=fail-fast, prompt=ask, auto=allow)",
|
|
120
|
+
)
|
|
121
|
+
fallback_group.add_argument(
|
|
122
|
+
"--allow-fallbacks", type=str, help="Comma-separated list of allowed fallbacks"
|
|
123
|
+
)
|
|
124
|
+
fallback_group.add_argument(
|
|
125
|
+
"--deny-fallbacks", type=str, help="Comma-separated list of denied fallbacks"
|
|
126
|
+
)
|
|
127
|
+
fallback_group.add_argument(
|
|
128
|
+
"--legacy-compatibility",
|
|
129
|
+
action="store_true",
|
|
130
|
+
help="Enable legacy compatibility mode (temporarily restores old fallback behavior)",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def flash_sub(parser: argparse._SubParsersAction):
|
|
135
|
+
p = parser.add_parser("flash", help="Flash a firmware binary via usbloader")
|
|
136
|
+
p.add_argument("firmware", help="Path to .bin")
|
|
137
|
+
p.add_argument(
|
|
138
|
+
"--board", required=True, choices=SUPPORTED_BOARDS, help="FPGA board"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_parser() -> argparse.ArgumentParser:
|
|
143
|
+
ap = argparse.ArgumentParser("cli", description=__doc__)
|
|
144
|
+
sub = ap.add_subparsers(
|
|
145
|
+
dest="cmd",
|
|
146
|
+
required=True,
|
|
147
|
+
help="Command to run (build/flash)",
|
|
148
|
+
)
|
|
149
|
+
build_sub(sub)
|
|
150
|
+
flash_sub(sub)
|
|
151
|
+
return ap
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def flash_bin(path: Path):
|
|
155
|
+
from .flash import flash_firmware
|
|
156
|
+
|
|
157
|
+
flash_firmware(path)
|
|
158
|
+
|
|
159
|
+
logger.info("Firmware flashed successfully ✓")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
# Entrypoint
|
|
164
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def main(argv: Optional[List[str]] = None):
|
|
168
|
+
# Setup proper logging with color support
|
|
169
|
+
setup_logging(level=logging.INFO)
|
|
170
|
+
|
|
171
|
+
args = get_parser().parse_args(argv)
|
|
172
|
+
|
|
173
|
+
if args.cmd == "build":
|
|
174
|
+
bdf = args.bdf or choose_device()["bdf"]
|
|
175
|
+
board = args.board or pick(SUPPORTED_BOARDS, "Board #: ")
|
|
176
|
+
# Process fallback lists
|
|
177
|
+
allowed_fallbacks = []
|
|
178
|
+
if hasattr(args, "allow_fallbacks") and args.allow_fallbacks:
|
|
179
|
+
allowed_fallbacks = [f.strip() for f in args.allow_fallbacks.split(",")]
|
|
180
|
+
|
|
181
|
+
denied_fallbacks = []
|
|
182
|
+
if hasattr(args, "deny_fallbacks") and args.deny_fallbacks:
|
|
183
|
+
denied_fallbacks = [f.strip() for f in args.deny_fallbacks.split(",")]
|
|
184
|
+
|
|
185
|
+
# Determine fallback mode based on legacy compatibility flag
|
|
186
|
+
fallback_mode = getattr(args, "fallback_mode", "none")
|
|
187
|
+
if (
|
|
188
|
+
hasattr(args, "legacy_compatibility")
|
|
189
|
+
and args.legacy_compatibility
|
|
190
|
+
and fallback_mode == "none"
|
|
191
|
+
):
|
|
192
|
+
logger.warning(
|
|
193
|
+
"Legacy compatibility mode enabled - using 'auto' fallback mode"
|
|
194
|
+
)
|
|
195
|
+
fallback_mode = "auto"
|
|
196
|
+
if not allowed_fallbacks:
|
|
197
|
+
allowed_fallbacks = [
|
|
198
|
+
"config-space",
|
|
199
|
+
"msix",
|
|
200
|
+
"behavior-profiling",
|
|
201
|
+
"build-integration",
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
cfg = BuildConfig(
|
|
205
|
+
bdf=bdf,
|
|
206
|
+
board=board,
|
|
207
|
+
device_type=args.device_type,
|
|
208
|
+
advanced_sv=args.advanced_sv,
|
|
209
|
+
enable_variance=args.enable_variance,
|
|
210
|
+
auto_fix=args.auto_fix,
|
|
211
|
+
fallback_mode=fallback_mode,
|
|
212
|
+
allowed_fallbacks=allowed_fallbacks,
|
|
213
|
+
denied_fallbacks=denied_fallbacks,
|
|
214
|
+
)
|
|
215
|
+
run_build(cfg)
|
|
216
|
+
|
|
217
|
+
elif args.cmd == "flash":
|
|
218
|
+
flash_bin(Path(args.firmware))
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if __name__ == "__main__":
|
|
222
|
+
main()
|
cli/config.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Configuration dataclass for PCILeech firmware generation."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class BuildConfig:
|
|
9
|
+
"""Strongly-typed configuration for firmware build process."""
|
|
10
|
+
|
|
11
|
+
# Device configuration
|
|
12
|
+
bdf: str
|
|
13
|
+
vendor: str
|
|
14
|
+
device: str
|
|
15
|
+
board: str
|
|
16
|
+
device_type: str
|
|
17
|
+
|
|
18
|
+
# Advanced features
|
|
19
|
+
advanced_sv: bool = True
|
|
20
|
+
enable_variance: bool = True
|
|
21
|
+
donor_dump: bool = True
|
|
22
|
+
auto_install_headers: bool = True
|
|
23
|
+
strict_vfio: bool = True # Fail hard if VFIO is not available
|
|
24
|
+
|
|
25
|
+
# Feature toggles
|
|
26
|
+
disable_power_management: bool = False
|
|
27
|
+
disable_error_handling: bool = False
|
|
28
|
+
disable_performance_counters: bool = False
|
|
29
|
+
|
|
30
|
+
flash: bool = True
|
|
31
|
+
|
|
32
|
+
# Timing configuration
|
|
33
|
+
behavior_profile_duration: int = 45
|
|
34
|
+
|
|
35
|
+
# Mode configuration
|
|
36
|
+
tui: bool = False
|
|
37
|
+
interactive: bool = False
|
|
38
|
+
|
|
39
|
+
# Runtime state
|
|
40
|
+
original_driver: Optional[str] = None
|
|
41
|
+
iommu_group: Optional[str] = None
|
|
42
|
+
vfio_device: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
"""Validate configuration after initialization."""
|
|
46
|
+
|
|
47
|
+
# Validate BDF format
|
|
48
|
+
import re
|
|
49
|
+
|
|
50
|
+
bdf_pattern = re.compile(
|
|
51
|
+
r"^[0-9a-fA-F]{4}:[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-7]$"
|
|
52
|
+
)
|
|
53
|
+
if not bdf_pattern.match(self.bdf):
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Invalid BDF format: {self.bdf}. Expected format: DDDD:BB:DD.F"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Validate vendor and device IDs
|
|
59
|
+
if not re.match(r"^[0-9a-fA-F]{4}$", self.vendor):
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Invalid vendor ID format: {self.vendor}. Expected 4-digit hex."
|
|
62
|
+
)
|
|
63
|
+
if not re.match(r"^[0-9a-fA-F]{4}$", self.device):
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Invalid device ID format: {self.device}. Expected 4-digit hex."
|
|
66
|
+
)
|
cli/container.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""container_build – unified VFIO‑aware Podman build runner"""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import textwrap
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
|
|
14
|
+
from log_config import get_logger
|
|
15
|
+
from shell import Shell
|
|
16
|
+
|
|
17
|
+
from .vfio_handler import VFIOBinder # auto‑fix & diagnostics baked in
|
|
18
|
+
|
|
19
|
+
# Import safe logging functions
|
|
20
|
+
try:
|
|
21
|
+
from ..string_utils import (log_debug_safe, log_error_safe, log_info_safe,
|
|
22
|
+
log_warning_safe)
|
|
23
|
+
except ImportError:
|
|
24
|
+
# Fallback implementations
|
|
25
|
+
def log_info_safe(logger, template, **kwargs):
|
|
26
|
+
logger.info(template.format(**kwargs))
|
|
27
|
+
|
|
28
|
+
def log_error_safe(logger, template, **kwargs):
|
|
29
|
+
logger.error(template.format(**kwargs))
|
|
30
|
+
|
|
31
|
+
def log_warning_safe(logger, template, **kwargs):
|
|
32
|
+
logger.warning(template.format(**kwargs))
|
|
33
|
+
|
|
34
|
+
def log_debug_safe(logger, template, **kwargs):
|
|
35
|
+
logger.debug(template.format(**kwargs))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
# Exceptions
|
|
43
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
class ContainerError(RuntimeError):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class VFIOError(RuntimeError):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EnvError(RuntimeError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
# Build configuration (thin wrapper over original)
|
|
58
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
59
|
+
@dataclass
|
|
60
|
+
class BuildConfig:
|
|
61
|
+
bdf: str
|
|
62
|
+
board: str
|
|
63
|
+
# feature toggles
|
|
64
|
+
advanced_sv: bool = False
|
|
65
|
+
device_type: str = "generic"
|
|
66
|
+
enable_variance: bool = False
|
|
67
|
+
disable_power_management: bool = False
|
|
68
|
+
disable_error_handling: bool = False
|
|
69
|
+
disable_performance_counters: bool = False
|
|
70
|
+
behavior_profile_duration: int = 30
|
|
71
|
+
# runtime toggles
|
|
72
|
+
auto_fix: bool = True # hand to VFIOBinder
|
|
73
|
+
container_tag: str = "latest"
|
|
74
|
+
container_image: str = "pcileech-fw-generator"
|
|
75
|
+
# fallback control options
|
|
76
|
+
fallback_mode: str = "none" # "none", "prompt", or "auto"
|
|
77
|
+
allowed_fallbacks: List[str] = field(default_factory=list)
|
|
78
|
+
denied_fallbacks: List[str] = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
def cmd_args(self) -> List[str]:
|
|
81
|
+
"""Translate config to build.py flags"""
|
|
82
|
+
args = [f"--bdf {self.bdf}", f"--board {self.board}"]
|
|
83
|
+
if self.advanced_sv:
|
|
84
|
+
args.append("--advanced-sv")
|
|
85
|
+
if self.device_type != "generic":
|
|
86
|
+
args.append(f"--device-type {self.device_type}")
|
|
87
|
+
if self.enable_variance:
|
|
88
|
+
args.append("--enable-variance")
|
|
89
|
+
if self.disable_power_management:
|
|
90
|
+
args.append("--disable-power-management")
|
|
91
|
+
if self.disable_error_handling:
|
|
92
|
+
args.append("--disable-error-handling")
|
|
93
|
+
if self.disable_performance_counters:
|
|
94
|
+
args.append("--disable-performance-counters")
|
|
95
|
+
if self.behavior_profile_duration != 30:
|
|
96
|
+
args.append(f"--behavior-profile-duration {self.behavior_profile_duration}")
|
|
97
|
+
|
|
98
|
+
# Add fallback control arguments
|
|
99
|
+
if self.fallback_mode != "none":
|
|
100
|
+
args.append(f"--fallback-mode {self.fallback_mode}")
|
|
101
|
+
if self.allowed_fallbacks:
|
|
102
|
+
args.append(f"--allow-fallbacks {','.join(self.allowed_fallbacks)}")
|
|
103
|
+
if self.denied_fallbacks:
|
|
104
|
+
args.append(f"--deny-fallbacks {','.join(self.denied_fallbacks)}")
|
|
105
|
+
|
|
106
|
+
return args
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
110
|
+
# Helpers
|
|
111
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def require_podman() -> None:
|
|
115
|
+
if shutil.which("podman") is None:
|
|
116
|
+
raise EnvError("Podman not found – install it or adjust PATH")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def image_exists(name: str) -> bool:
|
|
120
|
+
shell = Shell()
|
|
121
|
+
out = shell.run("podman images --format '{{.Repository}}:{{.Tag}}'", timeout=5)
|
|
122
|
+
return any(line.startswith(name) for line in out.splitlines())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def build_image(name: str, tag: str) -> None:
|
|
126
|
+
log_info_safe(logger, "Building container image {name}:{tag}", name=name, tag=tag)
|
|
127
|
+
cmd = f"podman build -t {name}:{tag} -f Containerfile ."
|
|
128
|
+
subprocess.run(cmd, shell=True, check=True)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
132
|
+
# Public façade
|
|
133
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_build(cfg: BuildConfig) -> None:
|
|
137
|
+
"""High‑level orchestration: VFIO bind → container run → cleanup"""
|
|
138
|
+
require_podman()
|
|
139
|
+
if not image_exists(f"{cfg.container_image}:{cfg.container_tag}"):
|
|
140
|
+
build_image(cfg.container_image, cfg.container_tag)
|
|
141
|
+
|
|
142
|
+
# Ensure host output dir exists and is absolute
|
|
143
|
+
output_dir = (Path.cwd() / "output").resolve()
|
|
144
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Bind without keeping the FD (call the context manager only long
|
|
148
|
+
# enough to flip the drivers)
|
|
149
|
+
binder = VFIOBinder(cfg.bdf, attach=False)
|
|
150
|
+
with binder:
|
|
151
|
+
# enter/exit immediately → binds device
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
# Get the group device path as a string (safe, just a string)
|
|
155
|
+
from .vfio_handler import _get_iommu_group
|
|
156
|
+
|
|
157
|
+
group_id = _get_iommu_group(cfg.bdf)
|
|
158
|
+
group_dev = f"/dev/vfio/{group_id}"
|
|
159
|
+
|
|
160
|
+
log_info_safe(
|
|
161
|
+
logger,
|
|
162
|
+
"Launching build container – board={board}, tag={tag}",
|
|
163
|
+
board=cfg.board,
|
|
164
|
+
tag=cfg.container_tag,
|
|
165
|
+
prefix="CONT",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
cmd_args = " ".join(cfg.cmd_args())
|
|
169
|
+
podman_cmd = textwrap.dedent(
|
|
170
|
+
f"""
|
|
171
|
+
podman run --rm --privileged \
|
|
172
|
+
--device={group_dev} \
|
|
173
|
+
--device=/dev/vfio/vfio \
|
|
174
|
+
--entrypoint python3 \
|
|
175
|
+
--user root \
|
|
176
|
+
-v {output_dir}:/app/output \
|
|
177
|
+
-v /lib/modules/$(uname -r)/build:/kernel-headers:ro \
|
|
178
|
+
{cfg.container_image}:{cfg.container_tag} \
|
|
179
|
+
src/build.py {cmd_args}
|
|
180
|
+
"""
|
|
181
|
+
).strip()
|
|
182
|
+
|
|
183
|
+
log_debug_safe(
|
|
184
|
+
logger, "Container command: {cmd}", cmd=podman_cmd, prefix="CONT"
|
|
185
|
+
)
|
|
186
|
+
start = time.time()
|
|
187
|
+
try:
|
|
188
|
+
subprocess.run(podman_cmd, shell=True, check=True)
|
|
189
|
+
except subprocess.CalledProcessError as e:
|
|
190
|
+
raise ContainerError(f"Build failed (exit {e.returncode})") from e
|
|
191
|
+
except KeyboardInterrupt:
|
|
192
|
+
log_warning_safe(
|
|
193
|
+
logger,
|
|
194
|
+
"Build interrupted by user - cleaning up...",
|
|
195
|
+
prefix="CONT",
|
|
196
|
+
)
|
|
197
|
+
# Get the container ID if possible
|
|
198
|
+
try:
|
|
199
|
+
container_id = (
|
|
200
|
+
subprocess.check_output(
|
|
201
|
+
"podman ps -q --filter ancestor=pcileech-fw-generator:latest",
|
|
202
|
+
shell=True,
|
|
203
|
+
)
|
|
204
|
+
.decode()
|
|
205
|
+
.strip()
|
|
206
|
+
)
|
|
207
|
+
if container_id:
|
|
208
|
+
log_info_safe(
|
|
209
|
+
logger,
|
|
210
|
+
"Stopping container {container_id}",
|
|
211
|
+
container_id=container_id,
|
|
212
|
+
prefix="CONT",
|
|
213
|
+
)
|
|
214
|
+
subprocess.run(
|
|
215
|
+
f"podman stop {container_id}", shell=True, check=False
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
log_warning_safe(
|
|
219
|
+
logger,
|
|
220
|
+
"Failed to clean up container: {error}",
|
|
221
|
+
error=str(e),
|
|
222
|
+
prefix="CONT",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Ensure VFIO cleanup
|
|
226
|
+
try:
|
|
227
|
+
from .vfio import restore_driver
|
|
228
|
+
|
|
229
|
+
if hasattr(cfg, "bdf") and cfg.bdf:
|
|
230
|
+
log_info_safe(
|
|
231
|
+
logger,
|
|
232
|
+
"Ensuring VFIO cleanup for device {bdf}",
|
|
233
|
+
bdf=cfg.bdf,
|
|
234
|
+
prefix="CLEA",
|
|
235
|
+
)
|
|
236
|
+
# Get original driver if possible
|
|
237
|
+
try:
|
|
238
|
+
from .vfio import get_current_driver
|
|
239
|
+
|
|
240
|
+
original_driver = get_current_driver(cfg.bdf)
|
|
241
|
+
restore_driver(cfg.bdf, original_driver)
|
|
242
|
+
except Exception:
|
|
243
|
+
# Just try to unbind from vfio-pci
|
|
244
|
+
try:
|
|
245
|
+
with open(
|
|
246
|
+
f"/sys/bus/pci/drivers/vfio-pci/unbind", "w"
|
|
247
|
+
) as f:
|
|
248
|
+
f.write(f"{cfg.bdf}\n")
|
|
249
|
+
except Exception:
|
|
250
|
+
pass
|
|
251
|
+
except Exception as e:
|
|
252
|
+
log_warning_safe(
|
|
253
|
+
logger,
|
|
254
|
+
"VFIO cleanup after interrupt failed: {error}",
|
|
255
|
+
error=str(e),
|
|
256
|
+
prefix="CLEA",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
raise KeyboardInterrupt("Build interrupted by user")
|
|
260
|
+
duration = time.time() - start
|
|
261
|
+
log_info_safe(
|
|
262
|
+
logger,
|
|
263
|
+
"Build completed in {duration:.1f}s",
|
|
264
|
+
duration=duration,
|
|
265
|
+
prefix="CONT",
|
|
266
|
+
)
|
|
267
|
+
except RuntimeError as e:
|
|
268
|
+
if "VFIO" in str(e):
|
|
269
|
+
# VFIO binding failed, diagnostics have already been run
|
|
270
|
+
log_error_safe(
|
|
271
|
+
logger,
|
|
272
|
+
"Build aborted due to VFIO issues: {error}",
|
|
273
|
+
error=str(e),
|
|
274
|
+
prefix="VFIO",
|
|
275
|
+
)
|
|
276
|
+
from .vfio_diagnostics import Diagnostics, render
|
|
277
|
+
|
|
278
|
+
# Run diagnostics one more time to ensure user sees the report
|
|
279
|
+
diag = Diagnostics(cfg.bdf)
|
|
280
|
+
report = diag.run()
|
|
281
|
+
if not report.can_proceed:
|
|
282
|
+
log_error_safe(
|
|
283
|
+
logger,
|
|
284
|
+
"VFIO diagnostics indicate system is not ready for VFIO operations",
|
|
285
|
+
prefix="VFIO",
|
|
286
|
+
)
|
|
287
|
+
log_error_safe(
|
|
288
|
+
logger,
|
|
289
|
+
"Please fix the issues reported above and try again",
|
|
290
|
+
prefix="VFIO",
|
|
291
|
+
)
|
|
292
|
+
sys.exit(1)
|
|
293
|
+
else:
|
|
294
|
+
# Re-raise other runtime errors
|
|
295
|
+
raise
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
299
|
+
# CLI entry for humans / CI
|
|
300
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
301
|
+
if __name__ == "__main__":
|
|
302
|
+
import argparse
|
|
303
|
+
|
|
304
|
+
p = argparse.ArgumentParser(description="VFIO‑aware Podman build wrapper")
|
|
305
|
+
p.add_argument("bdf", help="PCIe BDF, e.g. 0000:03:00.0")
|
|
306
|
+
p.add_argument("board", help="Target board string")
|
|
307
|
+
p.add_argument("--advanced-sv", action="store_true")
|
|
308
|
+
p.add_argument("--device-type", default="generic")
|
|
309
|
+
p.add_argument("--enable-variance", action="store_true")
|
|
310
|
+
p.add_argument("--auto-fix", action="store_true")
|
|
311
|
+
args = p.parse_args()
|
|
312
|
+
|
|
313
|
+
cfg = BuildConfig(
|
|
314
|
+
bdf=args.bdf,
|
|
315
|
+
board=args.board,
|
|
316
|
+
advanced_sv=args.advanced_sv,
|
|
317
|
+
device_type=args.device_type,
|
|
318
|
+
enable_variance=args.enable_variance,
|
|
319
|
+
auto_fix=args.auto_fix,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
run_build(cfg)
|