diff --git a/.claude/agents/hardware-safety-reviewer.md b/.claude/agents/hardware-safety-reviewer.md new file mode 100644 index 00000000..982c54d3 --- /dev/null +++ b/.claude/agents/hardware-safety-reviewer.md @@ -0,0 +1,122 @@ +--- +name: hardware-safety-reviewer +description: Reviews changes for PCIe-spec compliance, donor-ID propagation correctness, DMA/BAR safety, and SystemVerilog template correctness. Use proactively after any change to src/device_clone/, src/pci_capability/, src/templates/sv/, src/templating/timing_constraints/, or src/vivado_handling/. This agent is NOT a substitute for generic code review (`feature-dev:code-reviewer` handles that); it complements it with hardware-domain checks the generic reviewer cannot perform. +tools: Read, Grep, Glob, Bash +--- + +# Hardware Safety Reviewer + +You are a domain-specific code reviewer for PCILeechFWGenerator. Your job is to catch a narrow class of bugs that generic code review will miss: violations of PCIe spec, donor-profile invariants, DMA safety, and SystemVerilog template correctness. + +You are **not** a stylistic reviewer. You do **not** comment on naming, formatting, docstring presence, or test coverage. The pre-commit suite and `feature-dev:code-reviewer` cover those. Your output should be empty if no hardware-domain issues exist. + +## Scope of files you care about + +Always relevant: + +- `src/device_clone/` — donor profile, BAR parsing, config space management. +- `src/pci_capability/` — capability chain construction. +- `src/templates/sv/` — Jinja2 → SystemVerilog templates. +- `src/templating/timing_constraints/` — XDC generation. +- `src/vivado_handling/` — TCL invocation, error reporting. +- `src/file_management/board_discovery.py` — FPGA part / lane count assertions. +- `configs/fallbacks.yaml` — fallback donor values. + +Out of scope (defer to other reviewers): + +- `src/tui/`, `src/cli/`, `src/host_collect/`, top-level orchestration. +- Anything under `tests/`. +- Documentation, build infra, CI. + +## Concrete checks to run + +For each changed file in scope, look for: + +### 1. Donor-ID propagation + +- Has the change introduced a hard-coded VID/PID, subsystem VID, revision, or + class code anywhere other than `configs/fallbacks.yaml` or explicit test + fixtures? Hard-coded donor IDs in generation code is a recent regression + source (see commit `26617ee` — "donor IDs in FIFO + IP CONFIG"). +- If a new module reads donor identity, does it use the same source-of-truth + accessor as the rest of the codebase (search for `DeviceConfig` / + `device_info_lookup` / similar) rather than a parallel path? +- Are placeholder values like `0xDEAD`, `0xBEEF`, `0x1234`, `0xFFFF` present + in generated output paths? Placeholders in templates are an explicit + anti-pattern in this project (see README's warning). + +### 2. PCI capability chain + +- If the capability list is built or modified, is the **next-pointer** of the + last capability set to `0x00`? Forgetting to terminate is a classic source + of OS-side enumeration hangs. +- Is the order preserved across edits? Some OSes are sensitive to capability + ordering (MSI/MSI-X before PCIe Express cap, etc.). +- Are capability sizes correct? Wrong sizes shift the next-pointer offsets and + silently break enumeration in non-obvious ways. + +### 3. BAR / DMA safety + +- BAR size: always a power of two? Always ≥ 128 bytes (PCIe min)? Aperture + size encoded as expected (`~(size - 1)` mask)? +- 64-bit BARs: is the high half properly set/cleared in pair? +- Prefetchable bit set/cleared consistent with memory type? +- Any unbounded `size_t` / unbounded loop driven by donor-controlled value? + (Donor values are *inputs*, treat them as untrusted for the purpose of + generation-time safety even if the donor is the user's own device.) + +### 4. SystemVerilog template correctness (`*.j2` files) + +- Jinja2 expressions inside SV string literals need to **not** introduce + unescaped quotes or backslashes that break SV lexing. +- Signal widths: any `assign foo = bar;` where `foo` and `bar` differ in + width without an explicit slice or extension? Vivado warns but synthesizes; + the resulting RTL is often wrong. +- `case` statements: is `default:` present? Missing defaults synthesize + latches. +- Generate blocks: is the `genvar` declared at the outer scope? +- Macros: any new `\`define` in a header without an `\`ifndef` guard? + +### 5. Constraints (XDC) + +- New constraints: do they reference signal names that actually exist in the + generated RTL (grep the templates)? +- Are clock period constraints tighter than the donor's actual clock? Doing + so makes timing fail for no real reason. + +### 6. Vivado handling + +- Subprocess invocations: arg-list (`["vivado", ...]`) form, not + `shell=True`. The codebase recently hardened this (commits `8bf9464`, + `5770fba`) — regressions here matter. +- Working directory: are TCL scripts invoked from a deterministic CWD? Vivado + emits artifacts relative to CWD. + +## How to deliver your review + +1. Read the diff using `git diff` against the appropriate base. +2. For each in-scope file, run the relevant checks above. +3. Report findings as a **prioritized list**, highest-severity first. + Each finding must include: + - File and line range. + - Which check it violates. + - The specific risk (what breaks on which OS / under what condition). + - A concrete suggested fix, or "needs investigation" if not obvious. +4. If you have no findings, say exactly: **"No hardware-domain issues found in + the changed scope."** Do not pad with style commentary. + +## What you must not do + +- Do not run any build / synthesis — you have read-only tools by design. +- Do not modify files. +- Do not duplicate findings that the generic reviewer would also catch + (style, naming, missing tests). Stay in your lane. +- Do not invent invariants that aren't in the spec or in the codebase's + existing patterns. + +## Useful references + +- PCIe Base Spec rev 5.0, sections 7 (config space) and 9 (capabilities). +- `src/pci_capability/` for the project's working capability builder. +- `src/templates/sv/` for the SV style this project actually emits. +- Recent relevant commits: `26617ee`, `5770fba`, `8bf9464`. diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000..304d6d03 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-settings.json", + "_comment": "Shared Claude Code settings for PCILeechFWGenerator. User-local overrides go in settings.local.json (gitignored).", + "hooks": { + "PostToolUse": [ { + "hooks": [ { + "command": "path=$(jq -r '.tool_input.file_path // empty'); case \"$path\" in *.py) command -v ruff >/dev/null 2>&1 && ruff check --fix --quiet \"$path\" 2>&1 | head -40 || true ;; esac", + "type": "command" + } ], + "matcher": "Edit|Write|MultiEdit" + } ], + "PreToolUse": [ { + "hooks": [ { + "command": "path=$(jq -r '.tool_input.file_path // empty'); echo \"$path\" | grep -qE '(^|/)src/_version\\.py$|(^|/)lib/voltcyclone-fpga/' && { echo \"Blocked: $path is either autogenerated by setuptools-scm (src/_version.py) or inside the voltcyclone-fpga git submodule (lib/voltcyclone-fpga/). Hand edits will be overwritten or land in the wrong repository. Make the change upstream or via the build system.\" >&2; exit 2; } || exit 0", + "type": "command" + } ], + "matcher": "Edit|Write|MultiEdit" + } ] + } +} \ No newline at end of file diff --git a/.claude/skills/new-board-target/SKILL.md b/.claude/skills/new-board-target/SKILL.md new file mode 100644 index 00000000..7acaee82 --- /dev/null +++ b/.claude/skills/new-board-target/SKILL.md @@ -0,0 +1,120 @@ +--- +name: new-board-target +description: Use when the user wants to add support for a new FPGA board / dev kit target to PCILeechFWGenerator (e.g. "add support for board X", "register new board", "I want to target FPGA part Y"). Walks through the exact file touches needed, validation steps, and pitfalls specific to this codebase. Do NOT use for fixing build failures on an already-supported board — that's the vivado-log-analyzer skill. +disable-model-invocation: true +--- + +# Add a New Board Target + +This is the canonical workflow for adding a new FPGA board to PCILeechFWGenerator. Follow it top-to-bottom — skipping steps almost always causes confusing synthesis failures down the line. + +## 0. Confirm the upstream board exists + +Boards are auto-discovered from the `voltcyclone-fpga` submodule +(`lib/voltcyclone-fpga/`). If the board directory doesn't exist there, you +must add it **upstream first**: + +```bash +ls lib/voltcyclone-fpga/ +# look for a directory matching the board (e.g. EnigmaX1, PCIeSquirrel, ZDMA) +``` + +If absent, open a PR against `VoltCyclone/voltcyclone-fpga` to add the board. +Do not hand-edit files under `lib/voltcyclone-fpga/` in this repo — that +directory is a git submodule and edits will not persist. (The PreToolUse hook +in `.claude/settings.json` will block such edits.) + +## 1. Register the board in `BOARD_CONFIGS` + +File: `src/file_management/board_discovery.py` + +Add an entry under `BoardDiscovery.BOARD_CONFIGS` matching the upstream +directory name and FPGA part. Use existing entries (e.g. `pcileech_enigma_x1`, +`pcileech_75t484_x1`) as the template. + +Key fields: +- `dir`: must match the directory name under `lib/voltcyclone-fpga/`. +- `fpga_part`: full Xilinx part (e.g. `xc7a75tfgg484-2`). Match the casing and + speed-grade suffix used elsewhere — wrong speed grades pass synthesis but + fail timing in mysterious ways. +- `max_lanes`: PCIe lane count the board exposes (1, 4, 8, 16). + +If the board has a colloquial name (e.g. `75t`, `100t`), add a **legacy alias** +entry that points to the same directory — this keeps existing CLI invocations +working. + +## 2. Verify discovery picks it up + +```bash +.venv/bin/python -c "from pcileechfwgenerator.file_management.board_discovery import discover_all_boards; \ + import json; print(json.dumps(list(discover_all_boards().keys()), indent=2))" +``` + +The new board name should appear. If it doesn't, the discovery walker isn't +finding it — usually because `dir` doesn't match the upstream directory +exactly (case-sensitive). + +## 3. Check for board-specific RTL / constraints + +```bash +ls src/templates/sv/ | grep -i # e.g. 7series, ultrascale +ls src/templating/timing_constraints/ # board-specific .xdc bits +``` + +If the board uses a new FPGA family (e.g. first UltraScale+ target), you may +need to extend `FPGA_FAMILY_PATTERNS` in `src/device_clone/board_config.py`. +Existing patterns: `7series` (xc7a/k/v/z), `ultrascale`, `ultrascale_plus`. + +## 4. Add tests + +There are two relevant test files: + +- `tests/test_board_discovery.py` — assert the new board is enumerated and has + the expected FPGA part. +- `tests/test_board_config.py` — assert `get_fpga_part('')` returns + the right string and `KeyError` for unknown boards. + +Add the new board to the parametrized cases. Run: + +```bash +.venv/bin/python -m pytest tests/test_board_discovery.py tests/test_board_config.py -x -q +``` + +## 5. End-to-end smoke test + +Generate firmware for the new board against a known donor profile: + +```bash +.venv/bin/python -m pcileechfwgenerator build \ + --board \ + --donor configs/donors/.json \ + --dry-run +``` + +`--dry-run` produces the generation outputs without invoking Vivado. If this +succeeds, you have at minimum a valid template materialization for the board. + +## 6. Update docs + +- Add the board to the README "Supported boards" section (search README for + an existing board name to find the table). +- Add a CHANGELOG entry under "Added". +- Do NOT add the board to `AUTHORS` or any other contributor-style file. + +## Common pitfalls + +| Symptom | Likely cause | +|---|---| +| Board not found at runtime | `dir` in `BOARD_CONFIGS` doesn't match upstream directory name (case-sensitive). | +| Synthesis fails with "part not supported" | `fpga_part` typo or wrong speed grade. | +| Timing fails immediately on impl | New FPGA family without a matching entry in `FPGA_FAMILY_PATTERNS`. | +| Donor ID propagation tests fail | New board needs a default in donor-profile fallback (`configs/fallbacks.yaml`). | +| Build worked locally but fails in CI | Submodule pin in `.gitmodules` is older than your local checkout — bump the submodule SHA. | + +## When NOT to use this skill + +- Fixing an existing board that suddenly broke → use `vivado-log-analyzer`. +- Just changing FPGA part of an existing board → edit `BOARD_CONFIGS` directly, + no need for the whole flow. +- Renaming a board → that's a breaking change; coordinate with a deprecation + period, don't follow this skill. diff --git a/.claude/skills/vivado-log-analyzer/SKILL.md b/.claude/skills/vivado-log-analyzer/SKILL.md new file mode 100644 index 00000000..04242c16 --- /dev/null +++ b/.claude/skills/vivado-log-analyzer/SKILL.md @@ -0,0 +1,66 @@ +--- +name: vivado-log-analyzer +description: Use when a Vivado synthesis or implementation run fails, or when the user asks "why did the build fail / what's wrong with this vivado log". Surfaces only the actionable error/critical-warning lines from large Vivado outputs (vivado.log, synth_1/runme.log, impl_1/runme.log, *.rpt) so the diagnosis fits in context. Trigger when the user shares a Vivado log path, mentions "synthesis failed", "implementation failed", "timing failure", "BRAM exhausted", or pastes raw Vivado output. +--- + +# Vivado Log Analyzer + +## When to invoke + +- A Vivado build (synth / impl / bitstream) failed and the user wants the cause. +- The user pastes or points at one of: `vivado.log`, `*.runs/synth_1/runme.log`, + `*.runs/impl_1/runme.log`, `*_timing_summary.rpt`, + `*_utilization_*.rpt`, `*_drc_*.rpt`. +- Auto-trigger when a tool result contains `ERROR: [Synth`, `CRITICAL WARNING: [Place`, + `Timing constraint not met`, or similar Vivado markers. + +## What it does + +Vivado logs are usually 5–50 MB and 95% noise. This skill runs +`scripts/analyze.py ` which extracts only: + +- `ERROR:` and `CRITICAL WARNING:` lines (Vivado's "this is why it failed"). +- Timing summary endpoints with negative slack. +- Resource utilization rows above 95% (BRAM, LUT, DSP, URAM exhaustion). +- DRC violations (UCIO-1, LUTLP-1, NSTD-1, etc.). +- IP licensing failures (`ERROR: [Common 17-69]`). +- Missing constraint file errors. + +It groups by failure category and prints **at most ~80 lines** so you can hand +the result to the user or reason about it without burning context. + +## How to use + +```bash +python3 .claude/skills/vivado-log-analyzer/scripts/analyze.py +``` + +`` can be any of: + +- A single `.log` or `.rpt` file +- A Vivado project's `*.runs/` directory (script will walk it) +- A build output directory (`output/build/...`) + +If the user hasn't given a path, look first in `output/`, then in +`build/`, then in any `*.runs/synth_1/` or `*.runs/impl_1/` directory. + +## After analysis + +1. Print the categorized summary the script produced. +2. **Do not** re-read the raw log unless the summary is empty or the user asks + for a specific line context. Re-reading defeats the purpose of the skill. +3. If timing is the failure category, point the user at + `src/templating/timing_constraints/` and any board-specific `.xdc` files. +4. If resource exhaustion is the failure category, the fix is almost always in + `src/templates/sv/` (template bloat) or in board selection + (`src/file_management/board_discovery.py` — wrong FPGA part). +5. If IP licensing or "missing IP" errors appear, check the Containerfile and + confirm the build is using a licensed Vivado image. + +## Anti-patterns + +- Do not paste the raw Vivado log back to the user. They already have it. +- Do not run this on logs smaller than ~500 lines — just read them directly. +- Do not try to "fix" timing failures by widening constraints without + understanding the constraint origin (`src/templating/timing_constraints/` + generates them; constraints are tied to donor profile). diff --git a/.claude/skills/vivado-log-analyzer/scripts/analyze.py b/.claude/skills/vivado-log-analyzer/scripts/analyze.py new file mode 100755 index 00000000..95c18430 --- /dev/null +++ b/.claude/skills/vivado-log-analyzer/scripts/analyze.py @@ -0,0 +1,174 @@ +#!/usr/bin/env python3 +""" +Vivado log analyzer for PCILeechFWGenerator. + +Extracts only the actionable lines from Vivado synthesis/implementation logs +and reports. Designed to keep failure diagnoses small enough to fit in an LLM +context window. + +Usage: + analyze.py + + can be: + - a single .log or .rpt file + - a Vivado .runs/ directory (will scan synth_1 / impl_1) + - any directory (will glob for *.log and *_summary.rpt below it) +""" + +from __future__ import annotations + +import re +import sys +from collections import defaultdict +from pathlib import Path +from typing import Iterable + +MAX_LINES_PER_CATEGORY = 20 +MAX_TOTAL_LINES = 80 + +ERROR_RE = re.compile(r"^(ERROR|CRITICAL WARNING):\s*\[([^\]]+)\]\s*(.*)$") +TIMING_FAIL_RE = re.compile(r"^\s*(WNS|WHS|WPWS|TNS|THS|TPWS)\s*\(ns\):\s*(-\d[\d.]*)") +UTIL_HIGH_RE = re.compile( + r"^\|\s*(LUT|FF|BRAM Tile|RAMB36|RAMB18|DSP|URAM|IO)\s*\|.*\|\s*([0-9]{2,3}\.\d+)\s*\|" +) +DRC_RE = re.compile(r"^(?:CRITICAL WARNING|ERROR):\s*\[DRC\s+([A-Z0-9-]+)\]\s*(.*)$") +LICENSE_RE = re.compile(r"\b(Common 17-69|licensing|no valid license)\b", re.I) +MISSING_FILE_RE = re.compile(r"Cannot find|does not exist|no such file", re.I) + + +def iter_lines(path: Path) -> Iterable[str]: + try: + with path.open("r", encoding="utf-8", errors="replace") as fh: + for line in fh: + yield line.rstrip("\n") + except OSError as exc: # noqa: PERF203 — tiny outer loop + print(f"# could not read {path}: {exc}", file=sys.stderr) + + +def collect_logs(root: Path) -> list[Path]: + if root.is_file(): + return [root] + patterns = [ + "vivado.log", + "**/runme.log", + "**/vivado.log", + "**/*_timing_summary*.rpt", + "**/*_utilization*.rpt", + "**/*_drc*.rpt", + ] + found: set[Path] = set() + for pat in patterns: + for hit in root.glob(pat): + if hit.is_file() and hit.stat().st_size > 0: + found.add(hit) + return sorted(found) + + +def categorize(line: str) -> tuple[str, str] | None: + """Return (category, normalized_line) or None to skip.""" + m = ERROR_RE.match(line) + if m: + severity, code, msg = m.groups() + # IP licensing → its own bucket so the fix is obvious + if LICENSE_RE.search(line): + return "ip-licensing", f"{severity} [{code}] {msg}" + if code.startswith("Synth"): + return "synthesis", f"{severity} [{code}] {msg}" + if code.startswith("Place") or code.startswith("Route"): + return "place-route", f"{severity} [{code}] {msg}" + if code.startswith("Timing"): + return "timing", f"{severity} [{code}] {msg}" + if code.startswith("DRC"): + return "drc", f"{severity} [{code}] {msg}" + if MISSING_FILE_RE.search(msg): + return "missing-files", f"{severity} [{code}] {msg}" + return "other-errors", f"{severity} [{code}] {msg}" + + m = TIMING_FAIL_RE.match(line) + if m: + metric, ns = m.groups() + return "timing", f"{metric} = {ns} ns (negative slack)" + + m = UTIL_HIGH_RE.match(line) + if m: + resource, pct = m.groups() + if float(pct) >= 95.0: + return "resource-exhaustion", f"{resource}: {pct}% utilization" + + return None + + +def main(argv: list[str]) -> int: + if len(argv) != 2: + print(__doc__.strip(), file=sys.stderr) + return 2 + + root = Path(argv[1]).expanduser().resolve() + if not root.exists(): + print(f"path does not exist: {root}", file=sys.stderr) + return 2 + + logs = collect_logs(root) + if not logs: + print(f"no Vivado logs or reports found under {root}", file=sys.stderr) + return 1 + + buckets: dict[str, list[str]] = defaultdict(list) + sources: dict[str, set[str]] = defaultdict(set) + + for log in logs: + for line in iter_lines(log): + hit = categorize(line) + if hit is None: + continue + category, normalized = hit + if normalized in buckets[category]: + continue + buckets[category].append(normalized) + sources[category].add(log.name) + + if not buckets: + print("# No Vivado ERROR / CRITICAL WARNING lines found.") + print(f"# Scanned {len(logs)} file(s) under {root}.") + print("# If the build did fail, check the run directory exit status or") + print("# the bitstream/DRC report directly.") + return 0 + + order = [ + "ip-licensing", + "missing-files", + "synthesis", + "place-route", + "timing", + "drc", + "resource-exhaustion", + "other-errors", + ] + + total_emitted = 0 + out: list[str] = [f"# Vivado log summary — {len(logs)} file(s) scanned\n"] + for cat in order: + items = buckets.get(cat) + if not items: + continue + src_list = ", ".join(sorted(sources[cat])) + out.append(f"## {cat} ({len(items)} unique, from: {src_list})") + for line in items[:MAX_LINES_PER_CATEGORY]: + out.append(f" - {line}") + total_emitted += 1 + if total_emitted >= MAX_TOTAL_LINES: + out.append(" - ... (truncated; rerun the script on a narrower path)") + print("\n".join(out)) + return 0 + if len(items) > MAX_LINES_PER_CATEGORY: + out.append( + f" - ... {len(items) - MAX_LINES_PER_CATEGORY} more in this category" + ) + out.append("") + + print("\n".join(out)) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..31762e75 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,32 @@ +# CODEOWNERS for PCILeechFWGenerator +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-security/customizing-your-repository/about-code-owners +# +# Owners listed here are automatically requested for review on PRs that touch +# matching paths. The last matching pattern wins, so more specific rules go +# below the catch-all. + +# Default owner for everything not matched below. +* @ramseymcgrath + +# Build, release, and CI plumbing. +/.github/ @ramseymcgrath +/.github/workflows/ @ramseymcgrath +/Containerfile @ramseymcgrath +/Makefile @ramseymcgrath +/cliff.toml @ramseymcgrath +/entrypoint.sh @ramseymcgrath +/install-sudo-wrapper.sh @ramseymcgrath +/build_vfio_constants.sh @ramseymcgrath +/force_vfio_binds.sh @ramseymcgrath + +# Security-sensitive surfaces: VFIO, kernel interaction, donor handling, +# firmware generation. Reviews here should be careful. +/src/ @ramseymcgrath + +# Docs and policy. +/README.md @ramseymcgrath +/SECURITY.md @ramseymcgrath +/CONTRIBUTING.rst @ramseymcgrath +/CODE_OF_CONDUCT.md @ramseymcgrath +/CHANGELOG.md @ramseymcgrath +/docs/ @ramseymcgrath diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..d57b0fd9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 🔐 Security vulnerability + url: https://github.com/VoltCyclone/PCILeechFWGenerator/security/advisories/new + about: Please report security issues privately via GitHub Security Advisories, not public issues. + - name: 📚 Documentation + url: https://github.com/VoltCyclone/PCILeechFWGenerator#readme + about: Check the README and docs/ before filing — your question may already be answered. + - name: 💬 Question, hardware compatibility, or open-ended idea + url: https://discord.gg/dwQfMNsb7W + about: For "does this work with…" / "how do I…" / brainstorming, please ask in the support Discord. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..60653f34 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,50 @@ +--- +name: ✨ Feature Request +about: Suggest a new capability or improvement for PCILeechFWGenerator +title: '[FEATURE] ' +labels: ['enhancement', 'needs-triage'] +assignees: '' +--- + + + +## Problem / motivation + + + +## Proposed solution + + + +## Alternatives considered + + + +## Scope + +- [ ] Affects firmware generation / RTL output +- [ ] Affects the TUI +- [ ] Affects the build pipeline (Vivado / container / release) +- [ ] Affects donor / device profiling +- [ ] Documentation only +- [ ] Other (describe below) + +## Are you willing to contribute? + +- [ ] I can submit a PR +- [ ] I can help test +- [ ] I'm just filing the idea + +## Additional context + + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..0b3d48d4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,51 @@ + + +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor / cleanup (no behavior change) +- [ ] Documentation +- [ ] Build / CI / tooling +- [ ] Security fix + +## Linked issues + + + +## How was this tested? + + + +## Risk / blast radius + +- [ ] Touches firmware generation / RTL output +- [ ] Touches VFIO binding or kernel-driver interaction +- [ ] Touches the build pipeline (Vivado, container, release) +- [ ] None of the above — surface change only + + + +## Checklist + +- [ ] Tests added or updated where it made sense +- [ ] Docs / CHANGELOG updated if user-visible behavior changed +- [ ] No sensitive data (UUIDs, real device serials, donor dumps) in the diff +- [ ] CI is green (or I've explained the failures below) diff --git a/.gitignore b/.gitignore index e3ab3efb..49b36b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,8 @@ site/venv/ # setuptools-scm-generated version file src/_version.py + +# Claude Code: keep shared config (settings.json, skills/, agents/) tracked, +# ignore user-local overrides and transient state. +.claude/settings.local.json +.claude/.cache/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 00000000..65584dfb --- /dev/null +++ b/.mcp.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-mcp.json", + "_comment": "Shared MCP servers for PCILeechFWGenerator. Contributors using Claude Code pick these up automatically. The GitHub server needs GITHUB_PERSONAL_ACCESS_TOKEN set in your shell (a fine-grained PAT with repo + pull-request + workflow scopes is sufficient).", + "mcpServers": { + "github": { + "args": [ "-y", "@modelcontextprotocol/server-github" ], + "command": "npx", + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}" + } + } + } +} \ No newline at end of file diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index ff38ae6e..00000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,5 +0,0 @@ -============ -Contributors -============ - -* Ramsey McGrath diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..98d9a36e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,192 @@ +# CLAUDE.md + +Guidance for Claude (and other AI assistants) working in the PCILeechFWGenerator +repository. Loaded automatically into every Claude Code session at this root. + +Audience for the assistant: **installers/users** asking for help getting the +tool running, and **developers** modifying the codebase. Sections below cover +both. The user-facing README and the hosted docs are the human source of +truth; this file tells the assistant where to point them. + +--- + +## What this project is + +PCILeechFWGenerator generates **authentic PCIe DMA firmware bitstreams** by +cloning a real donor device's PCIe configuration. The output is a Xilinx +FPGA bitstream consumable by PCILeech-family boards (Squirrel, Enigma, etc.). + +Three-stage pipeline: + +1. **Host collect** — read donor device config (VID/PID, BARs, capability + chain, MSI-X) from a Linux host via VFIO. Requires root. +2. **Templating** — render Jinja2 → SystemVerilog/TCL using collected data. + Optional Podman container for isolation. +3. **Vivado build** — synthesize the generated RTL into a bitstream. Requires + a working Xilinx Vivado install (free WebPACK is sufficient for 7-series + parts; UltraScale parts need a paid license). + +Real donor hardware is **required**. There is no synthetic-donor mode and +placeholder values in templates are an explicit anti-pattern (the README +calls this out — see the warning admonition). + +--- + +## Helping users install or run the tool + +Default support flow when someone asks "how do I install this" or "it won't +build": + +1. Confirm platform: **Ubuntu 22.04+ / Debian 12+**, Python ≥ 3.11. The tool + does not work on macOS or Windows hosts (VFIO is Linux-only). WSL2 does + not expose VFIO and is not supported. +2. Confirm install path: + - **pip (recommended for users):** `pip install pcileechfwgenerator[tui]` + in a venv (`python3 -m venv ~/.pcileech-venv`). Do NOT install into the + system Python — modern distros refuse with `externally-managed-environment`. + - **container:** Podman + the bundled `Containerfile`. Container only runs + Stage 2; Stages 1 and 3 still happen on the host. + - **dev from repo:** `git clone --recurse-submodules`, then + `pip install -e .[dev]` in a venv. Forgetting `--recurse-submodules` + is the single most common "boards not found" cause. +3. Confirm root-access pattern: all commands that touch VFIO must run as root + via `sudo -E ~/.pcileech-venv/bin/python3 -m pcileechfwgenerator ...` or + via the `pcileech-sudo` alias the README sets up. Running `sudo pcileech` + without preserving the venv path will hit the wrong Python. +4. Confirm Vivado is installed for Stage 3. If the user only wants to inspect + generated RTL, they can stop after Stage 2 with `--dry-run` / templating + flags and skip Vivado. + +### Common installation/runtime symptoms + +| Symptom | Cause | Fix | +|---|---|---| +| `externally-managed-environment` | pip into system Python | Use a venv. | +| `ModuleNotFoundError: textual` | installed without `[tui]` extra | `pip install pcileechfwgenerator[tui]` | +| "no boards available" / `KeyError` on board | git submodule not initialized | `git submodule update --init --recursive` | +| Permission denied on VFIO | not root, or vfio-pci not loaded | `sudo modprobe vfio vfio-pci`; run via `pcileech-sudo` | +| "boards not found" in pip install | sdist bundles boards — confirm `pip show pcileechfwgenerator` finds `lib/voltcyclone-fpga` contents | reinstall, check `MANIFEST.in` | +| Vivado errors (any) | invoke the `vivado-log-analyzer` skill rather than reading the log directly | | + +When troubleshooting goes deep, point users at: + +- **Docs site:** +- **Discord support:** +- **Bug template:** + +Security issues go through GitHub Security Advisories, never public issues. +See `SECURITY.md`. + +--- + +## Helping developers modify the codebase + +### Environment + +```bash +python3 -m venv .venv +.venv/bin/pip install -e ".[dev]" +.venv/bin/pre-commit install +``` + +`[dev]` pulls testing + TUI + lint/format/type/security tooling +(black, isort, flake8, mypy, bandit, pre-commit, build, twine). ruff is +present via its cache directory and runs through the PostToolUse hook on +edited files. + +### Running tests + +```bash +.venv/bin/python -m pytest -x -q # full suite (~2,400 tests) +.venv/bin/python -m pytest tests/test_foo.py # one file +.venv/bin/python -m pytest -k "board" -x # subset +``` + +`pytest-xdist` is available — use `-n auto` for parallel runs locally. +CI runs the full suite plus bandit + safety. + +### Architectural map + +| Path | Purpose | +|---|---| +| `src/cli/` | CLI entry points (subcommand dispatch). | +| `src/tui/` | Textual-based TUI. Newer dialogs subclass `BaseDialog`. | +| `src/host_collect/` | Stage 1: VFIO-driven donor extraction. | +| `src/device_clone/` | Donor profile parsing, BAR sizing, capability chain. | +| `src/pci_capability/` | PCIe capability list construction. | +| `src/templating/` | Stage 2: Jinja2 + helpers. | +| `src/templates/` | Jinja2 source: `sv/*.j2` (SystemVerilog), TCL, XDC. | +| `src/vivado_handling/` | Stage 3: Vivado subprocess + error reporting. | +| `src/file_management/board_discovery.py` | Board registry. Auto-discovers from `lib/voltcyclone-fpga/`. | +| `lib/voltcyclone-fpga/` | **Git submodule.** Board definitions / IP / constraints. | +| `configs/fallbacks.yaml` | Last-resort defaults for donor fields. | +| `tests/` | pytest suite (~158 files). | + +### Files you must NOT hand-edit + +These are protected by a `PreToolUse` hook in `.claude/settings.json`: + +- `src/_version.py` — autogenerated by `setuptools-scm` at build time. + Change the version by tagging a git release, not by editing this file. +- `lib/voltcyclone-fpga/` — git submodule. Changes there must land in the + upstream `VoltCyclone/voltcyclone-fpga` repo first, then the submodule + pointer is bumped here. + +### Code conventions to respect + +- **No placeholder donor IDs in generated paths.** `0xDEAD`, `0xBEEF`, + `0x1234`, `0xFFFF` in `src/templates/` or template-rendering code is + almost always a regression. Donor identity must propagate from the real + collected profile. +- **No `shell=True` in subprocess invocations.** Argv-list form only — + hardening pass `8bf9464` removed the last instances. Bandit will flag + regressions. +- **Use the `log_*_safe` helpers** from `src/string_utils` for any log line + that interpolates donor data. Plain f-strings can leak identifiers into + shared logs. +- **Logging:** module-level `logger = get_logger(__name__)` from + `src/log_config`. Don't reach for stdlib `logging.basicConfig`. +- **Jinja2 SystemVerilog templates:** mind quote/backslash escaping inside + SV string literals; always end `case` blocks with a `default:`; declare + `genvar` outside generate blocks. +- **Pydantic models** for any structured donor data — search for existing + models in `src/device_clone/` before introducing a new dict-shaped value. + +### Tests, then commits, then PRs + +The user's preference (also encoded in feedback memory): **no Co-Authored-By +trailer in commits**. Don't add the Claude attribution line. + +Commit style follows the existing log — short imperative summary, optional +fixup body. `git-cliff` builds the changelog from these, so prefixes like +`fix:`, `feat:`, `chore:` are useful but not strictly required. + +### Project-specific Claude tooling + +These exist in this repo — invoke them when applicable rather than +reimplementing: + +- **`.claude/skills/vivado-log-analyzer/`** — invoke whenever you see a + failed Vivado run, instead of reading the log directly. Outputs ≤ 80 + lines categorized by failure type. +- **`.claude/skills/new-board-target/`** — user-only skill walking through + adding an FPGA board. Don't auto-invoke; suggest it when the user asks to + add a board. +- **`.claude/agents/hardware-safety-reviewer.md`** — invoke proactively on + any change to `src/device_clone/`, `src/pci_capability/`, + `src/templates/sv/`, or `src/vivado_handling/`. It complements the + generic code reviewer with PCIe-domain checks. +- **GitHub MCP server** (configured in `.mcp.json`) — prefer it over + shelling out to `gh` for PR / issue / Actions work. + +--- + +## Out of scope for this assistant + +- Don't help users install the tool on macOS / Windows / WSL2 — it doesn't + work there. Redirect to a Linux VM or a real Linux host. +- Don't generate "example" donor profiles with synthetic IDs. The tool + refuses to work without real donor data by design. +- Don't suggest using the generated firmware against systems the user does + not own or have permission to test. See `SECURITY.md` and the legal notice + in the README. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.rst rename to CONTRIBUTING.md diff --git a/MANIFEST.in b/MANIFEST.in index ea764268..c092d273 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,8 +3,7 @@ include README.md include LICENSE.txt include CHANGELOG.md include cliff.toml -include CONTRIBUTING.rst -include AUTHORS.rst +include CONTRIBUTING.md include pyproject.toml include setup.cfg diff --git a/README.md b/README.md index 56399176..f9231e19 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ [![Wheel](https://img.shields.io/badge/wheel-✓-green)](https://github.com/voltcyclone/PCILeechFWGenerator/actions/workflows/consolidated-ci.yml) [![Source Distribution](https://img.shields.io/badge/sdist-✓-green)](https://github.com/voltcyclone/PCILeechFWGenerator/actions/workflows/consolidated-ci.yml) -![Discord](https://dcbadge.limes.pink/api/shield/429866199833247744) +[![Discord](https://img.shields.io/badge/Discord-Support-5865F2?logo=discord&logoColor=white)](https://discord.gg/dwQfMNsb7W) Generate authentic PCIe DMA firmware from real donor hardware using a **3-stage host-container-host pipeline**. This tool extracts donor configurations from a local device via VFIO and generates unique PCILeech FPGA bitstreams. diff --git a/SECURITY.md b/SECURITY.md index 7ee59b9a..7bf4b755 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,58 @@ ## Supported Versions -Use the latest version. This tool is as secure as it can be but please read and evaluate the source code yourself +Only the latest release on the `main` branch receives security fixes. If you're +running an older release, please upgrade before reporting an issue you can't +reproduce on `main`. ## Reporting a Vulnerability -Add an issue pls. +**Please do not open public GitHub issues for security vulnerabilities.** + +PCILeechFWGenerator produces firmware that interacts with PCIe DMA, VFIO, and +host kernel drivers. Bugs in this area can have real safety and privacy +consequences, so we handle them privately first. + +To report a vulnerability: + +1. Go to the **[Security tab][advisories]** of the repository and click + *"Report a vulnerability"* to open a private security advisory. This is the + preferred channel. +2. If you cannot use GitHub Security Advisories, email the maintainer at + `ramseymcgrath@gmail.com` with the subject line + `[PCILeechFWGenerator security]`. PGP is not required, but if you'd like to + encrypt the report ask for a key first. + +Please include: + +- A description of the issue and the impact you believe it has. +- Steps to reproduce, or a minimal proof of concept. +- The version / commit SHA you tested against. +- Any suggested mitigations, if you have them. + +## What to Expect + +- Acknowledgement of your report within **3 business days**. +- An initial assessment (severity, whether we can reproduce it, rough timeline) + within **10 business days**. +- Coordinated disclosure: we'll work with you on a fix and a disclosure date. + Credit is given in the release notes unless you'd prefer to remain anonymous. + +## Scope + +In scope: + +- Code in this repository (firmware generation, build pipeline, TUI, helper + scripts). +- Default configurations and example flows in the documentation. + +Out of scope: + +- Vulnerabilities in upstream dependencies (please report those upstream; we'll + bump the version once a fix is available). +- Issues that require an already-compromised host or physical access beyond + what the tool itself assumes. +- Misuse of generated firmware against systems you don't own or have permission + to test — that's a policy issue, not a vulnerability. + +[advisories]: https://github.com/VoltCyclone/PCILeechFWGenerator/security/advisories/new