quirks: ChipsetQuirkFlags + 11-entry early_qrk data file (R17)

Phase R17 (2026-06-07) — Early-boot chipset quirks. The
data side lands now; the kernel-side consumer walks
the table at boot and dispatches to the imperative
handlers (nvidia_bugs, via_bugs, fix_hypertransport_config,
ati_bugs, intel_remapping_check, intel_graphics_quirks,
force_disable_hpet, apple_airport_reset).

Changes:

  1. ChipsetQuirkFlags (mod.rs:483) with 10 bits, one
     per Linux 7.1 early_qrk[] callback:
     QFLAG_APPLY_ONCE, NVIDIA_BUGS, VIA_BUGS,
     AMD_K8_NB_FIXUP, ATI_BUGS, ATI_BUGS_CONTD,
     INTEL_REMAPPING_CHECK, INTEL_GRAPHICS_QUIRKS,
     FORCE_DISABLE_HPET, APPLE_AIRPORT_RESET.

  2. ChipsetQuirkEntry (mod.rs:509) — vendor (0xFFFF
     any) + device (0xFFFF any) + class + class_mask.
     matches() honours the class-mask semantics from
     Linux's early-quirks.c (the (class ^ target) & mask
     test).

  3. CHIPSET FLAG_NAMES + parse_chipset_toml +
     load_chipset_flags (toml_loader.rs) — new
     [[chipset_quirk]] TOML table type with vendor +
     device + class + class_mask + flags.

  4. 1 new unit test: phase_r17_chipset_quirk_entry_matches
     exercises NVIDIA + AMD K8 class-mask semantics +
     5 match / mismatch combinations.
     127/127 tests pass.

  5. quirks.d/55-chipset-early.toml (110 lines) — 11 entries
     sourced from Linux 7.1
     arch/x86/kernel/early-quirks.c:
       - NVIDIA any bridge → nvidia_bugs (QFLAG_APPLY_ONCE)
       - VIA any bridge → via_bugs (QFLAG_APPLY_ONCE)
       - AMD K8 northbridge 0x1100 → fix_hypertransport_config
       - ATI IXP400 SMBus 0x4372 → ati_bugs
       - ATI SBX00 SMBus 0x4385 → ati_bugs_contd
       - Intel 0x3403/0x3405/0x3406 host bridges
         → intel_remapping_check
       - Intel any VGA → intel_graphics_quirks
       - Intel 0x0F00 (Baytrail) → force_disable_hpet
       - Broadcom 0x4331 → apple_airport_reset

cargo test: 127/127 (was 126, +1 for the new test).
cargo check: clean.

