diff --git a/config/redbear-mini.toml b/config/redbear-mini.toml index fc66b7ea4..b5601840c 100644 --- a/config/redbear-mini.toml +++ b/config/redbear-mini.toml @@ -478,9 +478,9 @@ data = """ # redbear-live-mini: no display driver matched; class 0x03 devices are skipped """ -[[files]] -path = "/etc/environment.d/20-skip.conf" -data = "INIT_SKIP=gpiod,i2cd,dw-acpi-i2cd,i2c-gpio-expanderd,intel-gpiod,ucsid,redbear-wifictl,dbus-daemon,redbear-sessiond,redbear-polkit,redbear-udisks,redbear-upower,/usr/bin/cpufreqd,/usr/bin/driver-params" +# INIT_SKIP removed — all daemons are now enabled for boot. +# Individual hardware daemons that fail in QEMU will exit cleanly +# rather than blocking boot via the oneshot_async service type. [[files]] path = "/etc/init.d/29_activate_console.service" diff --git a/local/patches/base/P0-i2c-control-response-empty.patch b/local/patches/base/P0-i2c-control-response-empty.patch new file mode 100644 index 000000000..3afc8a33d --- /dev/null +++ b/local/patches/base/P0-i2c-control-response-empty.patch @@ -0,0 +1,30 @@ +--- a/drivers/usb/ucsid/src/main.rs ++++ b/drivers/usb/ucsid/src/main.rs +@@ -785,7 +785,11 @@ + .context("failed to read I2C control response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; +- ron::from_str(text).context("failed to decode I2C control response") ++ let trimmed = text.trim(); ++ if trimmed.is_empty() { ++ return Ok(I2cControlResponse::AdapterList(Vec::new())); ++ } ++ ron::from_str(trimmed).context("failed to decode I2C control response") + } + + fn read_symbol_id(path: &Path) -> Result> { +--- a/drivers/gpio/i2c-gpio-expanderd/src/main.rs ++++ b/drivers/gpio/i2c-gpio-expanderd/src/main.rs +@@ -429,7 +429,11 @@ + .context("failed to read I2C control response")?; + buffer.truncate(count); + let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?; +- ron::from_str(text).context("failed to decode I2C control response") ++ let trimmed = text.trim(); ++ if trimmed.is_empty() { ++ return Ok(I2cControlResponse::AdapterList(Vec::new())); ++ } ++ ron::from_str(trimmed).context("failed to decode I2C control response") + } + + fn eisa_id_from_integer(integer: u64) -> String { diff --git a/local/patches/base/P0-pcid-mcfg-diagnostics.patch b/local/patches/base/P0-pcid-mcfg-diagnostics.patch new file mode 100644 index 000000000..4eb4b1c55 --- /dev/null +++ b/local/patches/base/P0-pcid-mcfg-diagnostics.patch @@ -0,0 +1,148 @@ +--- a/drivers/pcid/src/cfg_access/mod.rs ++++ b/drivers/pcid/src/cfg_access/mod.rs +@@ -152,20 +152,22 @@ + fn with( + f: impl FnOnce(PcieAllocs<'_>, Vec, [u32; 4]) -> io::Result, + ) -> io::Result { +- let table_dir = fs::read_dir("/scheme/acpi/tables")?; ++ let table_dir = match fs::read_dir("/scheme/acpi/tables") { ++ Ok(dir) => dir, ++ Err(e) => { ++ log::debug!("pcid: cannot read /scheme/acpi/tables: {e} (acpid running?)"); ++ return Err(e); ++ } ++ }; + +- // TODO: validate/print MCFG? ++ let mut found_tables: Vec = Vec::new(); + + for table_direntry in table_dir { + let table_path = table_direntry?.path(); + +- // Every directory entry has to have a filename unless +- // the filesystem (or in this case acpid) misbehaves. +- // If it misbehaves we have worse problems than pcid +- // crashing. `as_encoded_bytes()` returns some superset +- // of ASCII, so directly comparing it with an ASCII name +- // is fine. + let table_filename = table_path.file_name().unwrap().as_encoded_bytes(); ++ found_tables.push(String::from_utf8_lossy(table_filename).into_owned()); ++ + if table_filename.get(0..4) == Some(&MCFG_NAME) { + let bytes = fs::read(table_path)?.into_boxed_slice(); + match Mcfg::parse(&*bytes) { +@@ -174,6 +176,7 @@ + return f(allocs, Vec::new(), [u32::MAX, u32::MAX, u32::MAX, u32::MAX]); + } + None => { ++ log::warn!("pcid: MCFG table found but failed to parse ({} bytes)", bytes.len()); + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "couldn't find mcfg table", +@@ -183,6 +186,12 @@ + } + } + ++ log::debug!( ++ "pcid: MCFG not found among {} ACPI table(s): {:?}", ++ found_tables.len(), ++ found_tables ++ ); ++ + Err(io::Error::new( + io::ErrorKind::NotFound, + "couldn't find mcfg table", +@@ -219,6 +228,24 @@ + pub interrupt_map_mask: [u32; 4], + fallback: Pci, + } ++ ++/// Validate an MCFG allocation entry address (cross-referenced with Linux ++/// `acpi_mcfg_valid_entry` in arch/x86/pci/mmconfig-shared.c). ++/// ++/// - Addresses below 4 GiB are valid for legacy PCIe ECAM. ++/// - Addresses at or above 4 GiB require the MCFG table revision >= 1, matching ++/// the ACPI 4.0+ rule that 64-bit ECAM addresses are only valid when the ++/// firmware signals support via the revision field. ++fn validate_mcfg_addr(addr: u64) -> Result<(), String> { ++ const FOUR_GIB: u64 = 0x1_0000_0000; ++ ++ if addr < FOUR_GIB { ++ return Ok(()); ++ } ++ ++ Err(format!("address {addr:#018x} >= 4 GiB requires MCFG revision >= 1 (ACPI 4.0+); entry skipped")) ++} ++ + struct Alloc { + seg: u16, + start_bus: u8, +@@ -239,9 +266,11 @@ + Ok(pcie) => pcie, + Err(fdt_error) => { + log::warn!( +- "Couldn't retrieve PCIe info, perhaps the kernel is not compiled with \ +- acpi or device tree support? Using the PCI 3.0 configuration space \ +- instead. ACPI error: {:?} FDT error: {:?}", ++ "PCIe (ECAM/MCFG) not available: {}. \ ++ Device tree ECAM also not found: {}. \ ++ Falling back to PCI 3.0 config space (I/O ports 0xCF8/0xCFC). \ ++ For PCI Express support, use QEMU Q35 machine type (-machine q35) \ ++ or ensure your platform firmware provides an MCFG ACPI table.", + acpi_error, + fdt_error + ); +@@ -266,24 +295,44 @@ + .0 + .iter() + .filter_map(|desc| { ++ if desc.seg_group_num != 0 { ++ let seg = desc.seg_group_num; ++ let start = desc.start_bus; ++ let end = desc.end_bus; ++ log::warn!( ++ "pcid: skipping MCFG entry at seg={seg} bus {start}..={end}: multi-segment not yet implemented", ++ ); ++ return None; ++ } ++ ++ if let Err(reason) = validate_mcfg_addr(desc.base_addr) { ++ let addr = desc.base_addr; ++ let seg = desc.seg_group_num; ++ let start = desc.start_bus; ++ let end = desc.end_bus; ++ log::warn!( ++ "pcid: skipping MCFG entry at {addr:#018x} (seg={seg} bus {start}..={end}): {reason}", ++ ); ++ return None; ++ } ++ ++ let seg = desc.seg_group_num; ++ let start = desc.start_bus; ++ let end = desc.end_bus; + Some(Alloc { +- seg: desc.seg_group_num, +- start_bus: desc.start_bus, +- end_bus: desc.end_bus, ++ seg, ++ start_bus: start, ++ end_bus: end, + mem: PhysBorrowed::map( + desc.base_addr.try_into().ok()?, + BYTES_PER_BUS +- * (usize::from(desc.end_bus) - usize::from(desc.start_bus) + 1), ++ * (usize::from(end) - usize::from(start) + 1), + Prot::RW, + MemoryType::Uncacheable, + ) + .inspect_err(|err| { + log::error!( +- "failed to map seg {} bus {}..={}: {}", +- { desc.seg_group_num }, +- { desc.start_bus }, +- { desc.end_bus }, +- err ++ "failed to map seg {seg} bus {start}..={end}: {err}", + ) + }) + .ok()?, diff --git a/local/patches/base/P0-procmgr-sigchld-debug.patch b/local/patches/base/P0-procmgr-sigchld-debug.patch new file mode 100644 index 000000000..4e6e17400 --- /dev/null +++ b/local/patches/base/P0-procmgr-sigchld-debug.patch @@ -0,0 +1,15 @@ +diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs +index ce37d57..b1268e0 100644 +--- a/bootstrap/src/procmgr.rs ++++ b/bootstrap/src/procmgr.rs +@@ -1701,7 +1701,9 @@ impl<'a> ProcScheme<'a> { + false, // stop_or_continue + awoken, + ) { +- log::error!("failed to send SIGCHLD to parent PID {ppid:?}: {err}"); ++ // EPERM on SIGCHLD to PID 1 is a known kernel limitation. ++ // The procmgr continues correctly after this; downgrade to debug. ++ log::debug!("failed to send SIGCHLD to parent PID {ppid:?}: {err}"); + } + + if let Some(init_rc) = self.processes.get(&INIT_PID) { diff --git a/local/patches/base/P0-ps2d-mouse-fastfail.patch b/local/patches/base/P0-ps2d-mouse-fastfail.patch new file mode 100644 index 000000000..8a7dca489 --- /dev/null +++ b/local/patches/base/P0-ps2d-mouse-fastfail.patch @@ -0,0 +1,42 @@ +--- a/drivers/input/ps2d/src/mouse.rs ++++ b/drivers/input/ps2d/src/mouse.rs +@@ -1,8 +1,8 @@ + use crate::controller::Ps2; + use std::time::Duration; + +-pub const RESET_RETRIES: usize = 10; +-pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000); ++pub const RESET_RETRIES: usize = 3; ++pub const RESET_TIMEOUT: Duration = Duration::from_millis(250); + pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100); + + #[derive(Clone, Copy, Debug)] +@@ -260,7 +264,8 @@ + MouseResult::Timeout(COMMAND_TIMEOUT) + } else { + log::warn!("unknown mouse response {:02X} after reset", data); +- self.reset(ps2) ++ *self = MouseState::None; ++ MouseResult::None + } + } + MouseState::Bat => { +@@ -352,12 +357,14 @@ + self.reset(ps2) + } + MouseState::Reset => { +- log::warn!("timeout waiting for mouse reset"); +- self.reset(ps2) ++ log::debug!("timeout waiting for mouse reset, fast-failing"); ++ *self = MouseState::None; ++ MouseResult::None + } + MouseState::Bat => { +- log::warn!("timeout waiting for BAT completion"); +- self.reset(ps2) ++ log::debug!("timeout waiting for BAT completion, fast-failing"); ++ *self = MouseState::None; ++ MouseResult::None + } + MouseState::IdentifyTouchpad { .. } => { + //TODO: retry? diff --git a/local/patches/base/P3-ps2d-led-feedback.patch b/local/patches/base/P3-ps2d-led-feedback.patch index b83be9e8f..6490cccb7 100644 --- a/local/patches/base/P3-ps2d-led-feedback.patch +++ b/local/patches/base/P3-ps2d-led-feedback.patch @@ -25,20 +25,20 @@ + if num { led_byte |= 2; } + if caps { led_byte |= 4; } + if let Err(err) = self.keyboard_command_inner(0xED) { -+ warn!("ps2d: failed to send LED command 0xED: {:?}", err); ++ log::debug!("ps2d: LED command 0xED not supported: {:?}", err); + return; + } + match self.read_timeout(DEFAULT_TIMEOUT) { + Ok(0xFA) => { + if let Err(err) = self.write(led_byte) { -+ warn!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err); ++ log::debug!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err); + } + } + Ok(val) => { -+ warn!("ps2d: LED command ACK expected 0xFA, got {:02X}", val); ++ log::debug!("ps2d: LED command ACK expected 0xFA, got {:02X}", val); + } + Err(err) => { -+ warn!("ps2d: LED command ACK timeout: {:?}", err); ++ log::debug!("ps2d: LED command ACK timeout: {:?}", err); + } + } + } diff --git a/local/recipes/qt/qtbase/redox.patch b/local/recipes/qt/qtbase/redox.patch index 4fa340a8e..4988847d1 100644 --- a/local/recipes/qt/qtbase/redox.patch +++ b/local/recipes/qt/qtbase/redox.patch @@ -517,3 +517,46 @@ diff -ruwN source-old/src/corelib/CMakeLists.txt source/src/corelib/CMakeLists.t global/qfunctionpointer.h global/qgettid_p.h global/qglobal.cpp global/qglobal.h global/qglobal_p.h +diff -ruwN source-old/src/plugins/platforms/wayland/qwaylanddisplay.cpp source/src/plugins/platforms/wayland/qwaylanddisplay.cpp +--- source-old/src/plugins/platforms/wayland/qwaylanddisplay.cpp 2025-05-27 09:11:00.000000000 +0000 ++++ source/src/plugins/platforms/wayland/qwaylanddisplay.cpp 2026-05-06 00:00:00.000000000 +0000 +@@ -526,16 +526,29 @@ + void QWaylandDisplay::flushRequests() + { ++#ifdef Q_OS_REDOX ++ if (!m_eventThread) { ++ wl_display_dispatch_pending(mDisplay); ++ wl_display_flush(mDisplay); ++ return; ++ } ++#endif + m_eventThread->readAndDispatchEvents(); + } + + // We have to wait until we have an eventDispatcher before creating the eventThread, + // otherwise forceRoundTrip() may block inside _events_read() because eventThread is + // polling. + void QWaylandDisplay::initEventThread() + { ++#ifdef Q_OS_REDOX ++ if (!m_frameEventQueue) ++ m_frameEventQueue = wl_display_create_queue(mDisplay); ++ return; ++#endif + m_eventThread.reset( + new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); + connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, ++diff -ruwN source-old/src/plugins/platforms/wayland/qwaylanddisplay.cpp source/src/plugins/platforms/wayland/qwaylanddisplay.cpp ++--- source-old/src/plugins/platforms/wayland/qwaylanddisplay.cpp 2025-05-27 09:11:00.000000000 +0000 +++++ source/src/plugins/platforms/wayland/qwaylanddisplay.cpp 2026-05-06 00:00:00.000000000 +0000 ++@@ -338,7 +338,12 @@ ++ void QWaylandDisplay::setupConnection() ++ { ++ struct ::wl_registry *registry = wl_display_get_registry(mDisplay); +++ if (!registry) { +++ qCritical("QWaylandDisplay: wl_display_get_registry() returned NULL"); +++ _exit(1); +++ } ++ init(registry); ++ ++ #if QT_CONFIG(xkbcommon) diff --git a/local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 79ad39876..c6fa63350 100644 --- a/local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/local/recipes/qt/qtbase/source/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -338,6 +338,10 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) void QWaylandDisplay::setupConnection() { struct ::wl_registry *registry = wl_display_get_registry(mDisplay); + if (!registry) { + qCritical("QWaylandDisplay: wl_display_get_registry() returned NULL"); + _exit(1); + } init(registry); #if QT_CONFIG(xkbcommon) @@ -525,6 +529,13 @@ void QWaylandDisplay::reconnect() void QWaylandDisplay::flushRequests() { +#ifdef Q_OS_REDOX + if (!m_eventThread) { + wl_display_dispatch_pending(mDisplay); + wl_display_flush(mDisplay); + return; + } +#endif m_eventThread->readAndDispatchEvents(); } @@ -533,6 +544,11 @@ void QWaylandDisplay::flushRequests() // polling. void QWaylandDisplay::initEventThread() { +#ifdef Q_OS_REDOX + if (!m_frameEventQueue) + m_frameEventQueue = wl_display_create_queue(mDisplay); + return; +#endif m_eventThread.reset( new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch)); connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this, diff --git a/local/recipes/system/driver-params/source/src/main.rs b/local/recipes/system/driver-params/source/src/main.rs index ed3e53840..891c59b67 100644 --- a/local/recipes/system/driver-params/source/src/main.rs +++ b/local/recipes/system/driver-params/source/src/main.rs @@ -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() ), diff --git a/local/recipes/system/firmware-loader/source/src/main.rs b/local/recipes/system/firmware-loader/source/src/main.rs index 49d90eb29..df8355543 100644 --- a/local/recipes/system/firmware-loader/source/src/main.rs +++ b/local/recipes/system/firmware-loader/source/src/main.rs @@ -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 { + 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::() 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, 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, registry: FirmwareRegistry) -> ! { let socket = Socket::create().expect("firmware-loader: failed to create scheme socket"); let mut scheme = FirmwareScheme::new(registry); diff --git a/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor b/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor index 8865edbd6..6686f2455 100755 --- a/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor +++ b/local/recipes/system/redbear-greeter/source/redbear-greeter-compositor @@ -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 diff --git a/local/recipes/system/redbear-greeter/source/redbear-kde-session b/local/recipes/system/redbear-greeter/source/redbear-kde-session index 02cb391e7..5ed5c18cd 100755 --- a/local/recipes/system/redbear-greeter/source/redbear-kde-session +++ b/local/recipes/system/redbear-greeter/source/redbear-kde-session @@ -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") diff --git a/local/recipes/system/redbear-greeter/source/ui/main.cpp b/local/recipes/system/redbear-greeter/source/ui/main.cpp index 77a9c70ae..1508e04b6 100644 --- a/local/recipes/system/redbear-greeter/source/ui/main.cpp +++ b/local/recipes/system/redbear-greeter/source/ui/main.cpp @@ -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")); diff --git a/local/recipes/system/thermald/source/src/main.rs b/local/recipes/system/thermald/source/src/main.rs index f3f3b67e7..7a3824987 100644 --- a/local/recipes/system/thermald/source/src/main.rs +++ b/local/recipes/system/thermald/source/src/main.rs @@ -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 { entry.file_name().into_string().ok() } +fn read_msr(cpu: u32, msr: u32) -> Option { + 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::().is_ok() { + count += 1; + } + } + } + } + count.max(1) +} + +fn read_cpu_temperature(cpu: u32) -> Option { + 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 { - 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::().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 { 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 } diff --git a/local/recipes/wayland/redbear-compositor/source/src/main.rs b/local/recipes/wayland/redbear-compositor/source/src/main.rs index 36ecf3d5d..620d4c943 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/main.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/main.rs @@ -219,6 +219,8 @@ const WL_KEYBOARD_KEY: u16 = 3; const WL_OUTPUT_GEOMETRY: u16 = 0; const WL_OUTPUT_MODE: u16 = 1; +const WL_OUTPUT_DONE: u16 = 2; +const WL_OUTPUT_SCALE: u16 = 3; const WL_CALLBACK_DONE: u16 = 0; @@ -1022,13 +1024,38 @@ impl Compositor { push_i32(&mut msg, 60); let _ = stream.write_all(&msg); } + // wl_output.scale and wl_output.done are required for wl_output v2+ clients to + // treat the output as fully initialized. + { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, output_id, WL_OUTPUT_SCALE, 4); + push_i32(&mut msg, 1); + let _ = stream.write_all(&msg); + } + { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, output_id, WL_OUTPUT_DONE, 0); + let _ = stream.write_all(&msg); + } } fn send_seat_capabilities(&self, stream: &mut UnixStream, seat_id: u32) { - let mut msg = Vec::with_capacity(12); - push_header(&mut msg, seat_id, WL_SEAT_CAPABILITIES, 4); - push_u32(&mut msg, 0x3); - let _ = stream.write_all(&msg); + // wl_seat.name (v2) — required by Qt6 to fully initialize the seat + { + let mut payload = Vec::new(); + push_wayland_string(&mut payload, "seat0"); + let mut msg = Vec::with_capacity(8 + payload.len()); + push_header(&mut msg, seat_id, 1, payload.len()); // opcode 1 = wl_seat.name + msg.extend_from_slice(&payload); + let _ = stream.write_all(&msg); + } + // wl_seat.capabilities — advertise pointer so Qt creates a wl_pointer proxy + { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, seat_id, WL_SEAT_CAPABILITIES, 4); + push_u32(&mut msg, 0x1); // WL_SEAT_CAPABILITY_POINTER + let _ = stream.write_all(&msg); + } } } diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 9415462ad..da07a73a4 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -5,6 +5,9 @@ patches = [ "P0-daemon-fix-init-notify-unwrap.patch", "P0-workspace-add-bootstrap.patch", "P0-init-continuous-scheduling.patch", + "P0-dhcpd-auto-iface.patch", + "P0-procmgr-sigchld-debug.patch", + "P0-pcid-mcfg-diagnostics.patch", # TODO: P6-e1000d-msi-migration.patch conflicts with P6-driver-main-fixes # (both touch e1000d/src/main.rs). Merge into P6-driver-main-fixes on rebase. # TODO: P1 patches (11) exist in local/patches/base/ but need rebase @@ -24,11 +27,13 @@ patches = [ # P5-init-daemon-panic-hardening.patch # P5-init-supervisor-restart.patch "P2-i2c-gpio-ucsi-drivers.patch", + "P0-i2c-control-response-empty.patch", "P2-ihdad-graceful-init.patch", "P9-fix-so-pecred.patch", "P3-inputd-keymap-bridge.patch", "P3-ps2d-led-feedback.patch", "P3-ps2d-mouse-resend.patch", + "P0-ps2d-mouse-fastfail.patch", "P3-usbhidd-hardening.patch", "P3-init-colored-output.patch", "P4-logd-persistent-logging.patch",