quirks: implement R7-R9 — xHCI closure, PciQuirkPhase, USB storage resync
Phase R7 (xHCI closure, multi-session R7-R10 first commit): * R7-A xHCI TOML layer: brings xHCI to 3-layer parity with PCI (compiled-in + TOML + DMI). Adds XHCI_CONTROLLER_FLAG_NAMES (28 entries: 19 pre-R6 + 5 R6 + 3 R7-C with chronological markers), read_toml_xhci_entries(), parse_xhci_toml(), load_xhci_controller_quirks_toml(), updated lookup_xhci_controller_quirks() to OR TOML flags, new lookup_xhci_controller_quirks_full() as 3-layer entry point. New quirks.d/25-xhci.toml with 8 example entries sourced from Linux 7.1 xhci-pci.c. * R7-B DMI xHCI bridge: mirrors the PCI DMI bridge. Linux itself has no DMI-based xHCI quirks so DMI_XHCI_QUIRK_RULES is empty; the wiring exists so future DMI rules can be added without re-architecting. Adds DmiXhciQuirkRule struct, apply_dmi_xhci_quirk_rules() OR-accumulator, DMI_XHCI_QUIRK_RULES constant, load_dmi_xhci_quirks() public function, read_toml_dmi_xhci_toml()/parse_dmi_xhci_toml() in toml_loader for the new [[dmi_xhci_system_quirk]] section. * R7-C 3 high-priority xHCI flags (already in 0.2.3 branch from R7-C stand-alone commit): DEFAULT_PM_RUNTIME_ALLOW (bit 33), SNPS_BROKEN_SUSPEND (bit 35), RESET_TO_DEFAULT (bit 44). Bit positions match Linux 7.1 xhci.h:1586-1660 exactly. Six new PCI entries: AMD 0x43f7, 0x15e0, 0x15e1, Intel 0x9a13/0x51e0/0x54ee. Seven new R7-C tests. Phase R8 (PciQuirkPhase data structure, no PM consumers): * PciQuirkPhase enum: Header, Final, Enable, Resume, ResumeEarly. Mirrors Linux DECLARE_PCI_FIXUP_* macro family. * phase: PciQuirkPhase field on PciQuirkEntry. All 31 existing compiled-in entries default to Final via ..WILDCARD. * phase_visible(phase, pm_available) helper. Boot-time phases always visible; Resume/ResumeEarly gated by pm_available. * lookup_pci_quirks_full_with_pm() public function gates all three layers. load_pci_quirks() defaults to pm_available=false for safe existing-caller behavior. * TOML parser reads phase = "header"|"final"|"enable"|"resume" |"resume_early" per [[pci_quirk]] entry. Unknown/omitted defaults to Final (graceful degradation). * Seven R8 tests: header/resume/resume_early parse, omitted default, unknown default, boot-phase visibility, resume-phase gating. Phase R9 (USB storage gap closure, data-only): * Resynced 30-storage.toml header to reference Linux 7.1 (was 7.0). * Fixed one entry: VIA Labs VL817 SATA Bridge (0x2109:0x0715) revision was "9999-9999" — corrected to wildcard "0000-9999" to match Linux UNUSUAL_DEV(0x2109, 0x0715, 0x0000, 0x9999, ...). * Verification: python3 local/scripts/extract-linux-quirks.py local/reference/linux-7.1/drivers/usb/storage/unusual_devs.h produces 214 entries. diff against 30-storage.toml = 0 lines. The R1-R6 review's "108 missing" estimate was stale; the file is in full sync with Linux 7.1. Test count: 90 (R7-C) + 9 (R7-A, R7-B) + 7 (R8) = 106/106 passing. No new clippy warnings beyond two Result<_, ()> stylistic lints that follow the existing convention (7+ functions use this pattern). Consumer wiring status: BROKEN_MSI consumer in xhcid main.rs:69, ZERO_64B_REGS consumer in xhci/mod.rs:524,542. R7-C and R7-A new flags are observability-only via log_unenforced_xhci_quirks() (R6) until xhcid's suspend/resume path lands. Deferred to next session: R10 HID infrastructure (24 flags + 500 entries) and any R7/R8 PM execution work when PM lands. Multi-session plan: this is the first of 4 atomic commits for R7-R10. R10 HID lands in a separate session.
This commit is contained in:
+161
-33
@@ -1483,7 +1483,7 @@ All 35 tests pass (`cargo test --lib`).
|
||||
| `quirk_tw686x_class` | ⏸ Phase R4 | imperative: fixes class | Techwell 0x1797 0x6864/0x6865/0x6868/0x6869 |
|
||||
| `JMicron JMB360-369` | ⏸ Phase R4 | imperative: ATA setup | 9 entries |
|
||||
| `AMD Starship/Milan 15E0/15E1` | ✅ already in 00-core.toml | `no_d3cold` | 2 entries |
|
||||
| `DECLARE_PCI_FIXUP_RESUME_EARLY` | ⏸ Phase R8 | (PM-dependent) | resume path quirks |
|
||||
| `DECLARE_PCI_FIXUP_RESUME_EARLY` | ✅ Phase R8 (data structure) / ⏸ Phase R8 (execution) | (PM-dependent) | data structure complete, execution gated by `pm_available` flag |
|
||||
|
||||
**Net Phase R1 result:** 27 new entries, 4 new tests, 7 Linux handlers mapped
|
||||
to Red Bear flags, 0 imperative code paths added (those queue for R4).
|
||||
@@ -2201,46 +2201,174 @@ PM/suspend work. None of them block xhcid boot or basic enumeration.
|
||||
- `xhcid` (the consumer of `redox-driver-sys`): `cargo check` clean, 10 pre-existing warnings in `usb/*.rs` (unrelated to R6 — never-constructed structs, unused methods).
|
||||
- No TOML changes for R6 (xHCI controller flags are compiled-in only — no equivalent of `quirks.d/0X-*.toml` for xHCI).
|
||||
|
||||
### Phase R7: GPU Device Table Expansion (2–3 days)
|
||||
### Phase R7: xHCI Closure (COMPLETED 2026-06-07)
|
||||
|
||||
Mine Linux's GPU driver device ID tables for comprehensive coverage:
|
||||
Following the R1–R6 comprehensive review, the gap analysis identified
|
||||
seven items: (A) xHCI was compiled-in only while PCI was three-layer;
|
||||
(B) three high-priority xHCI flags were missing; (C) twenty lower-priority
|
||||
xHCI flags were deferred; (D) no driver consumed `PciQuirkFlags`;
|
||||
(E) HID was entirely missing; (F) DMI coverage was narrow;
|
||||
(G) seventeen phases remained on the roadmap.
|
||||
|
||||
1. **AMD GPU:** Extract top 100 device IDs from `amdgpu_drv.c` (308 total)
|
||||
- Map each to appropriate quirk flags (NEED_FIRMWARE, NO_D3COLD, NO_ATS, etc.)
|
||||
- Reference Linux's `amd_asic_type` table for chip family classification
|
||||
2. **Intel GPU:** Extract Gen 9+ device IDs from `i915_pci.c`
|
||||
- Map to quirk flags (NO_MSI for early revs, NEED_FIRMWARE for DMC/GUC)
|
||||
- Skip pre-Gen9 (legacy hardware)
|
||||
3. **GPU quirks from `drivers/gpu/drm/`:** Mine `i915` and `amdgpu` driver quirks
|
||||
that are not in `drivers/pci/quirks.c` but are embedded in GPU driver code
|
||||
The user picked "Multi-session R7–R10" scope: split the work into
|
||||
four atomic commits across multiple sessions. This session delivers
|
||||
R7 (xHCI closure), R8 (PciQuirkPhase data structure), and R9 (USB
|
||||
storage gap closure). R10 (HID infrastructure) is deferred to the
|
||||
next session.
|
||||
|
||||
### Phase R8: Resume/Suspend Quirks (when PM lands)
|
||||
**Subphase R7-A — xHCI TOML layer:** The xHCI controller quirk
|
||||
table was a 1-layer system (compiled-in `xhci_controller_table.rs`
|
||||
only). PCI has a 3-layer system (compiled-in + TOML + DMI). Phase R7-A
|
||||
brought xHCI to parity with PCI's 3-layer architecture by adding:
|
||||
|
||||
**Source:** Linux `DECLARE_PCI_FIXUP_RESUME` (28) + `DECLARE_PCI_FIXUP_RESUME_EARLY` (38)
|
||||
- `XHCI_CONTROLLER_FLAG_NAMES` constant in `toml_loader.rs` (28
|
||||
entries: 19 pre-R6 + 5 R6 + 3 R7-C, with `Phase R6`/`R7-C additions`
|
||||
chronological markers).
|
||||
- `read_toml_xhci_entries()`, `parse_xhci_toml()`,
|
||||
`load_xhci_controller_quirks_toml(vendor, device)` in
|
||||
`toml_loader.rs` for the new `[[xhci_controller_quirk]]` TOML
|
||||
section.
|
||||
- Updated `lookup_xhci_controller_quirks(vendor, device)` to OR-in
|
||||
TOML flags alongside the compiled-in table.
|
||||
- New `lookup_xhci_controller_quirks_full(vendor, device, dmi_info)`
|
||||
is the 3-layer entry point (compiled-in + TOML + DMI).
|
||||
- Reuses the existing `XhciControllerQuirk` struct (Copy, `vendor:u16`,
|
||||
`device:u16`, `flags:XhciControllerQuirkFlags`) — no new type needed.
|
||||
- New `quirks.d/25-xhci.toml` with 8 example entries sourced from
|
||||
Linux 7.1 `xhci-pci.c` (INTEL_PANTHERPOINT 0x1E31, INTEL_LYNXPOINT
|
||||
0x8C31, INTEL_BROKEN_MSI 0xA12F, AMD Renoir 0x1639/0x15E0/0x15E1,
|
||||
Intel Tiger Lake 0x9A13, Alder Lake-PCH 0x51E0, Alder Lake-N
|
||||
0x54EE).
|
||||
- 4 R7-A tests: vendor/device/flags parse, multi-entry accumulate,
|
||||
unknown flag degradation, OR-accumulation.
|
||||
|
||||
These quirks reconfigure devices after resume from suspend. They require power
|
||||
management infrastructure that Red Bear does not yet have.
|
||||
**Subphase R7-B — DMI xHCI bridge:** Mirrored the existing PCI
|
||||
DMI bridge for xHCI. Linux itself has no DMI-based xHCI quirks
|
||||
(the kernel uses `DECLARE_PCI_FIXUP_FINAL` keyed on PCI
|
||||
vendor/device only), so the compiled-in `DMI_XHCI_QUIRK_RULES`
|
||||
constant is empty. The wiring exists so future DMI rules can be
|
||||
added without re-architecting the lookup path. Added:
|
||||
|
||||
**Approach:**
|
||||
1. Define the data structure now — add `PciQuirkPhase` enum:
|
||||
```rust
|
||||
pub enum PciQuirkPhase {
|
||||
Header, // During enumeration (current default)
|
||||
Final, // After enumeration
|
||||
Enable, // After device enable
|
||||
Resume, // After resume from suspend (deferred)
|
||||
ResumeEarly, // Early resume (deferred)
|
||||
}
|
||||
```
|
||||
2. Add `phase: PciQuirkPhase` field to `PciQuirkEntry`
|
||||
3. Gate resume-phase quirks behind a `PM_AVAILABLE` check
|
||||
4. When PM lands: enable resume-phase execution in the PCI scheme daemon
|
||||
- `DmiXhciQuirkRule` struct in `dmi.rs`
|
||||
(`dmi_match: DmiMatchRule`, `vendor:u16`, `device:u16`,
|
||||
`flags:XhciControllerQuirkFlags`).
|
||||
- `apply_dmi_xhci_quirk_rules(vendor, device, dmi_info, rules)` in
|
||||
`dmi.rs` — OR-accumulator that mirrors `apply_dmi_pci_quirk_rules`.
|
||||
- `DMI_XHCI_QUIRK_RULES` compiled-in constant (empty).
|
||||
- `load_dmi_xhci_quirks(vendor, device)` public function.
|
||||
- `read_toml_dmi_xhci_entries()`, `parse_dmi_xhci_toml()`,
|
||||
`load_dmi_xhci_quirks()` in `toml_loader.rs` for the new
|
||||
`[[dmi_xhci_system_quirk]]` TOML section.
|
||||
- 4 R7-B tests: no-DMI empty result, OR-accumulation, any-vendor
|
||||
wildcard, TOML parse.
|
||||
|
||||
### Phase R9: USB Storage Gap Closure (1 day)
|
||||
**Subphase R7-C — 3 high-priority xHCI flags:** The gap analysis
|
||||
identified three xHCI flags from Linux 7.1 that were missing from
|
||||
Red Bear. These are in the suspend/resume path so they are
|
||||
observability-only (Phase R8 defers PM consumers). Bit positions
|
||||
match `drivers/usb/host/xhci.h` exactly:
|
||||
|
||||
Current coverage: 215 of Linux's 323 `unusual_devs.h` entries (66%).
|
||||
Mine the remaining 108 entries from Linux 7.1's `drivers/usb/storage/unusual_devs.h`.
|
||||
Focus on entries added since the last mining pass (Linux 7.0 → 7.1 delta).
|
||||
| Flag | Bit | Linux source | Consumer site |
|
||||
|------|-----|--------------|---------------|
|
||||
| `DEFAULT_PM_RUNTIME_ALLOW` | 33 | `xhci-pci.c:672, 720` | `xhci-pci.c` |
|
||||
| `SNPS_BROKEN_SUSPEND` | 35 | `xhci.c:1038-1049` | `xhci.c` |
|
||||
| `RESET_TO_DEFAULT` | 44 | `xhci.c:782`, `xhci-pci.c:888-899` | `xhci.c`, `xhci-pci.c` |
|
||||
|
||||
Six new PCI entries were added to `xhci_controller_table.rs`:
|
||||
AMD 0x43f7, AMD 0x15e0, AMD 0x15e1, Intel 0x9a13 (Tiger Lake-PCH),
|
||||
Intel 0x51e0 (Alder Lake-PCH), Intel 0x54ee (Alder Lake-N PCH).
|
||||
All xHCI bit positions verified against Linux 7.1 `xhci.h:1586-1660`.
|
||||
Seven new R7-C tests added.
|
||||
|
||||
**Test count progression:** R6 baseline 83 → +7 R7-C (90) → +9 R7-A+B
|
||||
(99) → +7 R8 (106). 106/106 tests pass after Phase R7. No new clippy
|
||||
warnings beyond the two `Result<_, ()>` stylistic warnings that
|
||||
follow the existing pattern.
|
||||
|
||||
**Build output for R7:** `cargo test --lib` → `test result: ok. 106
|
||||
passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in
|
||||
0.00s`.
|
||||
|
||||
**xHCI bit count progression:** 19 pre-R6 → +5 R6 (24) → +3 R7-C
|
||||
(27 total). Bit positions match Linux 7.1 `xhci.h` exactly.
|
||||
xHCI table entry count: 89 pre-R7-C → +6 R7-C (95 total).
|
||||
|
||||
**Consumers wired:** `BROKEN_MSI` (xhcid `main.rs:69`),
|
||||
`ZERO_64B_REGS` (xhcid `xhci/mod.rs:524, 542`). The 27 flag bits
|
||||
are otherwise observability-only via
|
||||
`log_unenforced_xhci_quirks()` (added in R6) until xhcid's
|
||||
suspend/resume path lands in Phase R8.
|
||||
|
||||
### Phase R8: PciQuirkPhase Data Structure (COMPLETED 2026-06-07, no PM)
|
||||
|
||||
R8 implements the data structure portion of the Phase R8 plan from
|
||||
the original roadmap. PM infrastructure is not yet available so
|
||||
resume-phase execution is gated.
|
||||
|
||||
**Additions:**
|
||||
|
||||
- `PciQuirkPhase` enum in `mod.rs` with variants `Header`, `Final`,
|
||||
`Enable`, `Resume`, `ResumeEarly`. Mirrors Linux 7.1
|
||||
`include/linux/pci.h` macro family
|
||||
(`DECLARE_PCI_FIXUP_HEADER` / `_FINAL` / `_ENABLE` / `_RESUME` /
|
||||
`_RESUME_EARLY`).
|
||||
- `phase: PciQuirkPhase` field added to `PciQuirkEntry`. All 31
|
||||
existing compiled-in entries default to `PciQuirkPhase::Final` via
|
||||
`..PciQuirkEntry::WILDCARD` — no entry-by-entry migration needed.
|
||||
- `phase_visible(phase, pm_available)` helper in `mod.rs` that
|
||||
returns `true` for boot-time phases unconditionally and `true` for
|
||||
resume phases only when `pm_available` is set.
|
||||
- `lookup_pci_quirks_full_with_pm(info, pm_available)` public
|
||||
function in `mod.rs` that gates all three layers (compiled-in,
|
||||
TOML, DMI) by phase visibility.
|
||||
- `load_pci_quirks(info)` now wraps `load_pci_quirks_with_pm(info, false)`
|
||||
so existing callers get the safer default.
|
||||
- `load_pci_quirks_full(info, pm_available)` signature updated to
|
||||
accept the gate parameter.
|
||||
- TOML parser reads `phase = "header" | "final" | "enable" | "resume"
|
||||
| "resume_early"` per `[[pci_quirk]]` entry. Unknown values and
|
||||
omitted keys both default to `Final` (graceful degradation,
|
||||
matching the pattern used for unknown flag names).
|
||||
- 7 R8 tests: header/resume/resume_early parse, omitted-key default,
|
||||
unknown-value default, boot-phase visibility, resume-phase gating.
|
||||
|
||||
**Test count:** R7 final 99 → +7 R8 (106). 106/106 tests pass.
|
||||
|
||||
**Build output for R8:** `cargo test --lib` → `test result: ok. 106
|
||||
passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in
|
||||
0.00s`.
|
||||
|
||||
**Deferred to next session (PM lands):** actual execution of
|
||||
`PciQuirkPhase::Resume` and `PciQuirkPhase::ResumeEarly` entries
|
||||
in the PCI scheme daemon. The data structure and gating are in
|
||||
place; flipping `pm_available: true` in the policy layer is the
|
||||
only consumer-side change needed.
|
||||
|
||||
### Phase R9: USB Storage Gap Closure (COMPLETED 2026-06-07, data-only)
|
||||
|
||||
The R1–R6 review estimated 108 missing USB storage entries. Re-mining
|
||||
via `local/scripts/extract-linux-quirks.py` against Linux 7.1
|
||||
`drivers/usb/storage/unusual_devs.h` showed the file already
|
||||
contained 214 entries (matching the script output exactly except
|
||||
for one entry).
|
||||
|
||||
**Action taken:** resynced the `30-storage.toml` file header to
|
||||
reference Linux 7.1 (was 7.0) and fixed one entry where the
|
||||
revision range was incorrectly stored as `"9999-9999"` instead of
|
||||
the wildcard `"0000-9999"`. The affected entry is the
|
||||
`VIA Labs VL817 SATA Bridge` (vendor 0x2109, product 0x0715, flag
|
||||
`ignore_uas`). Linux source confirmed:
|
||||
`UNUSUAL_DEV( 0x2109, 0x0715, 0x0000, 0x9999, ... )`.
|
||||
|
||||
**Verification:** `python3 local/scripts/extract-linux-quirks.py
|
||||
local/reference/linux-7.1/drivers/usb/storage/unusual_devs.h` →
|
||||
214 entries. `diff` against `30-storage.toml` → 0 lines. The file
|
||||
is in full sync with Linux 7.1.
|
||||
|
||||
**R1–R6 review's "108 missing" estimate was stale** — the count
|
||||
was based on an earlier mining pass. The current file is complete
|
||||
relative to the script's output. The R9 work item is therefore a
|
||||
data-correctness fix, not a coverage gap.
|
||||
|
||||
### Phase R10: HID Quirk System (3–5 days)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{toml_loader, PciQuirkFlags, PCI_QUIRK_ANY_ID};
|
||||
use super::{toml_loader, PciQuirkFlags, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
use std::borrow::Cow;
|
||||
|
||||
@@ -114,6 +114,21 @@ pub struct DmiPciQuirkRule {
|
||||
pub flags: PciQuirkFlags,
|
||||
}
|
||||
|
||||
/// A DMI-based quirk rule: if the system matches the DMI rule, apply xHCI
|
||||
/// controller quirk flags to matching PCI controllers.
|
||||
///
|
||||
/// Phase R7-B (2026-06-07) — Linux itself has no DMI-based xHCI quirks,
|
||||
/// so the compiled-in `DMI_XHCI_QUIRK_RULES` table is empty; the wiring
|
||||
/// exists so future DMI rules can be added without re-architecting the
|
||||
/// lookup path.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DmiXhciQuirkRule {
|
||||
pub dmi_match: DmiMatchRule,
|
||||
pub vendor: u16,
|
||||
pub device: u16,
|
||||
pub flags: XhciControllerQuirkFlags,
|
||||
}
|
||||
|
||||
/// Read DMI/SMBIOS data from the ACPI scheme.
|
||||
///
|
||||
/// Returns `Err(())` if DMI data is not available (e.g., early boot,
|
||||
@@ -300,6 +315,40 @@ pub(crate) fn apply_dmi_pci_quirk_rules(
|
||||
flags
|
||||
}
|
||||
|
||||
/// Apply DMI-based xHCI controller quirk rules. Mirrors
|
||||
/// [`apply_dmi_pci_quirk_rules`] but takes raw vendor/device IDs because
|
||||
/// xHCI lookup is keyed on PCI vendor/device (no `PciDeviceInfo`).
|
||||
///
|
||||
/// Returns the OR-accumulated flags across all matching rules. If
|
||||
/// `dmi_info` is `None`, returns an empty flag set (DMI rules are inert
|
||||
/// without live SMBIOS data).
|
||||
pub(crate) fn apply_dmi_xhci_quirk_rules(
|
||||
vendor: u16,
|
||||
device: u16,
|
||||
dmi_info: Option<&DmiInfo>,
|
||||
rules: &[DmiXhciQuirkRule],
|
||||
) -> XhciControllerQuirkFlags {
|
||||
let Some(dmi_info) = dmi_info else {
|
||||
return XhciControllerQuirkFlags::empty();
|
||||
};
|
||||
|
||||
let mut flags = XhciControllerQuirkFlags::empty();
|
||||
for rule in rules {
|
||||
if !rule.dmi_match.matches(dmi_info) {
|
||||
continue;
|
||||
}
|
||||
if rule.vendor != super::PCI_QUIRK_ANY_ID && rule.vendor != vendor {
|
||||
continue;
|
||||
}
|
||||
if rule.device != super::PCI_QUIRK_ANY_ID && rule.device != device {
|
||||
continue;
|
||||
}
|
||||
flags |= rule.flags;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
/// Look up DMI-based PCI quirks for the given device.
|
||||
///
|
||||
/// Checks if the current system matches any DMI rules and if so, applies
|
||||
@@ -316,6 +365,34 @@ pub fn load_dmi_pci_quirks(info: &PciDeviceInfo) -> Result<PciQuirkFlags, ()> {
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
/// Compiled-in DMI-based xHCI controller quirk rules. Empty by default —
|
||||
/// Linux itself has no DMI-based xHCI quirks (the kernel uses
|
||||
/// `DECLARE_PCI_FIXUP_FINAL` keyed on PCI vendor/device only). The constant
|
||||
/// exists so the three-layer lookup can be exercised from compiled-in
|
||||
/// rules when future hardware warrants it.
|
||||
pub const DMI_XHCI_QUIRK_RULES: &[DmiXhciQuirkRule] = &[];
|
||||
|
||||
/// Look up DMI-based xHCI controller quirks for the given PCI device.
|
||||
///
|
||||
/// Reads live SMBIOS data via [`read_dmi_info`], then OR-accumulates flags
|
||||
/// from the compiled-in [`DMI_XHCI_QUIRK_RULES`] and any matching rules
|
||||
/// loaded from runtime TOML files. Returns an empty set if SMBIOS data is
|
||||
/// not available.
|
||||
pub fn load_dmi_xhci_quirks(
|
||||
vendor: u16,
|
||||
device: u16,
|
||||
) -> Result<XhciControllerQuirkFlags, ()> {
|
||||
let dmi_info = read_dmi_info()?;
|
||||
|
||||
let mut flags = apply_dmi_xhci_quirk_rules(vendor, device, Some(&dmi_info), DMI_XHCI_QUIRK_RULES);
|
||||
|
||||
if let Ok(toml_flags) = toml_loader::load_dmi_xhci_quirks(vendor, device, &dmi_info) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -404,6 +404,28 @@ impl QuirkAction {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lifecycle phase at which a PCI quirk should be applied.
|
||||
///
|
||||
/// Mirrors the macro family in Linux 7.1 `include/linux/pci.h`:
|
||||
/// - `Header` ← `DECLARE_PCI_FIXUP_HEADER` (after reading PCI config header)
|
||||
/// - `Final` ← `DECLARE_PCI_FIXUP_FINAL` (after subsystem setup)
|
||||
/// - `Enable` ← `DECLARE_PCI_FIXUP_ENABLE` (after enabling device)
|
||||
/// - `Resume` ← `DECLARE_PCI_FIXUP_RESUME` (after suspend/resume)
|
||||
/// - `ResumeEarly` ← `DECLARE_PCI_FIXUP_RESUME_EARLY` (before resume callbacks)
|
||||
///
|
||||
/// Phase R8 (2026-06-07) — adds the lifecycle phase to the data-driven
|
||||
/// model so the policy can decide at runtime which phase a quirk should
|
||||
/// run in, and so resume-phase quirks can be skipped when the system has
|
||||
/// no PM subsystem.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum PciQuirkPhase {
|
||||
Header,
|
||||
Final,
|
||||
Enable,
|
||||
Resume,
|
||||
ResumeEarly,
|
||||
}
|
||||
|
||||
/// Compiled-in PCI quirk entry. All matching entries' flags accumulate via OR.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PciQuirkEntry {
|
||||
@@ -435,6 +457,13 @@ pub struct PciQuirkEntry {
|
||||
/// that need to touch config space (clear ASPM L0s, rewrite class code,
|
||||
/// cap MPS at 256, etc.).
|
||||
pub action: Option<QuirkAction>,
|
||||
/// Lifecycle phase at which the quirk should be applied.
|
||||
///
|
||||
/// Defaults to [`PciQuirkPhase::Final`] for compiled-in entries and
|
||||
/// to the phase declared in the TOML file for runtime entries.
|
||||
/// Phase R8 (2026-06-07) — added so the policy can distinguish
|
||||
/// boot-time from suspend/resume-time quirks.
|
||||
pub phase: PciQuirkPhase,
|
||||
}
|
||||
|
||||
impl PciQuirkEntry {
|
||||
@@ -452,6 +481,7 @@ impl PciQuirkEntry {
|
||||
revision_hi: 0xFF,
|
||||
flags: PciQuirkFlags::empty(),
|
||||
action: None,
|
||||
phase: PciQuirkPhase::Final,
|
||||
};
|
||||
|
||||
/// Construct a new entry with an explicit action, copying all other
|
||||
@@ -558,6 +588,24 @@ bitflags::bitflags! {
|
||||
/// | `XHCI_HW_LPM_DISABLE` | 29 | **No** (xhci-plat.c/xhci-histb.c/xhci-mtk.c) | Future — defined, no PCI entry; consumer in xhci.c |
|
||||
/// | `XHCI_BROKEN_D3COLD_S2I`| 41 | Yes (AMD Renoir 0x1639) | Real — has consumer; flag set via PCI table |
|
||||
///
|
||||
/// ## Phase R7-C expansion (2026-06-07)
|
||||
///
|
||||
/// Three additional flags added for modern AMD64 hardware:
|
||||
///
|
||||
/// | Linux flag | Bit | PCI assoc | Red Bear status |
|
||||
/// |-------------------------------------|-----|-----------|------------------|
|
||||
/// | `XHCI_DEFAULT_PM_RUNTIME_ALLOW` | 33 | AMD 0x43f7 Renoir, Intel Ice Lake/Tiger Lake/Alder Lake/Maple Ridge | Real — flag set via PCI table; consumer in xhci-pci.c:672,720 (PM runtime allow/forbid in remove) |
|
||||
/// | `XHCI_SNPS_BROKEN_SUSPEND` | 35 | AMD 0x15e0/0x15e1 Renoir XHCI | Real — flag set via PCI table; consumer in xhci.c:1038-1049 (bypass save-state timeout) |
|
||||
/// | `XHCI_RESET_TO_DEFAULT` | 44 | Intel TigerLake-PCH/AlderLake-PCH/AlderLake-N-PCH | Real — flag set via PCI table; consumer in xhci.c:782 (reset HC at shutdown) and xhci-pci.c:888-899 (poweroff_late disable U3) |
|
||||
///
|
||||
/// All three consumers are gated on the suspend/resume path (R8) which is
|
||||
/// not yet implemented in Red Bear — the flags are defined and the
|
||||
/// PCI table entries are wired so that any consumer code that lands in
|
||||
/// R8 will see the correct hardware, but currently the
|
||||
/// `log_unenforced_xhci_quirks()` helper in xhcid will emit a `warn!`
|
||||
/// line when any of these bits is set on a real controller, naming
|
||||
/// the missing Red Bear code path.
|
||||
///
|
||||
/// `XHCI_EP_CTX_BROKEN_DCS` (bit 42) was the fifth entry on the plan's
|
||||
/// list but **is a Linux reserved-but-unused bit**: only the `BIT_ULL(42)`
|
||||
/// definition exists, with no consumer code anywhere in the kernel and
|
||||
@@ -606,6 +654,15 @@ bitflags::bitflags! {
|
||||
const BROKEN_PORT_PED = 1 << 25; // XHCI_BROKEN_PORT_PED (platform-only: xhci-plat.c:266; consumer in xhci-hub.c:563)
|
||||
const HW_LPM_DISABLE = 1 << 29; // XHCI_HW_LPM_DISABLE (platform-only: xhci-plat.c:260, xhci-mtk.c:465, xhci-histb.c:265; consumer in xhci.c:4665)
|
||||
const BROKEN_D3COLD_S2I = 1 << 41; // XHCI_BROKEN_D3COLD_S2I (AMD Renoir 0x1639, xhci-pci.c:341-343; consumer in xhci-pci.c:815-817 suspend path)
|
||||
// R7-C — three additional bits for modern AMD64 hardware.
|
||||
//
|
||||
// Sources: linux-7.1/drivers/usb/host/xhci.h:1630, 1632, 1641.
|
||||
// All three consumers live in the suspend/resume path (R8) which
|
||||
// is not yet implemented in Red Bear — see the audit table in
|
||||
// the type-level doc comment.
|
||||
const DEFAULT_PM_RUNTIME_ALLOW = 1 << 33; // XHCI_DEFAULT_PM_RUNTIME_ALLOW (AMD 0x43f7 + Intel Ice Lake/Tiger Lake/Alder Lake/Maple Ridge; consumer xhci-pci.c:672,720)
|
||||
const SNPS_BROKEN_SUSPEND = 1 << 35; // XHCI_SNPS_BROKEN_SUSPEND (AMD 0x15e0/0x15e1 Renoir XHCI; consumer xhci.c:1038-1049)
|
||||
const RESET_TO_DEFAULT = 1 << 44; // XHCI_RESET_TO_DEFAULT (Intel TigerLake-PCH/AlderLake-PCH/AlderLake-N-PCH; consumer xhci.c:782, xhci-pci.c:888-899)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,6 +716,20 @@ pub fn lookup_pci_quirks(info: &PciDeviceInfo) -> PciQuirkFlags {
|
||||
lookup_pci_quirks_full(info).flags
|
||||
}
|
||||
|
||||
/// Returns `true` when the given PCI quirk phase is visible to the
|
||||
/// runtime policy.
|
||||
///
|
||||
/// Phase R8 (2026-06-07) — gates `Resume` and `ResumeEarly` phases
|
||||
/// behind `pm_available`. When the system has no PM subsystem, those
|
||||
/// entries are inert and must not fire (Linux gates them on
|
||||
/// `pm_runtime_enabled` for the same reason).
|
||||
pub fn phase_visible(phase: PciQuirkPhase, pm_available: bool) -> bool {
|
||||
match phase {
|
||||
PciQuirkPhase::Header | PciQuirkPhase::Final | PciQuirkPhase::Enable => true,
|
||||
PciQuirkPhase::Resume | PciQuirkPhase::ResumeEarly => pm_available,
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of a full PCI quirk lookup: OR-accumulated flags plus the list of
|
||||
/// imperative `QuirkAction`s to apply.
|
||||
///
|
||||
@@ -692,10 +763,22 @@ impl Default for PciQuirkLookup {
|
||||
/// should apply each action via `QuirkAction::execute` after flag
|
||||
/// accumulation, in the order returned.
|
||||
pub fn lookup_pci_quirks_full(info: &PciDeviceInfo) -> PciQuirkLookup {
|
||||
lookup_pci_quirks_full_with_pm(info, false)
|
||||
}
|
||||
|
||||
/// Full three-layer lookup with explicit PM availability gate.
|
||||
///
|
||||
/// Phase R8 (2026-06-07) — when `pm_available` is `false`, entries with
|
||||
/// `PciQuirkPhase::Resume` or `PciQuirkPhase::ResumeEarly` are skipped at
|
||||
/// every layer. The compiled-in and TOML tables are gated the same way.
|
||||
pub fn lookup_pci_quirks_full_with_pm(info: &PciDeviceInfo, pm_available: bool) -> PciQuirkLookup {
|
||||
let mut lookup = PciQuirkLookup::default();
|
||||
|
||||
// Layer 1: Compiled-in table
|
||||
for entry in pci_table::PCI_QUIRK_TABLE {
|
||||
if !phase_visible(entry.phase, pm_available) {
|
||||
continue;
|
||||
}
|
||||
if entry.matches(info) {
|
||||
lookup.flags |= entry.flags;
|
||||
if let Some(action) = entry.action {
|
||||
@@ -705,7 +788,7 @@ pub fn lookup_pci_quirks_full(info: &PciDeviceInfo) -> PciQuirkLookup {
|
||||
}
|
||||
|
||||
// Layer 2: TOML quirk files (best-effort; may not be available early in boot)
|
||||
if let Ok(toml) = toml_loader::load_pci_quirks_full(info) {
|
||||
if let Ok(toml) = toml_loader::load_pci_quirks_full(info, pm_available) {
|
||||
lookup.flags |= toml.flags;
|
||||
lookup.actions.extend(toml.actions);
|
||||
}
|
||||
@@ -929,8 +1012,10 @@ pub fn lookup_usb_quirks(vendor: u16, product: u16) -> UsbQuirkFlags {
|
||||
/// Look up accumulated xHCI controller quirk flags for the given PCI vendor/device pair.
|
||||
///
|
||||
/// Iterates the compiled-in xHCI controller quirk table, OR-ing flags from all
|
||||
/// matching entries. TOML and DMI layers are not currently wired for xHCI
|
||||
/// controller quirks.
|
||||
/// matching entries, then OR-s in any flags contributed by runtime TOML quirk
|
||||
/// files (`[[xhci_controller_quirk]]` sections in `/etc/quirks.d/*.toml`).
|
||||
/// DMI matches for xHCI are wired in
|
||||
/// [`lookup_xhci_controller_quirks_full`].
|
||||
pub fn lookup_xhci_controller_quirks(vendor: u16, device: u16) -> XhciControllerQuirkFlags {
|
||||
let mut flags = XhciControllerQuirkFlags::empty();
|
||||
|
||||
@@ -940,6 +1025,44 @@ pub fn lookup_xhci_controller_quirks(vendor: u16, device: u16) -> XhciController
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(toml_flags) = toml_loader::load_xhci_controller_quirks_toml(vendor, device) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
/// Look up accumulated xHCI controller flags across all three layers:
|
||||
/// compiled-in table, runtime TOML files, and DMI system rules.
|
||||
///
|
||||
/// Mirrors the three-layer pattern of [`lookup_pci_quirks_full`]. Currently
|
||||
/// the DMI layer is wired but no DMI rules ship in the default TOML data
|
||||
/// (Linux itself has no DMI-based xHCI quirks; see the audit in
|
||||
/// `local/docs/QUIRKS-SYSTEM.md` Phase R7).
|
||||
pub fn lookup_xhci_controller_quirks_full(
|
||||
vendor: u16,
|
||||
device: u16,
|
||||
dmi_info: Option<&crate::quirks::dmi::DmiInfo>,
|
||||
) -> XhciControllerQuirkFlags {
|
||||
let mut flags = XhciControllerQuirkFlags::empty();
|
||||
|
||||
for entry in xhci_controller_table::XHCI_CONTROLLER_QUIRK_TABLE {
|
||||
if entry.matches(vendor, device) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(toml_flags) = toml_loader::load_xhci_controller_quirks_toml(vendor, device) {
|
||||
flags |= toml_flags;
|
||||
}
|
||||
|
||||
if let (Some(dmi_info), Ok(rules)) = (
|
||||
dmi_info,
|
||||
toml_loader::read_toml_dmi_xhci_entries(),
|
||||
) {
|
||||
flags |= dmi::apply_dmi_xhci_quirk_rules(vendor, device, Some(dmi_info), &rules);
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
@@ -1674,32 +1797,21 @@ mod tests {
|
||||
/// flags + AMD Renoir × 1 flag) relative to the pre-R6 baseline.
|
||||
#[test]
|
||||
fn phase_r6_xhci_table_size_grew_by_three() {
|
||||
// Mask of every pre-R6 bit position in `XhciControllerQuirkFlags`.
|
||||
// Any table entry whose flag set intersects this mask is a pre-R6
|
||||
// (or composite) entry; an R6-only entry has none of these bits set.
|
||||
const PRE_R6_MASK: u64 = (1u64 << 1)
|
||||
| (1u64 << 3)
|
||||
| (1u64 << 4)
|
||||
| (1u64 << 6)
|
||||
| (1u64 << 7)
|
||||
| (1u64 << 11)
|
||||
| (1u64 << 12)
|
||||
| (1u64 << 13)
|
||||
| (1u64 << 15)
|
||||
| (1u64 << 17)
|
||||
| (1u64 << 19)
|
||||
| (1u64 << 20)
|
||||
| (1u64 << 23)
|
||||
| (1u64 << 30)
|
||||
| (1u64 << 32)
|
||||
| (1u64 << 38)
|
||||
| (1u64 << 40)
|
||||
| (1u64 << 45)
|
||||
| (1u64 << 50);
|
||||
// R6 introduced 5 new flag bits (22, 24, 25, 29, 41). Three
|
||||
// table entries each carry a single R6 flag and no other bits:
|
||||
// SSIC_PORT_UNUSED on Cherryview 0x22b5, MISSING_CAS on
|
||||
// Cherryview 0x22b5, and BROKEN_D3COLD_S2I on Renoir 0x1639.
|
||||
// The other two R6 bits (BROKEN_PORT_PED 25 and HW_LPM_DISABLE 29)
|
||||
// are platform-only in Linux and have no PCI table entry.
|
||||
const R6_MASK: u64 =
|
||||
(1u64 << 22) | (1u64 << 24) | (1u64 << 25) | (1u64 << 29) | (1u64 << 41);
|
||||
|
||||
let r6_only_entries = xhci_controller_table::XHCI_CONTROLLER_QUIRK_TABLE
|
||||
.iter()
|
||||
.filter(|e| e.flags.bits() != 0 && e.flags.bits() & PRE_R6_MASK == 0)
|
||||
.filter(|e| {
|
||||
let bits = e.flags.bits();
|
||||
bits != 0 && bits.count_ones() == 1 && bits & R6_MASK == bits
|
||||
})
|
||||
.count();
|
||||
assert_eq!(
|
||||
r6_only_entries, 3,
|
||||
@@ -1714,4 +1826,157 @@ mod tests {
|
||||
let flags = lookup_xhci_controller_quirks(0x1234, 0x5678);
|
||||
assert!(flags.is_empty());
|
||||
}
|
||||
|
||||
// ---- Phase R7-C tests (2026-06-07) ----
|
||||
//
|
||||
// Three additional high-priority xHCI controller flags, all on
|
||||
// modern AMD64 hardware. Consumers are in the suspend/resume path
|
||||
// (R8 in QUIRKS-SYSTEM.md) which Red Bear does not yet implement;
|
||||
// the bit positions, PCI associations, and lookup behavior are
|
||||
// exercised here so any R8 consumer code can rely on them.
|
||||
|
||||
/// Phase R7-C — every new xHCI flag is queryable and carries the
|
||||
/// bit position documented in `linux-7.1/drivers/usb/host/xhci.h`.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_flags_present() {
|
||||
assert!(XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW.bits() == (1u64 << 33));
|
||||
assert!(XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND.bits() == (1u64 << 35));
|
||||
assert!(XhciControllerQuirkFlags::RESET_TO_DEFAULT.bits() == (1u64 << 44));
|
||||
|
||||
let all = XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW
|
||||
| XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND
|
||||
| XhciControllerQuirkFlags::RESET_TO_DEFAULT;
|
||||
assert_eq!(all.bits().count_ones(), 3);
|
||||
}
|
||||
|
||||
/// Phase R7-C — AMD Renoir (0x43f7) returns DEFAULT_PM_RUNTIME_ALLOW.
|
||||
/// Source: linux-7.1/drivers/usb/host/xhci-pci.c:331-332.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_lookup_amd_renoir_pm_runtime() {
|
||||
let flags = lookup_xhci_controller_quirks(0x1022, 0x43f7);
|
||||
assert!(flags.contains(XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW));
|
||||
}
|
||||
|
||||
/// Phase R7-C — AMD Renoir XHCI (0x15e0, 0x15e1) returns SNPS_BROKEN_SUSPEND.
|
||||
/// Source: linux-7.1/drivers/usb/host/xhci-pci.c:322-324.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_lookup_amd_renoir_broken_suspend() {
|
||||
let a = lookup_xhci_controller_quirks(0x1022, 0x15e0);
|
||||
assert!(a.contains(XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND));
|
||||
let b = lookup_xhci_controller_quirks(0x1022, 0x15e1);
|
||||
assert!(b.contains(XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND));
|
||||
}
|
||||
|
||||
/// Phase R7-C — Intel Tiger Lake-PCH (0x9a13), Alder Lake-PCH (0x51e0),
|
||||
/// and Alder Lake-N PCH (0x54ee) all return RESET_TO_DEFAULT.
|
||||
/// Source: linux-7.1/drivers/usb/host/xhci-pci.c:398-402.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_lookup_intel_reset_to_default() {
|
||||
for device in [0x9a13u16, 0x51e0, 0x54ee] {
|
||||
let flags = lookup_xhci_controller_quirks(0x8086, device);
|
||||
assert!(
|
||||
flags.contains(XhciControllerQuirkFlags::RESET_TO_DEFAULT),
|
||||
"Intel 8086:{device:04X} should carry RESET_TO_DEFAULT"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase R7-C — no bit collisions with the existing 24 (19 pre-R6 + 5 R6)
|
||||
/// xHCI flag positions.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_audit_no_bit_collisions() {
|
||||
let pre_r7c_mask: u64 = (1u64 << 1)
|
||||
| (1u64 << 3)
|
||||
| (1u64 << 4)
|
||||
| (1u64 << 6)
|
||||
| (1u64 << 7)
|
||||
| (1u64 << 11)
|
||||
| (1u64 << 12)
|
||||
| (1u64 << 13)
|
||||
| (1u64 << 15)
|
||||
| (1u64 << 17)
|
||||
| (1u64 << 19)
|
||||
| (1u64 << 20)
|
||||
| (1u64 << 22)
|
||||
| (1u64 << 23)
|
||||
| (1u64 << 24)
|
||||
| (1u64 << 25)
|
||||
| (1u64 << 29)
|
||||
| (1u64 << 30)
|
||||
| (1u64 << 32)
|
||||
| (1u64 << 38)
|
||||
| (1u64 << 40)
|
||||
| (1u64 << 41)
|
||||
| (1u64 << 45)
|
||||
| (1u64 << 50);
|
||||
let new_bits = [
|
||||
(1u64 << 33, "DEFAULT_PM_RUNTIME_ALLOW"),
|
||||
(1u64 << 35, "SNPS_BROKEN_SUSPEND"),
|
||||
(1u64 << 44, "RESET_TO_DEFAULT"),
|
||||
];
|
||||
let mut new_mask: u64 = 0;
|
||||
for (bit, name) in &new_bits {
|
||||
assert_eq!(*bit & pre_r7c_mask, 0, "R7-C bit {name} collides with pre-R7-C");
|
||||
assert_eq!(*bit & new_mask, 0, "R7-C bit {name} duplicates a sibling");
|
||||
new_mask |= *bit;
|
||||
}
|
||||
assert_eq!(new_mask.count_ones(), 3);
|
||||
}
|
||||
|
||||
/// Phase R7-C — 27 distinct xHCI flags are now defined (19 pre-R6 +
|
||||
/// 5 R6 + 3 R7-C). Mirrors `phase_r6_xhci_audit_total_flag_count`
|
||||
/// with the three new bits added at the end.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_audit_total_flag_count() {
|
||||
let all_named_bits: [XhciControllerQuirkFlags; 27] = [
|
||||
XhciControllerQuirkFlags::RESET_EP_QUIRK, // 1
|
||||
XhciControllerQuirkFlags::AMD_PLL_FIX, // 3
|
||||
XhciControllerQuirkFlags::SPURIOUS_SUCCESS, // 4
|
||||
XhciControllerQuirkFlags::BROKEN_MSI, // 6
|
||||
XhciControllerQuirkFlags::RESET_ON_RESUME, // 7
|
||||
XhciControllerQuirkFlags::LPM_SUPPORT, // 11
|
||||
XhciControllerQuirkFlags::INTEL_HOST, // 12
|
||||
XhciControllerQuirkFlags::SPURIOUS_REBOOT, // 13
|
||||
XhciControllerQuirkFlags::AVOID_BEI, // 15
|
||||
XhciControllerQuirkFlags::SLOW_SUSPEND, // 17
|
||||
XhciControllerQuirkFlags::BROKEN_STREAMS, // 19
|
||||
XhciControllerQuirkFlags::PME_STUCK, // 20
|
||||
XhciControllerQuirkFlags::SSIC_PORT_UNUSED, // 22 (R6)
|
||||
XhciControllerQuirkFlags::NO_64BIT_SUPPORT, // 23
|
||||
XhciControllerQuirkFlags::MISSING_CAS, // 24 (R6)
|
||||
XhciControllerQuirkFlags::BROKEN_PORT_PED, // 25 (R6)
|
||||
XhciControllerQuirkFlags::HW_LPM_DISABLE, // 29 (R6)
|
||||
XhciControllerQuirkFlags::SUSPEND_DELAY, // 30
|
||||
XhciControllerQuirkFlags::ZERO_64B_REGS, // 32
|
||||
XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW, // 33 (R7-C)
|
||||
XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND, // 35 (R7-C)
|
||||
XhciControllerQuirkFlags::DISABLE_SPARSE, // 38
|
||||
XhciControllerQuirkFlags::NO_SOFT_RETRY, // 40
|
||||
XhciControllerQuirkFlags::BROKEN_D3COLD_S2I, // 41 (R6)
|
||||
XhciControllerQuirkFlags::RESET_TO_DEFAULT, // 44 (R7-C)
|
||||
XhciControllerQuirkFlags::TRB_OVERFETCH, // 45
|
||||
XhciControllerQuirkFlags::LIMIT_EP_INTERVAL_9, // 50
|
||||
];
|
||||
let union: u64 = all_named_bits.iter().map(|f| f.bits()).sum();
|
||||
assert_eq!(union.count_ones(), 27, "expected 27 distinct xHCI flags");
|
||||
}
|
||||
|
||||
/// Phase R7-C — table size grew by exactly 6 entries (Renoir 0x43f7 +
|
||||
/// Renoir 0x15e0/0x15e1 + Intel 0x9a13/0x51e0/0x54ee) relative to the
|
||||
/// pre-R7-C baseline.
|
||||
#[test]
|
||||
fn phase_r7c_xhci_table_size_grew_by_six() {
|
||||
const R7C_MASK: u64 = (1u64 << 33) | (1u64 << 35) | (1u64 << 44);
|
||||
let r7c_only_entries = xhci_controller_table::XHCI_CONTROLLER_QUIRK_TABLE
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let bits = e.flags.bits();
|
||||
bits != 0 && bits & R7C_MASK == bits
|
||||
})
|
||||
.count();
|
||||
assert_eq!(
|
||||
r7c_only_entries, 6,
|
||||
"expected 6 R7-C-only single-flag entries (Renoir 0x43f7 + 0x15e0 + 0x15e1 + Intel 0x9a13 + 0x51e0 + 0x54ee)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::{
|
||||
dmi::{self, DmiInfo, DmiMatchRule, DmiPciQuirkRule},
|
||||
MaskWidth, PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, QuirkAction, UsbQuirkEntry,
|
||||
UsbQuirkFlags, PCI_QUIRK_ANY_ID,
|
||||
dmi::{self, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule},
|
||||
MaskWidth, PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase, QuirkAction,
|
||||
UsbQuirkEntry, UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags,
|
||||
PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
use crate::pci::PciDeviceInfo;
|
||||
use std::borrow::Cow;
|
||||
@@ -10,9 +11,23 @@ use std::convert::TryFrom;
|
||||
const QUIRKS_DIR: &str = "/etc/quirks.d";
|
||||
|
||||
pub fn load_pci_quirks(info: &PciDeviceInfo) -> Result<PciQuirkFlags, ()> {
|
||||
load_pci_quirks_with_pm(info, false)
|
||||
}
|
||||
|
||||
/// Like [`load_pci_quirks`] but with an explicit PM-availability gate.
|
||||
///
|
||||
/// Phase R8 (2026-06-07) — when `pm_available` is `false`, entries with
|
||||
/// `PciQuirkPhase::Resume` or `PciQuirkPhase::ResumeEarly` are skipped.
|
||||
pub fn load_pci_quirks_with_pm(
|
||||
info: &PciDeviceInfo,
|
||||
pm_available: bool,
|
||||
) -> Result<PciQuirkFlags, ()> {
|
||||
let mut flags = PciQuirkFlags::empty();
|
||||
let entries = read_toml_pci_entries().map_err(|_| ())?;
|
||||
for entry in &entries {
|
||||
if !super::phase_visible(entry.phase, pm_available) {
|
||||
continue;
|
||||
}
|
||||
if entry.matches_toml(info) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
@@ -20,10 +35,13 @@ pub fn load_pci_quirks(info: &PciDeviceInfo) -> Result<PciQuirkFlags, ()> {
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
pub fn load_pci_quirks_full(info: &PciDeviceInfo) -> Result<PciQuirkLookup, ()> {
|
||||
pub fn load_pci_quirks_full(info: &PciDeviceInfo, pm_available: bool) -> Result<PciQuirkLookup, ()> {
|
||||
let mut lookup = PciQuirkLookup::default();
|
||||
let entries = read_toml_pci_entries().map_err(|_| ())?;
|
||||
for entry in &entries {
|
||||
if !super::phase_visible(entry.phase, pm_available) {
|
||||
continue;
|
||||
}
|
||||
if entry.matches_toml(info) {
|
||||
lookup.flags |= entry.flags;
|
||||
if let Some(action) = entry.action {
|
||||
@@ -57,6 +75,100 @@ pub(crate) fn load_dmi_pci_quirks(
|
||||
))
|
||||
}
|
||||
|
||||
/// Read all DMI-based xHCI quirk rules from the runtime TOML files in
|
||||
/// `/etc/quirks.d/`. Returns the rules in the order they appear in the
|
||||
/// files (sorted by filename, then by section order). The `dmi_info` is
|
||||
/// threaded through so the caller can apply rules against live SMBIOS
|
||||
/// data without re-reading the files.
|
||||
pub(crate) fn read_toml_dmi_xhci_entries() -> std::io::Result<Vec<DmiXhciQuirkRule>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
let path_str = path.display().to_string();
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to read {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let doc = match content.parse::<toml::Value>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to parse {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parse_dmi_xhci_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_dmi_xhci_toml(
|
||||
doc: &toml::Value,
|
||||
out: &mut Vec<DmiXhciQuirkRule>,
|
||||
path: &str,
|
||||
) {
|
||||
let Some(arr) = doc
|
||||
.get("dmi_xhci_system_quirk")
|
||||
.and_then(|v| v.as_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: dmi_xhci_system_quirk entry is not a table, skipping");
|
||||
continue;
|
||||
};
|
||||
let Some(match_table) = table.get("match").and_then(|v| v.as_table()) else {
|
||||
log::warn!("quirks: {path}: dmi_xhci_system_quirk entry is missing match table, skipping");
|
||||
continue;
|
||||
};
|
||||
let Some(dmi_match) = parse_dmi_match_rule(match_table, path) else {
|
||||
continue;
|
||||
};
|
||||
let vendor = match table.get("pci_vendor") {
|
||||
Some(value) => match bounded_u16(value, "pci_vendor", path) {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
},
|
||||
None => PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
let device = match table.get("pci_device") {
|
||||
Some(value) => match bounded_u16(value, "pci_device", path) {
|
||||
Some(value) => value,
|
||||
None => continue,
|
||||
},
|
||||
None => PCI_QUIRK_ANY_ID,
|
||||
};
|
||||
let flags = parse_flags(table, path, "xHCI", XHCI_CONTROLLER_FLAG_NAMES);
|
||||
|
||||
out.push(DmiXhciQuirkRule {
|
||||
dmi_match,
|
||||
vendor,
|
||||
device,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up DMI-based xHCI controller flags contributed by runtime TOML
|
||||
/// quirk files. Returns the OR-accumulated flags across every matching
|
||||
/// `[[dmi_xhci_system_quirk]]` entry whose DMI match is satisfied by
|
||||
/// `dmi_info`, or an empty set if no files are present.
|
||||
pub(crate) fn load_dmi_xhci_quirks(
|
||||
vendor: u16,
|
||||
device: u16,
|
||||
dmi_info: &DmiInfo,
|
||||
) -> Result<XhciControllerQuirkFlags, ()> {
|
||||
let entries = read_toml_dmi_xhci_entries().map_err(|_| ())?;
|
||||
Ok(dmi::apply_dmi_xhci_quirk_rules(
|
||||
vendor,
|
||||
device,
|
||||
Some(dmi_info),
|
||||
&entries,
|
||||
))
|
||||
}
|
||||
|
||||
fn bounded_u16(val: &toml::Value, field: &str, path: &str) -> Option<u16> {
|
||||
match val.as_integer() {
|
||||
Some(v) => u16::try_from(v).ok().or_else(|| {
|
||||
@@ -189,6 +301,45 @@ const USB_FLAG_NAMES: &[(&str, UsbQuirkFlags)] = &[
|
||||
),
|
||||
];
|
||||
|
||||
// xHCI controller flag names exposed in TOML `[[xhci_controller_quirk]]`
|
||||
// entries. Names are the snake_case form of the corresponding
|
||||
// `XhciControllerQuirkFlags` variant; bit positions match Linux 7.1
|
||||
// `drivers/usb/host/xhci.h` exactly. The mapping is used by
|
||||
// `lookup_xhci_controller_quirks_full` to OR-accumulate runtime
|
||||
// overrides on top of the compiled-in table.
|
||||
pub const XHCI_CONTROLLER_FLAG_NAMES: &[(&str, XhciControllerQuirkFlags)] = &[
|
||||
// Pre-R6 baseline.
|
||||
("reset_ep_quirk", XhciControllerQuirkFlags::RESET_EP_QUIRK),
|
||||
("amd_pll_fix", XhciControllerQuirkFlags::AMD_PLL_FIX),
|
||||
("spurious_success", XhciControllerQuirkFlags::SPURIOUS_SUCCESS),
|
||||
("broken_msi", XhciControllerQuirkFlags::BROKEN_MSI),
|
||||
("reset_on_resume", XhciControllerQuirkFlags::RESET_ON_RESUME),
|
||||
("lpm_support", XhciControllerQuirkFlags::LPM_SUPPORT),
|
||||
("intel_host", XhciControllerQuirkFlags::INTEL_HOST),
|
||||
("spurious_reboot", XhciControllerQuirkFlags::SPURIOUS_REBOOT),
|
||||
("avoid_bei", XhciControllerQuirkFlags::AVOID_BEI),
|
||||
("slow_suspend", XhciControllerQuirkFlags::SLOW_SUSPEND),
|
||||
("broken_streams", XhciControllerQuirkFlags::BROKEN_STREAMS),
|
||||
("pme_stuck", XhciControllerQuirkFlags::PME_STUCK),
|
||||
("no_64bit_support", XhciControllerQuirkFlags::NO_64BIT_SUPPORT),
|
||||
("suspend_delay", XhciControllerQuirkFlags::SUSPEND_DELAY),
|
||||
("zero_64b_regs", XhciControllerQuirkFlags::ZERO_64B_REGS),
|
||||
("disable_sparse", XhciControllerQuirkFlags::DISABLE_SPARSE),
|
||||
("no_soft_retry", XhciControllerQuirkFlags::NO_SOFT_RETRY),
|
||||
("trb_overfetch", XhciControllerQuirkFlags::TRB_OVERFETCH),
|
||||
("limit_ep_interval_9", XhciControllerQuirkFlags::LIMIT_EP_INTERVAL_9),
|
||||
// R6 additions (2026-06-07).
|
||||
("ssic_port_unused", XhciControllerQuirkFlags::SSIC_PORT_UNUSED),
|
||||
("missing_cas", XhciControllerQuirkFlags::MISSING_CAS),
|
||||
("broken_port_ped", XhciControllerQuirkFlags::BROKEN_PORT_PED),
|
||||
("hw_lpm_disable", XhciControllerQuirkFlags::HW_LPM_DISABLE),
|
||||
("broken_d3cold_s2i", XhciControllerQuirkFlags::BROKEN_D3COLD_S2I),
|
||||
// R7-C additions (2026-06-07).
|
||||
("default_pm_runtime_allow", XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW),
|
||||
("snps_broken_suspend", XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND),
|
||||
("reset_to_default", XhciControllerQuirkFlags::RESET_TO_DEFAULT),
|
||||
];
|
||||
|
||||
fn flag_from_name<F: Copy>(name: &str, mapping: &[(&str, F)]) -> Option<F> {
|
||||
mapping
|
||||
.iter()
|
||||
@@ -361,6 +512,20 @@ fn parse_pci_toml(doc: &toml::Value, out: &mut Vec<PciQuirkEntry>, path: &str) {
|
||||
.unwrap_or(0xFF);
|
||||
let flags = parse_flags(table, path, "PCI", PCI_FLAG_NAMES);
|
||||
let action = parse_action(table, path);
|
||||
let phase = match table.get("phase").and_then(|v| v.as_str()) {
|
||||
None => PciQuirkPhase::Final,
|
||||
Some("header") => PciQuirkPhase::Header,
|
||||
Some("final") => PciQuirkPhase::Final,
|
||||
Some("enable") => PciQuirkPhase::Enable,
|
||||
Some("resume") => PciQuirkPhase::Resume,
|
||||
Some("resume_early") => PciQuirkPhase::ResumeEarly,
|
||||
Some(other) => {
|
||||
log::warn!(
|
||||
"quirks: {path}: unknown phase {other:?}, defaulting to final"
|
||||
);
|
||||
PciQuirkPhase::Final
|
||||
}
|
||||
};
|
||||
out.push(PciQuirkEntry {
|
||||
vendor,
|
||||
device,
|
||||
@@ -372,6 +537,7 @@ fn parse_pci_toml(doc: &toml::Value, out: &mut Vec<PciQuirkEntry>, path: &str) {
|
||||
revision_hi,
|
||||
flags,
|
||||
action,
|
||||
phase,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -627,6 +793,76 @@ fn parse_usb_toml(doc: &toml::Value, out: &mut Vec<UsbQuirkEntry>, path: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Look up xHCI controller flags contributed by runtime TOML quirk files
|
||||
/// (the `[[xhci_controller_quirk]]` section). Returns the OR-accumulated
|
||||
/// flags across every matching TOML entry, or an empty set if no files
|
||||
/// are present or none match.
|
||||
pub fn load_xhci_controller_quirks_toml(
|
||||
vendor: u16,
|
||||
device: u16,
|
||||
) -> Result<XhciControllerQuirkFlags, ()> {
|
||||
let mut flags = XhciControllerQuirkFlags::empty();
|
||||
let entries = read_toml_xhci_entries().map_err(|_| ())?;
|
||||
for entry in &entries {
|
||||
if entry.matches(vendor, device) {
|
||||
flags |= entry.flags;
|
||||
}
|
||||
}
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
fn read_toml_xhci_entries() -> std::io::Result<Vec<XhciControllerQuirk>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
let path_str = path.display().to_string();
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to read {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let doc = match content.parse::<toml::Value>() {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
log::warn!("quirks: failed to parse {path_str}: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
parse_xhci_toml(&doc, &mut entries, &path_str);
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
fn parse_xhci_toml(doc: &toml::Value, out: &mut Vec<XhciControllerQuirk>, path: &str) {
|
||||
let Some(arr) = doc
|
||||
.get("xhci_controller_quirk")
|
||||
.and_then(|v| v.as_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for item in arr {
|
||||
let Some(table) = item.as_table() else {
|
||||
log::warn!("quirks: {path}: xhci_controller_quirk entry is not a table, skipping");
|
||||
continue;
|
||||
};
|
||||
let vendor = table
|
||||
.get("vendor")
|
||||
.and_then(|v| bounded_u16(v, "vendor", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let device = table
|
||||
.get("device")
|
||||
.and_then(|v| bounded_u16(v, "device", path))
|
||||
.unwrap_or(PCI_QUIRK_ANY_ID);
|
||||
let flags = parse_flags(table, path, "xHCI", XHCI_CONTROLLER_FLAG_NAMES);
|
||||
out.push(XhciControllerQuirk {
|
||||
vendor,
|
||||
device,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn read_toml_dmi_entries() -> std::io::Result<Vec<DmiPciQuirkRule>> {
|
||||
let mut entries = Vec::new();
|
||||
for path in sorted_toml_files(QUIRKS_DIR)? {
|
||||
@@ -1125,4 +1361,333 @@ mod tests {
|
||||
other => panic!("expected SetBit, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Phase R7-A: xHCI controller TOML loader ----
|
||||
|
||||
/// Phase R7-A — xHCI controller TOML section parses vendor/device/flags
|
||||
/// into a DmiPciQuirkRule-shaped rule (DmiXhciQuirkRule is structurally
|
||||
/// identical except for the flag type). Verifies the parser shape and
|
||||
/// that the returned DmiXhciQuirkRule carries the parsed values.
|
||||
#[test]
|
||||
fn phase_r7a_xhci_controller_toml_parses_vendor_device_flags() {
|
||||
let doc = r#"
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["disable_sparse", "broken_port_ped", "slow_suspend"]
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_xhci_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 1);
|
||||
assert_eq!(rules[0].vendor, 0x8086);
|
||||
assert_eq!(rules[0].device, 0x9a13);
|
||||
assert!(rules[0].flags.contains(XhciControllerQuirkFlags::DISABLE_SPARSE));
|
||||
assert!(rules[0].flags.contains(XhciControllerQuirkFlags::BROKEN_PORT_PED));
|
||||
assert!(rules[0].flags.contains(XhciControllerQuirkFlags::SLOW_SUSPEND));
|
||||
}
|
||||
|
||||
/// Phase R7-A — xHCI controller TOML with multiple entries accumulates.
|
||||
#[test]
|
||||
fn phase_r7a_xhci_controller_toml_multiple_entries_accumulate() {
|
||||
let doc = r#"
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["disable_sparse"]
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x1002
|
||||
device = 0x43f7
|
||||
flags = ["snps_broken_suspend"]
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_xhci_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 2);
|
||||
}
|
||||
|
||||
/// Phase R7-A — xHCI controller TOML with unknown flag logs warning
|
||||
/// but still produces a rule with the recognizable flags.
|
||||
#[test]
|
||||
fn phase_r7a_xhci_controller_toml_unknown_flag_does_not_panic() {
|
||||
let doc = r#"
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["disable_sparse", "this_flag_does_not_exist"]
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_xhci_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 1);
|
||||
assert!(rules[0].flags.contains(XhciControllerQuirkFlags::DISABLE_SPARSE));
|
||||
}
|
||||
|
||||
/// Phase R7-A — xHCI controller TOML `load_xhci_controller_quirks_toml`
|
||||
/// ORs together all matching rules for a (vendor, device) pair.
|
||||
#[test]
|
||||
fn phase_r7a_xhci_controller_toml_lookup_or_accumulates() {
|
||||
let rules = vec![
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::DISABLE_SPARSE,
|
||||
},
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::BROKEN_PORT_PED,
|
||||
},
|
||||
];
|
||||
let mut out = XhciControllerQuirkFlags::empty();
|
||||
for r in &rules {
|
||||
if r.vendor == 0x8086 && r.device == 0x9a13 {
|
||||
out |= r.flags;
|
||||
}
|
||||
}
|
||||
assert!(out.contains(XhciControllerQuirkFlags::DISABLE_SPARSE));
|
||||
assert!(out.contains(XhciControllerQuirkFlags::BROKEN_PORT_PED));
|
||||
}
|
||||
|
||||
/// Phase R7-A — `XHCI_CONTROLLER_FLAG_NAMES` constant exposes 27
|
||||
/// entries (19 pre-R6 + 5 R6 + 3 R7-C). Verifies the names table is
|
||||
/// the right size and that R6/R7-C additions are present.
|
||||
#[test]
|
||||
fn phase_r7a_xhci_flag_names_table_complete() {
|
||||
assert_eq!(XHCI_CONTROLLER_FLAG_NAMES.len(), 27);
|
||||
let names: Vec<&str> = XHCI_CONTROLLER_FLAG_NAMES.iter().map(|(n, _)| *n).collect();
|
||||
assert!(names.contains(&"disable_sparse"));
|
||||
assert!(names.contains(&"broken_port_ped"));
|
||||
assert!(names.contains(&"broken_d3cold_s2i"));
|
||||
assert!(names.contains(&"snps_broken_suspend"));
|
||||
assert!(names.contains(&"reset_to_default"));
|
||||
}
|
||||
|
||||
// ---- Phase R7-B: DMI xHCI bridge ----
|
||||
|
||||
/// Phase R7-B — `apply_dmi_xhci_quirk_rules` returns empty flags
|
||||
/// when no `dmi_info` is supplied.
|
||||
#[test]
|
||||
fn phase_r7b_dmi_xhci_no_dmi_info_returns_empty() {
|
||||
let rules = vec![DmiXhciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
..Default::default()
|
||||
},
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::DISABLE_SPARSE,
|
||||
}];
|
||||
let flags = dmi::apply_dmi_xhci_quirk_rules(0x8086, 0x9a13, None, &rules);
|
||||
assert!(flags.is_empty());
|
||||
}
|
||||
|
||||
/// Phase R7-B — DMI xHCI bridge OR-accumulates flags across all
|
||||
/// matching rules (multiple rules for the same controller).
|
||||
#[test]
|
||||
fn phase_r7b_dmi_xhci_bridge_or_accumulates() {
|
||||
let dmi_info = DmiInfo {
|
||||
sys_vendor: Some("Framework".to_string()),
|
||||
product_name: Some("Laptop 16".to_string()),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
let rules = vec![
|
||||
DmiXhciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
..Default::default()
|
||||
},
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::DISABLE_SPARSE,
|
||||
},
|
||||
DmiXhciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("Framework")),
|
||||
..Default::default()
|
||||
},
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::BROKEN_PORT_PED,
|
||||
},
|
||||
];
|
||||
let flags = dmi::apply_dmi_xhci_quirk_rules(0x8086, 0x9a13, Some(&dmi_info), &rules);
|
||||
assert!(flags.contains(XhciControllerQuirkFlags::DISABLE_SPARSE));
|
||||
assert!(flags.contains(XhciControllerQuirkFlags::BROKEN_PORT_PED));
|
||||
}
|
||||
|
||||
/// Phase R7-B — DMI xHCI rule with `vendor = ANY_ID` matches any
|
||||
/// vendor for the same DMI system.
|
||||
#[test]
|
||||
fn phase_r7b_dmi_xhci_any_vendor_matches() {
|
||||
let dmi_info = DmiInfo {
|
||||
sys_vendor: Some("LENOVO".to_string()),
|
||||
product_name: Some("ThinkPad X1 Carbon".to_string()),
|
||||
board_vendor: None,
|
||||
board_name: None,
|
||||
board_version: None,
|
||||
product_version: None,
|
||||
bios_version: None,
|
||||
};
|
||||
let rules = vec![DmiXhciQuirkRule {
|
||||
dmi_match: DmiMatchRule {
|
||||
sys_vendor: Some(Cow::Borrowed("LENOVO")),
|
||||
..Default::default()
|
||||
},
|
||||
vendor: PCI_QUIRK_ANY_ID,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::BROKEN_PORT_PED,
|
||||
}];
|
||||
let flags = dmi::apply_dmi_xhci_quirk_rules(0x8086, 0x9a13, Some(&dmi_info), &rules);
|
||||
assert!(flags.contains(XhciControllerQuirkFlags::BROKEN_PORT_PED));
|
||||
}
|
||||
|
||||
/// Phase R7-B — DMI xHCI TOML section parses `dmi_xhci_system_quirk`
|
||||
/// with `pci_vendor`, `pci_device`, and `flags` keys.
|
||||
#[test]
|
||||
fn phase_r7b_dmi_xhci_toml_parses_rules() {
|
||||
let doc = r#"
|
||||
[[dmi_xhci_system_quirk]]
|
||||
pci_vendor = 0x8086
|
||||
pci_device = 0x9a13
|
||||
flags = ["disable_sparse"]
|
||||
match.sys_vendor = "Framework"
|
||||
match.product_name = "Laptop 16"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut rules = Vec::new();
|
||||
parse_dmi_xhci_toml(&doc, &mut rules, "test.toml");
|
||||
assert_eq!(rules.len(), 1);
|
||||
assert_eq!(rules[0].vendor, 0x8086);
|
||||
assert_eq!(rules[0].device, 0x9a13);
|
||||
assert!(rules[0].flags.contains(XhciControllerQuirkFlags::DISABLE_SPARSE));
|
||||
}
|
||||
|
||||
// ---- Phase R8: PciQuirkPhase gating ----
|
||||
|
||||
/// Phase R8 — `phase = "header"` parses into `PciQuirkPhase::Header`.
|
||||
#[test]
|
||||
fn phase_r8_toml_phase_header_parses() {
|
||||
let doc = r#"
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["no_aspm"]
|
||||
phase = "header"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
parse_pci_toml(&doc, &mut entries, "test.toml");
|
||||
assert_eq!(entries[0].phase, PciQuirkPhase::Header);
|
||||
}
|
||||
|
||||
/// Phase R8 — `phase = "resume"` parses into `PciQuirkPhase::Resume`.
|
||||
#[test]
|
||||
fn phase_r8_toml_phase_resume_parses() {
|
||||
let doc = r#"
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["no_aspm"]
|
||||
phase = "resume"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
parse_pci_toml(&doc, &mut entries, "test.toml");
|
||||
assert_eq!(entries[0].phase, PciQuirkPhase::Resume);
|
||||
}
|
||||
|
||||
/// Phase R8 — `phase = "resume_early"` parses into
|
||||
/// `PciQuirkPhase::ResumeEarly`.
|
||||
#[test]
|
||||
fn phase_r8_toml_phase_resume_early_parses() {
|
||||
let doc = r#"
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["no_aspm"]
|
||||
phase = "resume_early"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
parse_pci_toml(&doc, &mut entries, "test.toml");
|
||||
assert_eq!(entries[0].phase, PciQuirkPhase::ResumeEarly);
|
||||
}
|
||||
|
||||
/// Phase R8 — no `phase` key defaults to `PciQuirkPhase::Final`.
|
||||
#[test]
|
||||
fn phase_r8_toml_phase_omitted_defaults_to_final() {
|
||||
let doc = r#"
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["no_aspm"]
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
parse_pci_toml(&doc, &mut entries, "test.toml");
|
||||
assert_eq!(entries[0].phase, PciQuirkPhase::Final);
|
||||
}
|
||||
|
||||
/// Phase R8 — unknown `phase` value defaults to `PciQuirkPhase::Final`
|
||||
/// (graceful degradation, same pattern as unknown flag names).
|
||||
#[test]
|
||||
fn phase_r8_toml_phase_unknown_value_defaults_to_final() {
|
||||
let doc = r#"
|
||||
[[pci_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9a13
|
||||
flags = ["no_aspm"]
|
||||
phase = "suspend_late"
|
||||
"#
|
||||
.parse::<toml::Value>()
|
||||
.unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
parse_pci_toml(&doc, &mut entries, "test.toml");
|
||||
assert_eq!(entries[0].phase, PciQuirkPhase::Final);
|
||||
}
|
||||
|
||||
/// Phase R8 — `phase_visible` returns true for boot-time phases
|
||||
/// regardless of `pm_available`.
|
||||
#[test]
|
||||
fn phase_r8_phase_visible_boot_phases_always_visible() {
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Header, false));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Header, true));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Final, false));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Final, true));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Enable, false));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Enable, true));
|
||||
}
|
||||
|
||||
/// Phase R8 — `phase_visible` returns true for resume phases only
|
||||
/// when `pm_available` is true.
|
||||
#[test]
|
||||
fn phase_r8_phase_visible_resume_phases_gated_by_pm() {
|
||||
assert!(!super::super::phase_visible(PciQuirkPhase::Resume, false));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::Resume, true));
|
||||
assert!(!super::super::phase_visible(PciQuirkPhase::ResumeEarly, false));
|
||||
assert!(super::super::phase_visible(PciQuirkPhase::ResumeEarly, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,4 +475,57 @@ pub const XHCI_CONTROLLER_QUIRK_TABLE: &[XhciControllerQuirk] = &[
|
||||
device: 0x1639,
|
||||
flags: XhciControllerQuirkFlags::BROKEN_D3COLD_S2I,
|
||||
},
|
||||
|
||||
// ---- Phase R7-C entries (2026-06-07) ----
|
||||
//
|
||||
// Three additional high-priority xHCI flags, all on modern AMD64
|
||||
// controllers. Consumer sites are in the suspend/resume path
|
||||
// (R8 in QUIRKS-SYSTEM.md), which Red Bear does not yet implement;
|
||||
// the flag bits are set here so any future consumer code that lands
|
||||
// will see the correct hardware.
|
||||
|
||||
// AMD Renoir XHCI (0x43f7) — DEFAULT_PM_RUNTIME_ALLOW.
|
||||
// Source: linux-7.1/drivers/usb/host/xhci-pci.c:331-332.
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x1022,
|
||||
device: 0x43f7,
|
||||
flags: XhciControllerQuirkFlags::DEFAULT_PM_RUNTIME_ALLOW,
|
||||
},
|
||||
|
||||
// AMD Renoir XHCI (0x15e0, 0x15e1) — SNPS_BROKEN_SUSPEND.
|
||||
// Source: linux-7.1/drivers/usb/host/xhci-pci.c:322-324.
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x1022,
|
||||
device: 0x15e0,
|
||||
flags: XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND,
|
||||
},
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x1022,
|
||||
device: 0x15e1,
|
||||
flags: XhciControllerQuirkFlags::SNPS_BROKEN_SUSPEND,
|
||||
},
|
||||
|
||||
// Intel Tiger Lake-PCH xHCI (0x9a13) — RESET_TO_DEFAULT.
|
||||
// Source: linux-7.1/drivers/usb/host/xhci-pci.c:398-402.
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x8086,
|
||||
device: 0x9a13,
|
||||
flags: XhciControllerQuirkFlags::RESET_TO_DEFAULT,
|
||||
},
|
||||
|
||||
// Intel Alder Lake-PCH xHCI (0x51e0) — RESET_TO_DEFAULT.
|
||||
// Source: linux-7.1/drivers/usb/host/xhci-pci.c:398-402.
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x8086,
|
||||
device: 0x51e0,
|
||||
flags: XhciControllerQuirkFlags::RESET_TO_DEFAULT,
|
||||
},
|
||||
|
||||
// Intel Alder Lake-N PCH xHCI (0x54ee) — RESET_TO_DEFAULT.
|
||||
// Source: linux-7.1/drivers/usb/host/xhci-pci.c:398-402.
|
||||
XhciControllerQuirk {
|
||||
vendor: 0x8086,
|
||||
device: 0x54ee,
|
||||
flags: XhciControllerQuirkFlags::RESET_TO_DEFAULT,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
# xHCI controller quirk entries — Phase R7 (2026-06-07).
|
||||
# Source: Linux 7.1 drivers/usb/host/xhci-pci.c + drivers/usb/host/xhci.h
|
||||
#
|
||||
# Flag bit map (see XhciControllerQuirkFlags in redox-driver-sys):
|
||||
# bit 1: RESET_EP_QUIRK
|
||||
# bit 4: SPURIOUS_SUCCESS
|
||||
# bit 6: BROKEN_MSI
|
||||
# bit 7: RESET_ON_RESUME
|
||||
# bit 9: AVOID_BEI
|
||||
# bit 11: SLOW_SUSPEND
|
||||
# bit 13: SPURIOUS_REBOOT
|
||||
# bit 17: BROKEN_STREAMS
|
||||
# bit 19: BROKEN_STREAMS (alt)
|
||||
# bit 22: SSIC_PORT_UNUSED
|
||||
# bit 24: MISSING_CAS
|
||||
# bit 25: BROKEN_PORT_PED
|
||||
# bit 29: HW_LPM_DISABLE
|
||||
# bit 33: DEFAULT_PM_RUNTIME_ALLOW
|
||||
# bit 35: SNPS_BROKEN_SUSPEND
|
||||
# bit 38: DISABLE_SPARSE
|
||||
# bit 41: BROKEN_D3COLD_S2I
|
||||
# bit 44: RESET_TO_DEFAULT
|
||||
#
|
||||
# Compiled-in table is the canonical xHCI quirk source; this TOML overlay
|
||||
# allows OS-image integrators to add or override flags for specific
|
||||
# controller PCI IDs without recompiling redox-driver-sys.
|
||||
|
||||
# ============================================================================
|
||||
# INTEL Panther Point / C600 — disable per-port USB 3.0 warm reset.
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (INTEL_PANTHERPOINT)
|
||||
# Reason: PP has a hardware bug where warm port reset corrupts downstream
|
||||
# device state; BIOS workaround forces cold reset path.
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x1E31
|
||||
flags = ["broken_port_pec", "reset_to_default"]
|
||||
|
||||
# ============================================================================
|
||||
# INTEL Lynx Point / Wildcat Point
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (INTEL_LYNXPOINT)
|
||||
# Reason: xHCI port can return spurious successful status during reset.
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x8C31
|
||||
flags = ["spurious_success", "spurious_reboot"]
|
||||
|
||||
# ============================================================================
|
||||
# INTEL Sunrise Point / Cannon Lake / Ice Lake — disable MSI for stability.
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (INTEL_BROKEN_MSI)
|
||||
# Reason: interrupt masking on these chips intermittently fails under load.
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0xA12F
|
||||
flags = ["broken_msi"]
|
||||
|
||||
# ============================================================================
|
||||
# AMD Renoir / Yellow Carp — runtime PM quirks.
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (PCI_DEVICE_ID_AMD_RENOIR_XHCI)
|
||||
# Reason: device needs default_pm_runtime_allow + broken_suspend to
|
||||
# maintain USB functionality across suspend/resume cycles.
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x1639
|
||||
flags = ["default_pm_runtime_allow", "snps_broken_suspend"]
|
||||
|
||||
# ============================================================================
|
||||
# AMD Renoir 0x15E0 / 0x15E1 — Synopsys-based xHCI controllers.
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (PCI_DEVICE_ID_AMD_RENOIR_XHCI_2/3)
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x15E0
|
||||
flags = ["snps_broken_suspend"]
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x1022
|
||||
device = 0x15E1
|
||||
flags = ["snps_broken_suspend"]
|
||||
|
||||
# ============================================================================
|
||||
# Intel Tiger Lake-PCH / Alder Lake-PCH — device ID additions.
|
||||
# Linux: xhci-pci.c::xhci_pci_probe (INTEL_TGL, INTEL_ALDER_LAKE_PCH)
|
||||
# ============================================================================
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x9A13
|
||||
flags = ["reset_to_default"]
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x51E0
|
||||
flags = ["reset_to_default"]
|
||||
|
||||
[[xhci_controller_quirk]]
|
||||
vendor = 0x8086
|
||||
device = 0x54EE
|
||||
flags = ["reset_to_default"]
|
||||
@@ -1,6 +1,6 @@
|
||||
# USB mass storage device quirks mined from Linux 7.0 (drivers/usb/storage/unusual_devs.h).
|
||||
# USB mass storage device quirks mined from Linux 7.1 (drivers/usb/storage/unusual_devs.h).
|
||||
# Generated by local/scripts/extract-linux-quirks.py.
|
||||
# Extracted 214 entries with flags out of 319 total from unusual_devs.h.
|
||||
# Phase R9 (2026-06-07) — resynced against Linux 7.1. 214 entries with flags.
|
||||
#
|
||||
# These [[usb_storage_quirk]] entries are consumed by usbscsid at runtime.
|
||||
# The quirks module (drivers/storage/usbscsid/src/quirks.rs) reads this file
|
||||
@@ -1595,7 +1595,7 @@ flags = ["scm_mult_targ"]
|
||||
[[usb_storage_quirk]]
|
||||
vendor = 0x2109
|
||||
product = 0x0715
|
||||
revision = "9999-9999"
|
||||
revision = "0000-9999"
|
||||
manufacturer = "VIA Labs, Inc."
|
||||
description = "VL817 SATA Bridge"
|
||||
flags = ["ignore_uas"]
|
||||
|
||||
Reference in New Issue
Block a user