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:
@@ -478,9 +478,9 @@ data = """
|
|||||||
# redbear-live-mini: no display driver matched; class 0x03 devices are skipped
|
# redbear-live-mini: no display driver matched; class 0x03 devices are skipped
|
||||||
"""
|
"""
|
||||||
|
|
||||||
[[files]]
|
# INIT_SKIP removed — all daemons are now enabled for boot.
|
||||||
path = "/etc/environment.d/20-skip.conf"
|
# Individual hardware daemons that fail in QEMU will exit cleanly
|
||||||
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"
|
# rather than blocking boot via the oneshot_async service type.
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
path = "/etc/init.d/29_activate_console.service"
|
path = "/etc/init.d/29_activate_console.service"
|
||||||
|
|||||||
@@ -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<Option<String>> {
|
||||||
|
--- 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 {
|
||||||
@@ -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<T>(
|
||||||
|
f: impl FnOnce(PcieAllocs<'_>, Vec<InterruptMap>, [u32; 4]) -> io::Result<T>,
|
||||||
|
) -> io::Result<T> {
|
||||||
|
- 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<String> = 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()?,
|
||||||
@@ -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) {
|
||||||
@@ -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?
|
||||||
@@ -25,20 +25,20 @@
|
|||||||
+ if num { led_byte |= 2; }
|
+ if num { led_byte |= 2; }
|
||||||
+ if caps { led_byte |= 4; }
|
+ if caps { led_byte |= 4; }
|
||||||
+ if let Err(err) = self.keyboard_command_inner(0xED) {
|
+ 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;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ match self.read_timeout(DEFAULT_TIMEOUT) {
|
+ match self.read_timeout(DEFAULT_TIMEOUT) {
|
||||||
+ Ok(0xFA) => {
|
+ Ok(0xFA) => {
|
||||||
+ if let Err(err) = self.write(led_byte) {
|
+ 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) => {
|
+ 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) => {
|
+ Err(err) => {
|
||||||
+ warn!("ps2d: LED command ACK timeout: {:?}", err);
|
+ log::debug!("ps2d: LED command ACK timeout: {:?}", err);
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
|
|||||||
@@ -517,3 +517,46 @@ diff -ruwN source-old/src/corelib/CMakeLists.txt source/src/corelib/CMakeLists.t
|
|||||||
global/qfunctionpointer.h
|
global/qfunctionpointer.h
|
||||||
global/qgettid_p.h
|
global/qgettid_p.h
|
||||||
global/qglobal.cpp global/qglobal.h global/qglobal_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)
|
||||||
|
|||||||
@@ -338,6 +338,10 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration)
|
|||||||
void QWaylandDisplay::setupConnection()
|
void QWaylandDisplay::setupConnection()
|
||||||
{
|
{
|
||||||
struct ::wl_registry *registry = wl_display_get_registry(mDisplay);
|
struct ::wl_registry *registry = wl_display_get_registry(mDisplay);
|
||||||
|
if (!registry) {
|
||||||
|
qCritical("QWaylandDisplay: wl_display_get_registry() returned NULL");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
init(registry);
|
init(registry);
|
||||||
|
|
||||||
#if QT_CONFIG(xkbcommon)
|
#if QT_CONFIG(xkbcommon)
|
||||||
@@ -525,6 +529,13 @@ void QWaylandDisplay::reconnect()
|
|||||||
|
|
||||||
void QWaylandDisplay::flushRequests()
|
void QWaylandDisplay::flushRequests()
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_REDOX
|
||||||
|
if (!m_eventThread) {
|
||||||
|
wl_display_dispatch_pending(mDisplay);
|
||||||
|
wl_display_flush(mDisplay);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
m_eventThread->readAndDispatchEvents();
|
m_eventThread->readAndDispatchEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +544,11 @@ void QWaylandDisplay::flushRequests()
|
|||||||
// polling.
|
// polling.
|
||||||
void QWaylandDisplay::initEventThread()
|
void QWaylandDisplay::initEventThread()
|
||||||
{
|
{
|
||||||
|
#ifdef Q_OS_REDOX
|
||||||
|
if (!m_frameEventQueue)
|
||||||
|
m_frameEventQueue = wl_display_create_queue(mDisplay);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
m_eventThread.reset(
|
m_eventThread.reset(
|
||||||
new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch));
|
new EventThread(mDisplay, /* default queue */ nullptr, EventThread::EmitToDispatch));
|
||||||
connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this,
|
connect(m_eventThread.get(), &EventThread::needReadAndDispatch, this,
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ impl DriverParamsScheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
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}",
|
"driver-params: failed to read {}: {err}",
|
||||||
self.bound_path.display()
|
self.bound_path.display()
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -49,17 +49,25 @@ fn default_firmware_dir() -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(target_os = "redox")]
|
||||||
unsafe fn get_init_notify_fd() -> RawFd {
|
unsafe fn get_init_notify_fd() -> Option<RawFd> {
|
||||||
let fd: RawFd = env::var("INIT_NOTIFY")
|
let Ok(value) = env::var("INIT_NOTIFY") else {
|
||||||
.expect("firmware-loader: INIT_NOTIFY not set")
|
eprintln!("firmware-loader: INIT_NOTIFY not set; readiness notification disabled");
|
||||||
.parse()
|
return None;
|
||||||
.expect("firmware-loader: INIT_NOTIFY is not a valid fd");
|
};
|
||||||
|
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);
|
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
|
||||||
fd
|
Some(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
#[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
|
let cap_id = scheme
|
||||||
.scheme_root()
|
.scheme_root()
|
||||||
.expect("firmware-loader: scheme_root failed");
|
.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")]
|
#[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 socket = Socket::create().expect("firmware-loader: failed to create scheme socket");
|
||||||
let mut scheme = FirmwareScheme::new(registry);
|
let mut scheme = FirmwareScheme::new(registry);
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ drm_devices_ready() {
|
|||||||
|
|
||||||
wait_for_drm_devices() {
|
wait_for_drm_devices() {
|
||||||
local devices="${1:-}"
|
local devices="${1:-}"
|
||||||
local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-10}"
|
local wait_seconds="${REDBEAR_DRM_WAIT_SECONDS:-2}"
|
||||||
local attempts=0
|
local attempts=0
|
||||||
|
|
||||||
if [ -z "$devices" ]; then
|
if [ -z "$devices" ]; then
|
||||||
|
|||||||
@@ -209,7 +209,13 @@ launch_optional_component() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
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=$!
|
local pid=$!
|
||||||
optional_pids+=("$pid")
|
optional_pids+=("$pid")
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ int main(int argc, char *argv[]) {
|
|||||||
qputenv("QT_QUICK_BACKEND", QByteArrayLiteral("software"));
|
qputenv("QT_QUICK_BACKEND", QByteArrayLiteral("software"));
|
||||||
QQuickWindow::setGraphicsApi(QSGRendererInterface::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);
|
QGuiApplication app(argc, argv);
|
||||||
QQuickStyle::setStyle(QStringLiteral("Basic"));
|
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 thermal zone data from /scheme/acpi/thermal/
|
||||||
|
// Reads CPU temperature via MSR IA32_THERM_STATUS (0x19C)
|
||||||
// Provides /scheme/thermal for temperature queries
|
// 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::collections::BTreeMap;
|
||||||
use std::fs;
|
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 THERMAL_POLL_INTERVAL: Duration = Duration::from_secs(2);
|
||||||
const PASSIVE_HYSTERESIS_C: f64 = 2.0;
|
const PASSIVE_HYSTERESIS_C: f64 = 2.0;
|
||||||
const ACTIVE_MARGIN_C: f64 = 5.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 {
|
struct StderrLogger {
|
||||||
level: LevelFilter,
|
level: LevelFilter,
|
||||||
@@ -180,6 +188,53 @@ fn zone_name_for_entry(entry: &fs::DirEntry) -> Option<String> {
|
|||||||
entry.file_name().into_string().ok()
|
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)> {
|
fn discover_zone_dirs() -> Vec<(String, PathBuf)> {
|
||||||
let mut zones = Vec::new();
|
let mut zones = Vec::new();
|
||||||
let Ok(entries) = fs::read_dir(ACPI_THERMAL_ROOT) else {
|
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> {
|
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 =
|
let passive_threshold =
|
||||||
read_scalar(&dir, &["_PSV", "psv", "passive_threshold"]).map(normalize_temperature_celsius);
|
read_scalar(&dir, &["_PSV", "psv", "passive_threshold"]).map(normalize_temperature_celsius);
|
||||||
let critical_threshold =
|
let critical_threshold =
|
||||||
@@ -244,6 +304,15 @@ fn refresh_zones(previous: &[ZoneRuntime]) -> Vec<ZoneRuntime> {
|
|||||||
refreshed.push(zone);
|
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
|
refreshed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,8 @@ const WL_KEYBOARD_KEY: u16 = 3;
|
|||||||
|
|
||||||
const WL_OUTPUT_GEOMETRY: u16 = 0;
|
const WL_OUTPUT_GEOMETRY: u16 = 0;
|
||||||
const WL_OUTPUT_MODE: u16 = 1;
|
const WL_OUTPUT_MODE: u16 = 1;
|
||||||
|
const WL_OUTPUT_DONE: u16 = 2;
|
||||||
|
const WL_OUTPUT_SCALE: u16 = 3;
|
||||||
|
|
||||||
const WL_CALLBACK_DONE: u16 = 0;
|
const WL_CALLBACK_DONE: u16 = 0;
|
||||||
|
|
||||||
@@ -1022,13 +1024,38 @@ impl Compositor {
|
|||||||
push_i32(&mut msg, 60);
|
push_i32(&mut msg, 60);
|
||||||
let _ = stream.write_all(&msg);
|
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) {
|
fn send_seat_capabilities(&self, stream: &mut UnixStream, seat_id: u32) {
|
||||||
let mut msg = Vec::with_capacity(12);
|
// wl_seat.name (v2) — required by Qt6 to fully initialize the seat
|
||||||
push_header(&mut msg, seat_id, WL_SEAT_CAPABILITIES, 4);
|
{
|
||||||
push_u32(&mut msg, 0x3);
|
let mut payload = Vec::new();
|
||||||
let _ = stream.write_all(&msg);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ patches = [
|
|||||||
"P0-daemon-fix-init-notify-unwrap.patch",
|
"P0-daemon-fix-init-notify-unwrap.patch",
|
||||||
"P0-workspace-add-bootstrap.patch",
|
"P0-workspace-add-bootstrap.patch",
|
||||||
"P0-init-continuous-scheduling.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
|
# 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.
|
# (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
|
# 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-daemon-panic-hardening.patch
|
||||||
# P5-init-supervisor-restart.patch
|
# P5-init-supervisor-restart.patch
|
||||||
"P2-i2c-gpio-ucsi-drivers.patch",
|
"P2-i2c-gpio-ucsi-drivers.patch",
|
||||||
|
"P0-i2c-control-response-empty.patch",
|
||||||
"P2-ihdad-graceful-init.patch",
|
"P2-ihdad-graceful-init.patch",
|
||||||
"P9-fix-so-pecred.patch",
|
"P9-fix-so-pecred.patch",
|
||||||
"P3-inputd-keymap-bridge.patch",
|
"P3-inputd-keymap-bridge.patch",
|
||||||
"P3-ps2d-led-feedback.patch",
|
"P3-ps2d-led-feedback.patch",
|
||||||
"P3-ps2d-mouse-resend.patch",
|
"P3-ps2d-mouse-resend.patch",
|
||||||
|
"P0-ps2d-mouse-fastfail.patch",
|
||||||
"P3-usbhidd-hardening.patch",
|
"P3-usbhidd-hardening.patch",
|
||||||
"P3-init-colored-output.patch",
|
"P3-init-colored-output.patch",
|
||||||
"P4-logd-persistent-logging.patch",
|
"P4-logd-persistent-logging.patch",
|
||||||
|
|||||||
Reference in New Issue
Block a user