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.
Files changed (179) hide show
  1. cli/__init__.py +17 -0
  2. cli/build_wrapper.py +41 -0
  3. cli/cli.py +222 -0
  4. cli/config.py +66 -0
  5. cli/container.py +322 -0
  6. cli/device_prioritizer.py +91 -0
  7. cli/flash.py +116 -0
  8. cli/vfio.py +87 -0
  9. cli/vfio_constants.py +160 -0
  10. cli/vfio_diagnostics.py +889 -0
  11. cli/vfio_handler.py +852 -0
  12. cli/vfio_helpers.py +524 -0
  13. device_clone/__init__.py +95 -0
  14. device_clone/behavior_profiler.py +1634 -0
  15. device_clone/board_config.py +310 -0
  16. device_clone/config_space_manager.py +1057 -0
  17. device_clone/constants.py +92 -0
  18. device_clone/device_config.py +504 -0
  19. device_clone/fallback_manager.py +182 -0
  20. device_clone/manufacturing_variance.py +761 -0
  21. device_clone/msix_capability.py +521 -0
  22. device_clone/pcileech_context.py +1986 -0
  23. device_clone/pcileech_generator.py +1047 -0
  24. device_clone/string_utils.py +33 -0
  25. device_clone/variance_manager.py +212 -0
  26. donor_dump/Makefile +99 -0
  27. donor_dump/donor_dump.c +416 -0
  28. file_management/__init__.py +23 -0
  29. file_management/donor_dump_manager.py +1295 -0
  30. file_management/file_manager.py +810 -0
  31. file_management/option_rom_manager.py +525 -0
  32. file_management/repo_manager.py +346 -0
  33. pci_capability/__init__.py +79 -0
  34. pci_capability/_pruning.py +382 -0
  35. pci_capability/compat.py +465 -0
  36. pci_capability/constants.py +131 -0
  37. pci_capability/core.py +469 -0
  38. pci_capability/msix.py +432 -0
  39. pci_capability/patches.py +536 -0
  40. pci_capability/processor.py +595 -0
  41. pci_capability/rules.py +482 -0
  42. pci_capability/types.py +134 -0
  43. pci_capability/utils.py +261 -0
  44. pcileechfwgenerator-0.5.7.dist-info/METADATA +21 -0
  45. pcileechfwgenerator-0.5.7.dist-info/RECORD +179 -0
  46. pcileechfwgenerator-0.5.7.dist-info/WHEEL +5 -0
  47. pcileechfwgenerator-0.5.7.dist-info/entry_points.txt +6 -0
  48. pcileechfwgenerator-0.5.7.dist-info/licenses/LICENSE.txt +21 -0
  49. pcileechfwgenerator-0.5.7.dist-info/top_level.txt +10 -0
  50. scripts/__init__.py +8 -0
  51. scripts/driver_scrape.py +682 -0
  52. scripts/kernel_utils.py +312 -0
  53. scripts/state_machine_extractor.py +851 -0
  54. templates/README.md +95 -0
  55. templates/sv/advanced_controller.sv.j2 +80 -0
  56. templates/sv/bar_controller.sv.j2 +211 -0
  57. templates/sv/basic_bar_controller.sv.j2 +52 -0
  58. templates/sv/cfg_shadow.sv.j2 +255 -0
  59. templates/sv/clock_crossing.sv.j2 +50 -0
  60. templates/sv/clock_domain_logic.sv.j2 +20 -0
  61. templates/sv/device_config.sv.j2 +31 -0
  62. templates/sv/device_specific_ports.sv.j2 +25 -0
  63. templates/sv/error_counters.sv.j2 +14 -0
  64. templates/sv/error_declarations.sv.j2 +26 -0
  65. templates/sv/error_detection.sv.j2 +64 -0
  66. templates/sv/error_handling.sv.j2 +128 -0
  67. templates/sv/error_handling_complete.sv.j2 +14 -0
  68. templates/sv/error_injection.sv.j2 +24 -0
  69. templates/sv/error_logging.sv.j2 +22 -0
  70. templates/sv/error_outputs.sv.j2 +14 -0
  71. templates/sv/error_state_machine.sv.j2 +83 -0
  72. templates/sv/interrupt_logic.sv.j2 +38 -0
  73. templates/sv/main_module.sv.j2 +257 -0
  74. templates/sv/msix_capability_registers.sv.j2 +120 -0
  75. templates/sv/msix_implementation.sv.j2 +130 -0
  76. templates/sv/msix_table.sv.j2 +242 -0
  77. templates/sv/option_rom_bar_window.sv.j2 +128 -0
  78. templates/sv/option_rom_spi_flash.sv.j2 +527 -0
  79. templates/sv/pcileech_cfgspace.coe.j2 +242 -0
  80. templates/sv/pcileech_fifo.sv.j2 +297 -0
  81. templates/sv/pcileech_tlps128_bar_controller.sv.j2 +867 -0
  82. templates/sv/performance_counters.sv.j2 +333 -0
  83. templates/sv/pmcsr_stub.sv.j2 +94 -0
  84. templates/sv/power_declarations.sv.j2 +19 -0
  85. templates/sv/power_integration.sv.j2 +56 -0
  86. templates/sv/power_management.sv.j2 +95 -0
  87. templates/sv/read_logic.sv.j2 +35 -0
  88. templates/sv/register_declarations.sv.j2 +97 -0
  89. templates/sv/register_logic.sv.j2 +59 -0
  90. templates/sv/top_level_wrapper.sv.j2 +200 -0
  91. templates/tcl/bitstream.j2 +161 -0
  92. templates/tcl/common/header.j2 +25 -0
  93. templates/tcl/constraints.j2 +130 -0
  94. templates/tcl/device_setup.j2 +229 -0
  95. templates/tcl/header.j2 +25 -0
  96. templates/tcl/implementation.j2 +48 -0
  97. templates/tcl/ip_config.j2 +93 -0
  98. templates/tcl/ip_config_axi_pcie.j2 +33 -0
  99. templates/tcl/ip_config_pcie7x.j2 +57 -0
  100. templates/tcl/ip_config_ultrascale.j2 +46 -0
  101. templates/tcl/master_build.j2 +329 -0
  102. templates/tcl/pcileech_build.j2 +271 -0
  103. templates/tcl/pcileech_build_script.tcl.j2 +90 -0
  104. templates/tcl/pcileech_constraints.j2 +109 -0
  105. templates/tcl/pcileech_generate_project.j2 +230 -0
  106. templates/tcl/pcileech_implementation.j2 +102 -0
  107. templates/tcl/pcileech_project_setup.j2 +68 -0
  108. templates/tcl/pcileech_sources.j2 +118 -0
  109. templates/tcl/project_setup.j2 +41 -0
  110. templates/tcl/sources.j2 +64 -0
  111. templates/tcl/synthesis.j2 +71 -0
  112. templates/template_mapping.py +116 -0
  113. templating/__init__.py +50 -0
  114. templating/advanced_sv_error.py +123 -0
  115. templating/advanced_sv_features.py +274 -0
  116. templating/advanced_sv_generator.py +276 -0
  117. templating/advanced_sv_perf.py +423 -0
  118. templating/advanced_sv_power.py +167 -0
  119. templating/systemverilog_generator.py +912 -0
  120. templating/tcl_builder.py +876 -0
  121. templating/template_renderer.py +411 -0
  122. templating/templates/python/build_integration.py.j2 +90 -0
  123. templating/templates/python/pcileech_build_integration.py.j2 +228 -0
  124. templating/templates/systemverilog/advanced/advanced_controller.sv.j2 +80 -0
  125. templating/templates/systemverilog/advanced/clock_crossing.sv.j2 +50 -0
  126. templating/templates/systemverilog/advanced/error_counters.sv.j2 +14 -0
  127. templating/templates/systemverilog/advanced/error_declarations.sv.j2 +26 -0
  128. templating/templates/systemverilog/advanced/error_detection.sv.j2 +64 -0
  129. templating/templates/systemverilog/advanced/error_handling.sv.j2 +128 -0
  130. templating/templates/systemverilog/advanced/error_handling_complete.sv.j2 +14 -0
  131. templating/templates/systemverilog/advanced/error_injection.sv.j2 +24 -0
  132. templating/templates/systemverilog/advanced/error_logging.sv.j2 +22 -0
  133. templating/templates/systemverilog/advanced/error_outputs.sv.j2 +14 -0
  134. templating/templates/systemverilog/advanced/error_state_machine.sv.j2 +83 -0
  135. templating/templates/systemverilog/advanced/main_module.sv.j2 +257 -0
  136. templating/templates/systemverilog/advanced/performance_counters.sv.j2 +333 -0
  137. templating/templates/systemverilog/advanced/power_management.sv.j2 +95 -0
  138. templating/templates/systemverilog/bar_controller.sv.j2 +211 -0
  139. templating/templates/systemverilog/basic_bar_controller.sv.j2 +52 -0
  140. templating/templates/systemverilog/cfg_shadow.sv.j2 +255 -0
  141. templating/templates/systemverilog/components/clock_domain_logic.sv.j2 +20 -0
  142. templating/templates/systemverilog/components/device_specific_ports.sv.j2 +25 -0
  143. templating/templates/systemverilog/components/interrupt_logic.sv.j2 +38 -0
  144. templating/templates/systemverilog/components/power_declarations.sv.j2 +19 -0
  145. templating/templates/systemverilog/components/power_integration.sv.j2 +56 -0
  146. templating/templates/systemverilog/components/read_logic.sv.j2 +35 -0
  147. templating/templates/systemverilog/components/register_declarations.sv.j2 +97 -0
  148. templating/templates/systemverilog/components/register_logic.sv.j2 +59 -0
  149. templating/templates/systemverilog/device_config.sv.j2 +31 -0
  150. templating/templates/systemverilog/modules/pmcsr_stub.sv.j2 +94 -0
  151. templating/templates/systemverilog/msix_capability_registers.sv.j2 +120 -0
  152. templating/templates/systemverilog/msix_implementation.sv.j2 +130 -0
  153. templating/templates/systemverilog/msix_table.sv.j2 +242 -0
  154. templating/templates/systemverilog/option_rom_bar_window.sv.j2 +128 -0
  155. templating/templates/systemverilog/option_rom_spi_flash.sv.j2 +527 -0
  156. templating/templates/systemverilog/pcileech_cfgspace.coe.j2 +242 -0
  157. templating/templates/systemverilog/pcileech_fifo.sv.j2 +297 -0
  158. templating/templates/systemverilog/pcileech_tlps128_bar_controller.sv.j2 +867 -0
  159. templating/templates/systemverilog/top_level_wrapper.sv.j2 +200 -0
  160. templating/templates/tcl/pcileech_build.j2 +271 -0
  161. templating/templates/tcl/pcileech_build_script.tcl.j2 +90 -0
  162. templating/templates/tcl/pcileech_generate_project.j2 +230 -0
  163. tui/__init__.py +13 -0
  164. tui/core/__init__.py +13 -0
  165. tui/core/build_orchestrator.py +1031 -0
  166. tui/core/config_manager.py +616 -0
  167. tui/core/device_manager.py +574 -0
  168. tui/core/status_monitor.py +404 -0
  169. tui/main.py +1233 -0
  170. tui/models/__init__.py +18 -0
  171. tui/models/config.py +268 -0
  172. tui/models/device.py +136 -0
  173. tui/models/error.py +190 -0
  174. tui/models/progress.py +126 -0
  175. tui/styles/main.tcss +290 -0
  176. vivado_handling/__init__.py +28 -0
  177. vivado_handling/vivado_build_with_errors.py +132 -0
  178. vivado_handling/vivado_error_reporter.py +916 -0
  179. 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)