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:
@@ -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"]
|
||||
Reference in New Issue
Block a user