fix: comprehensive boot hardening — crashes, warnings, sensors, bare-metal PS/2

- firmware-loader: handle missing INIT_NOTIFY gracefully (Option<RawFd>)
- driver-params: suppress ENODEV (19) on missing driver-manager scheme
- compositor: add wl_seat.name + pointer capabilities for Qt6 compat
- greeter: use redox QPA (libqredox.so) instead of broken Qt6 Wayland
- greeter: reduce DRM wait 10s→2s, kded6 offscreen QPA to avoid crash
- thermald: add CPU die temperature via MSR IA32_THERM_STATUS (Linux coretemp)
- pcid: diagnostic MCFG warning with Q35 guidance, address validation
- dhcpd: auto-interface detection (P0-dhcpd-auto-iface.patch wired)
- procmgr: SIGCHLD EPERM→debug (kernel limitation, not a bug)
- ps2d LED: warn→debug on modern hw without real PS/2 controller
- ps2d mouse: 10×1s→3×250ms retry, fast-fail on unknown response
- i2c RON: handle empty response from i2cd when no adapters present
- base recipe: wire 6 new/improved patches
- config: remove INIT_SKIP, enable all 14 previously-suppressed daemons
This commit is contained in:
2026-05-06 11:16:18 +01:00
parent 1cdb6d4d99
commit ff5a132a9d
16 changed files with 441 additions and 24 deletions
@@ -112,7 +112,8 @@ impl DriverParamsScheme {
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => log::warn!(
Err(err) if err.raw_os_error() == Some(19) => {} // ENODEV: scheme not present
Err(err) => log::debug!(
"driver-params: failed to read {}: {err}",
self.bound_path.display()
),
@@ -49,17 +49,25 @@ fn default_firmware_dir() -> PathBuf {
}
#[cfg(target_os = "redox")]
unsafe fn get_init_notify_fd() -> RawFd {
let fd: RawFd = env::var("INIT_NOTIFY")
.expect("firmware-loader: INIT_NOTIFY not set")
.parse()
.expect("firmware-loader: INIT_NOTIFY is not a valid fd");
unsafe fn get_init_notify_fd() -> Option<RawFd> {
let Ok(value) = env::var("INIT_NOTIFY") else {
eprintln!("firmware-loader: INIT_NOTIFY not set; readiness notification disabled");
return None;
};
let Ok(fd) = value.parse::<RawFd>() else {
eprintln!("firmware-loader: INIT_NOTIFY is not a valid fd; readiness notification disabled");
return None;
};
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
fd
Some(fd)
}
#[cfg(target_os = "redox")]
fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareScheme) {
fn notify_scheme_ready(notify_fd: Option<RawFd>, socket: &Socket, scheme: &mut FirmwareScheme) {
let Some(notify_fd) = notify_fd else {
info!("firmware-loader: no INIT_NOTIFY fd, skipping readiness notification");
return;
};
let cap_id = scheme
.scheme_root()
.expect("firmware-loader: scheme_root failed");
@@ -77,7 +85,7 @@ fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut FirmwareS
}
#[cfg(target_os = "redox")]
fn run_daemon(notify_fd: RawFd, registry: FirmwareRegistry) -> ! {
fn run_daemon(notify_fd: Option<RawFd>, registry: FirmwareRegistry) -> ! {
let socket = Socket::create().expect("firmware-loader: failed to create scheme socket");
let mut scheme = FirmwareScheme::new(registry);
@@ -47,7 +47,7 @@ drm_devices_ready() {
wait_for_drm_devices() {
local devices="${1:-}"
local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-10}"
local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-2}"
local attempts=0
if [ -z "$devices" ]; then
@@ -209,7 +209,13 @@ launch_optional_component() {
return 0
fi
"$program" &
# kded6 is a D-Bus daemon with no GUI requirement.
# Force offscreen QPA to avoid the Qt6 Wayland page-fault crash at null+8.
if [ "$program" = "kded6" ]; then
QT_QPA_PLATFORM=offscreen "$program" &
else
"$program" &
fi
local pid=$!
optional_pids+=("$pid")
@@ -12,6 +12,13 @@ int main(int argc, char *argv[]) {
qputenv("QT_QUICK_BACKEND", QByteArrayLiteral("software"));
QQuickWindow::setGraphicsApi(QSGRendererInterface::Software);
// Use the Redox-native QPA plugin (libqredox.so) instead of Wayland.
// The Qt6 Wayland plugin crashes at null+8 during wl_registry init on Redox.
// The redox QPA plugin renders directly to the framebuffer via scheme:display.
if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) {
qputenv("QT_QPA_PLATFORM", "redox");
}
QGuiApplication app(argc, argv);
QQuickStyle::setStyle(QStringLiteral("Basic"));
@@ -1,6 +1,11 @@
// thermald — ACPI thermal zone manager
// thermald — ACPI thermal zone manager + CPU die temperature
// Reads thermal zone data from /scheme/acpi/thermal/
// Reads CPU temperature via MSR IA32_THERM_STATUS (0x19C)
// Provides /scheme/thermal for temperature queries
//
// Cross-referenced with Linux drivers/hwmon/coretemp.c:
// MSR 0x19C bits 16-22 = digital readout (offset from TjMax)
// MSR 0x1A2 bits 16-23 = temperature target (TjMax)
use std::collections::BTreeMap;
use std::fs;
@@ -34,6 +39,9 @@ const CPUFREQ_GOVERNOR_PATHS: [&str; 2] = ["/scheme/cpufreq/governor", "/scheme/
const THERMAL_POLL_INTERVAL: Duration = Duration::from_secs(2);
const PASSIVE_HYSTERESIS_C: f64 = 2.0;
const ACTIVE_MARGIN_C: f64 = 5.0;
const MSR_THERM_STATUS: u32 = 0x19C;
const MSR_TEMPERATURE_TARGET: u32 = 0x1A2;
const TJMAX_DEFAULT_C: f64 = 100.0;
struct StderrLogger {
level: LevelFilter,
@@ -180,6 +188,53 @@ fn zone_name_for_entry(entry: &fs::DirEntry) -> Option<String> {
entry.file_name().into_string().ok()
}
fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
let path = format!("/dev/cpu/{cpu}/msr");
let mut file = fs::File::open(&path).ok()?;
let mut buf = [0u8; 8];
use std::io::{Read, Seek, SeekFrom};
file.seek(SeekFrom::Start(msr as u64)).ok()?;
file.read_exact(&mut buf).ok()?;
Some(u64::from_le_bytes(buf))
}
fn cpu_count() -> u32 {
let mut count = 0u32;
if let Ok(entries) = fs::read_dir("/dev/cpu") {
for entry in entries.filter_map(Result::ok) {
if let Ok(name) = entry.file_name().into_string() {
if name.parse::<u32>().is_ok() {
count += 1;
}
}
}
}
count.max(1)
}
fn read_cpu_temperature(cpu: u32) -> Option<f64> {
let tjmax = read_msr(cpu, MSR_TEMPERATURE_TARGET)
.map(|v| ((v >> 16) & 0xFF) as f64)
.unwrap_or(TJMAX_DEFAULT_C);
let status = read_msr(cpu, MSR_THERM_STATUS)?;
if status & (1 << 31) == 0 {
return None;
}
let digital_readout = ((status >> 16) & 0x7F) as f64;
Some(tjmax - digital_readout)
}
fn discover_cpu_zones() -> Vec<(String, PathBuf)> {
let mut zones = Vec::new();
let count = cpu_count();
for cpu in 0..count {
if read_cpu_temperature(cpu).is_some() {
zones.push((format!("cpu{cpu}"), PathBuf::from(format!("/dev/cpu/{cpu}/msr"))));
}
}
zones
}
fn discover_zone_dirs() -> Vec<(String, PathBuf)> {
let mut zones = Vec::new();
let Ok(entries) = fs::read_dir(ACPI_THERMAL_ROOT) else {
@@ -207,7 +262,12 @@ fn discover_zone_dirs() -> Vec<(String, PathBuf)> {
}
fn read_zone_runtime(name: String, dir: PathBuf, previous: Option<&ZoneRuntime>) -> Option<ZoneRuntime> {
let temperature = normalize_temperature_celsius(read_scalar(&dir, &["_TMP", "tmp", "temperature"])?);
let temperature = if name.starts_with("cpu") {
let cpu = name.strip_prefix("cpu")?.parse::<u32>().ok()?;
read_cpu_temperature(cpu)?
} else {
normalize_temperature_celsius(read_scalar(&dir, &["_TMP", "tmp", "temperature"])?)
};
let passive_threshold =
read_scalar(&dir, &["_PSV", "psv", "passive_threshold"]).map(normalize_temperature_celsius);
let critical_threshold =
@@ -244,6 +304,15 @@ fn refresh_zones(previous: &[ZoneRuntime]) -> Vec<ZoneRuntime> {
refreshed.push(zone);
}
}
for (name, dir) in discover_cpu_zones() {
if refreshed.iter().any(|z| z.zone.name == name) {
continue;
}
let previous_zone = previous_by_name.get(name.as_str()).copied();
if let Some(zone) = read_zone_runtime(name, dir, previous_zone) {
refreshed.push(zone);
}
}
refreshed
}