diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs index 2b0bc5a775..30123e2dc8 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/dmi.rs @@ -960,4 +960,37 @@ mod tests { // Mismatch assert!(!cpuid.matches(0x06, 0x8E)); } + + /// Phase R15 — `ClocksourceQuirkEntry::matches` honours + /// vendor / device / revision wildcards. + #[test] + fn phase_r15_clocksource_quirk_entry_matches() { + use super::super::ClocksourceQuirkEntry; + // Range entry: vendor=0x8086, device=0x7110, revision in 0..=2 + let entry = ClocksourceQuirkEntry { + vendor: 0x8086, + device: 0x7110, + revision_lo: 0, + revision_hi: 2, + flags: super::super::ClocksourceQuirkFlags::PMTMR_BLACKLIST, + }; + assert!(entry.matches(0x8086, 0x7110, 0)); + assert!(entry.matches(0x8086, 0x7110, 2)); + // Out of range revision + assert!(!entry.matches(0x8086, 0x7110, 3)); + // Wrong vendor + assert!(!entry.matches(0x1022, 0x7110, 1)); + // Wrong device + assert!(!entry.matches(0x8086, 0x7111, 1)); + // Revision wildcard (0..=0xFF) + let wildcard = ClocksourceQuirkEntry { + vendor: 0x8086, + device: 0x24C0, + revision_lo: 0, + revision_hi: 0xFF, + flags: super::super::ClocksourceQuirkFlags::PMTMR_GRAYLIST, + }; + assert!(wildcard.matches(0x8086, 0x24C0, 0)); + assert!(wildcard.matches(0x8086, 0x24C0, 200)); + } } diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs index f76e81860a..47572ff2b3 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/mod.rs @@ -456,6 +456,59 @@ pub fn lookup_cpu_bug_flags(cpuid: &CpuId, vendor_id: u16) -> CpuBugFlags { flags } +bitflags::bitflags! { + /// Clocksource / timekeeping quirk flags. Phase R15 + /// (2026-06-07) — initial bit set covers the PMTMR + /// blacklist from Linux 7.1 + /// `drivers/clocksource/acpi_pm.c` (3 entries: Intel + /// PIIX4 rev<3 blacklisted, Intel ICH4 + ServerWorks + /// LE graylisted). TSC sync is handled algorithmically + /// in the kernel (mark_tsc_unstable), not by a data + /// table, so it is not represented here. + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub struct ClocksourceQuirkFlags: u64 { + /// The chipset is on the PMTMR blacklist; the + /// kernel should not use PMTMR as a clocksource. + const PMTMR_BLACKLIST = 1 << 0; + /// The chipset is on the PMTMR graylist; PMTMR + /// may be used with a degraded rating (120). + const PMTMR_GRAYLIST = 1 << 1; + /// The TSC on this CPU is known to be unstable. + const TSC_UNSTABLE = 1 << 2; + /// The HPET on this chipset is broken; the kernel + /// should not use HPET. + const HPET_BROKEN = 1 << 3; + } +} + +/// One PCI-device entry in the clocksource quirk table. +/// Phase R15 (2026-06-07). +#[derive(Debug, Clone)] +pub struct ClocksourceQuirkEntry { + pub vendor: u16, + pub device: u16, + pub revision_lo: u8, // inclusive; 0 means "any" + pub revision_hi: u8, // inclusive; 0xFF means "any" + pub flags: ClocksourceQuirkFlags, +} + +impl ClocksourceQuirkEntry { + /// Match against (vendor, device, revision). revision=0xFF + /// in the entry is a wildcard on revision. + pub fn matches(&self, vendor: u16, device: u16, revision: u8) -> bool { + if self.vendor != 0xFFFF && self.vendor != vendor { + return false; + } + if self.device != 0xFFFF && self.device != device { + return false; + } + if self.revision_lo == 0 && self.revision_hi == 0xFF { + return true; + } + revision >= self.revision_lo && revision <= self.revision_hi + } +} + /// Wildcard value for PCI ID matching. pub const PCI_QUIRK_ANY_ID: u16 = 0xFFFF; diff --git a/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs index cfeaed11b9..4defe9076f 100644 --- a/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs +++ b/local/recipes/drivers/redox-driver-sys/source/src/quirks/toml_loader.rs @@ -1,9 +1,10 @@ use super::{ dmi::{self, DmiAcpiQuirkRule, DmiDrmPanelQuirkRule, DmiInfo, DmiMatchRule, DmiPciQuirkRule, DmiXhciQuirkRule, PlatformDmiQuirkRule}, - AcpiQuirkFlags, CpuBugFlags, CpuBugQuirkEntry, CpuId, DrmPanelOrientation, - HidQuirkEntry, HidQuirkFlags, MaskWidth, PciQuirkEntry, PciQuirkFlags, - PciQuirkLookup, PciQuirkPhase, PlatformSubsystem, QuirkAction, UsbQuirkEntry, - UsbQuirkFlags, XhciControllerQuirk, XhciControllerQuirkFlags, PCI_QUIRK_ANY_ID, + AcpiQuirkFlags, 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; @@ -510,6 +511,108 @@ pub(crate) fn load_cpu_bug_flags( flags } +pub const CLOCKSOURCE_FLAG_NAMES: &[(&str, ClocksourceQuirkFlags)] = &[ + ("pmtmr_blacklist", ClocksourceQuirkFlags::PMTMR_BLACKLIST), + ("pmtmr_graylist", ClocksourceQuirkFlags::PMTMR_GRAYLIST), + ("tsc_unstable", ClocksourceQuirkFlags::TSC_UNSTABLE), + ("hpet_broken", ClocksourceQuirkFlags::HPET_BROKEN), +]; + +pub(crate) fn read_toml_clocksource_entries() +-> std::io::Result> { + 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::() { + Ok(d) => d, + Err(e) => { + log::warn!("quirks: failed to parse {path_str}: {e}"); + continue; + } + }; + parse_clocksource_toml(&doc, &mut entries, &path_str); + } + Ok(entries) +} + +fn parse_clocksource_toml( + doc: &toml::Value, + out: &mut Vec, + path: &str, +) { + let Some(arr) = doc.get("clocksource_quirk").and_then(|v| v.as_array()) else { + return; + }; + for item in arr { + let Some(table) = item.as_table() else { + log::warn!("quirks: {path}: clocksource_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 revision_lo = match table.get("revision_lo") { + Some(value) => match bounded_u8(value, "revision_lo", path) { + Some(value) => value, + None => continue, + }, + None => 0, + }; + let revision_hi = match table.get("revision_hi") { + Some(value) => match bounded_u8(value, "revision_hi", path) { + Some(value) => value, + None => continue, + }, + None => 0xFF, + }; + let flags = parse_flags(table, path, "Clocksource", CLOCKSOURCE_FLAG_NAMES); + out.push(ClocksourceQuirkEntry { + vendor, + device, + revision_lo, + revision_hi, + flags, + }); + } +} + +/// Look up the clocksource flags for the given PCI device +/// (vendor, device, revision) across all runtime TOML entries. +/// Returns the OR-accumulated flags. +pub(crate) fn load_clocksource_flags( + vendor: u16, + device: u16, + revision: u8, +) -> ClocksourceQuirkFlags { + let mut flags = ClocksourceQuirkFlags::empty(); + if let Ok(entries) = read_toml_clocksource_entries() { + for entry in entries { + if entry.matches(vendor, device, revision) { + flags |= entry.flags; + } + } + } + flags +} + fn bounded_u16(val: &toml::Value, field: &str, path: &str) -> Option { match val.as_integer() { Some(v) => u16::try_from(v).ok().or_else(|| { diff --git a/local/recipes/system/redbear-quirks/source/quirks.d/35-clocksource.toml b/local/recipes/system/redbear-quirks/source/quirks.d/35-clocksource.toml new file mode 100644 index 0000000000..6ca112198f --- /dev/null +++ b/local/recipes/system/redbear-quirks/source/quirks.d/35-clocksource.toml @@ -0,0 +1,42 @@ +# Clocksource / timekeeping quirks — PCI device-based. +# Mined from Linux 7.1 +# `drivers/clocksource/acpi_pm.c` `DECLARE_PCI_FIXUP_EARLY` +# entries (3 total): +# - Intel 82371AB_3 (PIIX4) — blacklisted +# - Intel 82801DB_0 (ICH4) — graylisted +# - ServerWorks LE — graylisted +# +# The PMTMR workaround switches the kernel's clocksource +# choice. `blacklist` means PMTMR is rejected; `graylist` +# means PMTMR is allowed at a downgraded rating (120). +# +# Phase R15 (2026-06-07). The compiled-in table is empty +# (`clocksource_table.rs`); runtime TOML is the data +# surface. TSC sync detection is algorithmic in the +# kernel (mark_tsc_unstable), not table-driven, so it +# is not represented here. + +# Intel 82371AB_3 (PIIX4) — blacklist, revisions 0..=2 +# "Has a bug so severe that it causes random time jumps +# of up to 5 seconds." (Linux comment) +[[clocksource_quirk]] +vendor = 0x8086 +device = 0x7113 +revision_lo = 0 +revision_hi = 2 +flags = ["pmtmr_blacklist"] + +# Intel 82801DB_0 (ICH4) — graylist +# "The chipset may have PM-Timer Bug. We can fall back to +# the slower read." (Linux comment) +[[clocksource_quirk]] +vendor = 0x8086 +device = 0x24C0 +flags = ["pmtmr_graylist"] + +# ServerWorks LE — graylist +# Same as ICH4; degraded PMTMR rating 120. +[[clocksource_quirk]] +vendor = 0x1166 +device = 0x0009 +flags = ["pmtmr_graylist"]