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:
2026-06-07 11:57:24 +03:00
parent 5e44191c90
commit b56b810c07
7 changed files with 1262 additions and 67 deletions
+161 -33
View File
@@ -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 (23 days)
### Phase R7: xHCI Closure (COMPLETED 2026-06-07)
Mine Linux's GPU driver device ID tables for comprehensive coverage:
Following the R1R6 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 R7R10" 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 R1R6 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.
**R1R6 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 (35 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"]