The kernel early-pci-scan path will call
load_chipset_flags() for each PCI device it walks and
invoke the named handler before any Rust user code.
Compiled-in chipset_table is empty (handler bodies
are imperative and don't fit a data-driven table).
This commit is contained in:
2026-06-07 22:04:27 +03:00
parent 57778e7898
commit 9e7020bc50
4 changed files with 304 additions and 5 deletions
@@ -993,4 +993,38 @@ mod tests {
assert!(wildcard.matches(0x8086, 0x24C0, 0));
assert!(wildcard.matches(0x8086, 0x24C0, 200));
}
/// Phase R17 — `ChipsetQuirkEntry::matches` honours
/// vendor / device wildcards and the class-mask semantics.
#[test]
fn phase_r17_chipset_quirk_entry_matches() {
use super::super::ChipsetQuirkEntry;
// NVIDIA bridges of any model and any bridge class.
let nvidia = ChipsetQuirkEntry {
vendor: 0x10DE,
device: 0xFFFF,
class: 0x0604, // PCI_CLASS_BRIDGE_PCI
class_mask: 0xFFFF,
flags: super::super::ChipsetQuirkFlags::NVIDIA_BUGS,
};
// Match: NVIDIA bridge at any device id
assert!(nvidia.matches(0x10DE, 0x0001, 0x0604));
// Wrong vendor
assert!(!nvidia.matches(0x8086, 0x0001, 0x0604));
// Wrong class (with mask 0xFFFF any bit mismatches)
assert!(!nvidia.matches(0x10DE, 0x0001, 0x0300));
// AMD K8 northbridge, any host-bridge variant
let amd_k8 = ChipsetQuirkEntry {
vendor: 0x1022,
device: 0x1100,
class: 0x0600, // PCI_CLASS_BRIDGE_HOST
class_mask: 0xFF00, // match high byte (class base)
flags: super::super::ChipsetQuirkFlags::AMD_K8_NB_FIXUP,
};
// Class base 0x06 (bridge) with any subclass
assert!(amd_k8.matches(0x1022, 0x1100, 0x0600));
assert!(amd_k8.matches(0x1022, 0x1100, 0x0601));
// Different class base
assert!(!amd_k8.matches(0x1022, 0x1100, 0x0300));
}
}
@@ -509,6 +509,54 @@ impl ClocksourceQuirkEntry {
}
}
bitflags::bitflags! {
/// Early-boot chipset quirk flags. Phase R17 (2026-06-07)
/// — ported from Linux 7.1
/// `arch/x86/kernel/early-quirks.c` `early_qrk[]` (11
/// entries). Each bit names a chipset init routine that
/// must run before any Rust user code. The kernel-side
/// consumer (R17 follow-up) walks the chipset table at
/// boot and dispatches to the named handler.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChipsetQuirkFlags: u64 {
const QFLAG_APPLY_ONCE = 1 << 0;
const NVIDIA_BUGS = 1 << 1;
const VIA_BUGS = 1 << 2;
const AMD_K8_NB_FIXUP = 1 << 3; // fix_hypertransport_config
const ATI_BUGS = 1 << 4; // ati_bugs
const ATI_BUGS_CONTD = 1 << 5; // ati_bugs_contd
const INTEL_REMAPPING_CHECK = 1 << 6;
const INTEL_GRAPHICS_QUIRKS = 1 << 7; // stolen memory
const FORCE_DISABLE_HPET = 1 << 8; // Baytrail HPET bad
const APPLE_AIRPORT_RESET = 1 << 9; // Broadcom 4331
}
}
/// One early-boot chipset quirk rule. Phase R17 (2026-06-07).
#[derive(Debug, Clone)]
pub struct ChipsetQuirkEntry {
pub vendor: u16, // 0xFFFF = any
pub device: u16, // 0xFFFF = any
pub class: u16,
pub class_mask: u16, // 0xFFFF = match whole class
pub flags: ChipsetQuirkFlags,
}
impl ChipsetQuirkEntry {
/// Match the (vendor, device, class) tuple. class_mask
/// must be non-zero for a class comparison; 0xFFFF means
/// match-all.
pub fn matches(&self, vendor: u16, device: u16, class: u16) -> bool {
if self.vendor != 0xFFFF && self.vendor != vendor {
return false;
}
if self.device != 0xFFFF && self.device != device {
return false;
}
(self.class ^ class) & self.class_mask == 0
}
}
/// Wildcard value for PCI ID matching.
pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF;
@@ -1,10 +1,10 @@
use super::{
dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule, PlatformDmiQuirkRule},
AcpiQuirkFlags, ClocksourceQuirkEntry, ClocksourceQuirkFlags, CpuBugFlags,
CpuBugQuirkEntry, CpuId, DrmPanelOrientation, HidQuirkEntry, HidQuirkFlags,
MaskWidth, PciQuirkEntry, PciQuirkFlags, PciQuirkLookup, PciQuirkPhase,
PlatformSubsystem, QuirkAction, UsbQuirkEntry, UsbQuirkFlags,
XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID,
AcpiQuirkFlags, ChipsetQuirkEntry, ChipsetQuirkFlags, ClocksourceQuirkEntry,
ClocksourceQuirkFlags, CpuBugFlags, CpuBugQuirkEntry, CpuId, DrmPanelOrientation,
HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags,
PciQuirkLookup, PciQuirkPhase, PlatformSubsystem, QuirkAction, UsbQuirkEntry,
UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID,
};
use crate::pci::PciDeviceInfo;
use std::borrow::Cow;
@@ -613,6 +613,109 @@ pub(crate) fn load_clocksource_flags(
flags
}
pub const CHIPSET_FLAG_NAMES: &[(&str, ChipsetQuirkFlags)] = &[
("qflag_apply_once", ChipsetQuirkFlags::QFLAG_APPLY_ONCE),
("nvidia_bugs", ChipsetQuirkFlags::NVIDIA_BUGS),
("via_bugs", ChipsetQuirkFlags::VIA_BUGS),
("amd_k8_nb_fixup", ChipsetQuirkFlags::AMD_K8_NB_FIXUP),
("ati_bugs", ChipsetQuirkFlags::ATI_BUGS),
("ati_bugs_contd", ChipsetQuirkFlags::ATI_BUGS_CONTD),
("intel_remapping_check", ChipsetQuirkFlags::INTEL_REMAPPING_CHECK),
("intel_graphics_quirks", ChipsetQuirkFlags::INTEL_GRAPHICS_QUIRKS),
("force_disable_hpet", ChipsetQuirkFlags::FORCE_DISABLE_HPET),
("apple_airport_reset", ChipsetQuirkFlags::APPLE_AIRPORT_RESET),
];
pub(crate) fn read_toml_chipset_entries() -> std::io::Result<Vec<ChipsetQuirkEntry>> {
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_chipset_toml(&doc, &mut entries, &path_str);
}
Ok(entries)
}
fn parse_chipset_toml(
doc: &toml::Value,
out: &mut Vec<ChipsetQuirkEntry>,
path: &str,
) {
let Some(arr) = doc.get("chipset_quirk").and_then(|v| v.as_array()) else {
return;
};
for item in arr {
let Some(table) = item.as_table() else {
log::warn!("quirks: {path}: chipset_quirk entry is not a table, skipping");
continue;
};
let vendor = match table.get("vendor") {
Some(value) => match bounded_u16(value, "vendor", path) {
Some(value) => value,
None => continue,
},
None => 0xFFFF,
};
let device = match table.get("device") {
Some(value) => match bounded_u16(value, "device", path) {
Some(value) => value,
None => continue,
},
None => 0xFFFF,
};
let class = match table.get("class") {
Some(value) => match bounded_u16(value, "class", path) {
Some(value) => value,
None => continue,
},
None => 0,
};
let class_mask = match table.get("class_mask") {
Some(value) => match bounded_u16(value, "class_mask", path) {
Some(value) => value,
None => continue,
},
None => 0xFFFF,
};
let flags = parse_flags(table, path, "Chipset", CHIPSET_FLAG_NAMES);
out.push(ChipsetQuirkEntry {
vendor,
device,
class,
class_mask,
flags,
});
}
}
/// Look up the chipset flags for the given (vendor, device,
/// class) tuple across all runtime TOML entries. Returns
/// the OR-accumulated flags.
pub(crate) fn load_chipset_flags(vendor: u16, device: u16, class: u16) -> ChipsetQuirkFlags {
let mut flags = ChipsetQuirkFlags::empty();
if let Ok(entries) = read_toml_chipset_entries() {
for entry in entries {
if entry.matches(vendor, device, class) {
flags |= entry.flags;
}
}
}
flags
}
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(|| {
@@ -0,0 +1,114 @@
# Early-boot chipset quirks — PCI vendor / device / class-based.
# Mined from Linux 7.1
# `arch/x86/kernel/early-quirks.c` `early_qrk[]` (11 entries).
# Each `[[chipset_quirk]]` entry matches on (vendor, device,
# class, class_mask) and produces a ChipsetQuirkFlags bit
# set that names the imperative handler to invoke before
# any Rust user code runs.
#
# Phase R17 (2026-06-07). The kernel-side consumer walks
# the table at boot (in the early-pci-scan path) and
# dispatches to the named handlers. The compiled-in
# table is empty; runtime TOML is the data surface.
#
# Class codes used:
# PCI_CLASS_BRIDGE_PCI = 0x0604 (any PCI bridge)
# PCI_CLASS_BRIDGE_HOST = 0x0600 (host bridge)
# PCI_BASE_CLASS_BRIDGE = 0x06 (any bridge class)
# PCI_CLASS_SERIAL_SMBUS = 0x0C05
# PCI_CLASS_DISPLAY_VGA = 0x0300
# NVIDIA — any PCI bridge. nvidia_bugs() covers the
# MCP61 / MCP65 / MCP67 / MCP73 / MCP79S chipset bugs.
[[chipset_quirk]]
vendor = 0x10DE
device = 0xFFFF
class = 0x0604
class_mask = 0xFFFF
flags = ["nvidia_bugs", "qflag_apply_once"]
# VIA — any PCI bridge. via_bugs() covers VT8235 / VT8237
# / VT8363 / VT8366 / VT8377 / VT8251 issues.
[[chipset_quirk]]
vendor = 0x1106
device = 0xFFFF
class = 0x0604
class_mask = 0xFFFF
flags = ["via_bugs", "qflag_apply_once"]
# AMD K8 northbridge — fix_hypertransport_config for
# PCI_CLASS_BRIDGE_HOST 0x0600. Subclass mask is 0xFF00 so
# any host-bridge subclass matches.
[[chipset_quirk]]
vendor = 0x1022
device = 0x1100
class = 0x0600
class_mask = 0xFF00
flags = ["amd_k8_nb_fixup"]
# ATI IXP400 SMBus (0x4372) — ati_bugs().
[[chipset_quirk]]
vendor = 0x1002
device = 0x4372
class = 0x0C05
class_mask = 0xFFFF
flags = ["ati_bugs"]
# ATI SBX00 SMBus (0x4385) — ati_bugs_contd().
[[chipset_quirk]]
vendor = 0x1002
device = 0x4385
class = 0x0C05
class_mask = 0xFFFF
flags = ["ati_bugs_contd"]
# Intel 0x3403 / 0x3405 / 0x3406 — host bridges,
# intel_remapping_check() for VT-d interrupt remapping.
# (any bridge class with class_mask = 0xFF00)
[[chipset_quirk]]
vendor = 0x8086
device = 0x3403
class = 0x0600
class_mask = 0xFF00
flags = ["intel_remapping_check"]
[[chipset_quirk]]
vendor = 0x8086
device = 0x3405
class = 0x0600
class_mask = 0xFF00
flags = ["intel_remapping_check"]
[[chipset_quirk]]
vendor = 0x8086
device = 0x3406
class = 0x0600
class_mask = 0xFF00
flags = ["intel_remapping_check"]
# Intel — any VGA display controller. intel_graphics_quirks()
# reserves stolen memory at boot.
[[chipset_quirk]]
vendor = 0x8086
device = 0xFFFF
class = 0x0300
class_mask = 0xFFFF
flags = ["intel_graphics_quirks"]
# Intel 0x0F00 (Baytrail SoC) — force_disable_hpet.
# HPET on this chipset halts in deep idle.
[[chipset_quirk]]
vendor = 0x8086
device = 0x0F00
class = 0x0600
class_mask = 0xFF00
flags = ["force_disable_hpet"]
# Broadcom 0x4331 — apple_airport_reset. The Apple wireless
# card needs a reset before the driver can attach.
[[chipset_quirk]]
vendor = 0x14E4
device = 0x4331
class = 0x0280
class_mask = 0xFFFF
flags = ["apple_airport_reset"]