diff --git a/config/redbear-live-mini.toml b/config/redbear-live-mini.toml index c60a5ade..5f907405 100644 --- a/config/redbear-live-mini.toml +++ b/config/redbear-live-mini.toml @@ -5,8 +5,9 @@ # # Target contract: # - keep a text-login live/recovery surface only +# - use boot framebuffer for VT text consoles via vesad + fbcond # - avoid the shared firmware/input/device-service stack from redbear-minimal -# - ship no graphics packages/services and no linux-firmware payload +# - ship no linux-firmware payload include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml"] @@ -34,66 +35,13 @@ redbear-info = {} # Keep package builder utility in live environment. cub = {} +# VT/getty/login chain: initfs starts inputd + vesad + fbcond in phase 1, +# then minimal.toml legacy 30_console runs inputd -A 2 + getty 2 + getty debug. + [[files]] path = "/etc/netctl/active" data = "wired-dhcp\n" -[[files]] -path = "/usr/lib/init.d/30_console" -data = """ -requires_weak 10_net -inputd -A 2 -nowait getty 2 -nowait getty /scheme/debug/no-preserve -J -""" - -[[files]] -path = "/usr/lib/init.d/29_activate_console.service" -data = """ -[unit] -description = "Activate console VT" -requires_weak = [ - "10_net.target", -] - -[service] -cmd = "inputd" -args = ["-A", "2"] -type = "oneshot" -""" - -[[files]] -path = "/usr/lib/init.d/30_console.service" -data = """ -[unit] -description = "Console terminals" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["2"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/lib/init.d/31_debug_console.service" -data = """ -[unit] -description = "Debug console" -requires_weak = [ - "10_net.target", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -respawn = true -""" - [[files]] path = "/etc/init.d/10_smolnetd.service" data = """ @@ -147,22 +95,6 @@ cmd = "audiod" type = "oneshot_async" """ -[[files]] -path = "/etc/init.d/01_debug_console.service" -data = """ -[unit] -description = "Debug serial login" -requires_weak = [ - "00_ptyd.service", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -respawn = true -""" - [[files]] path = "/etc/init.d/02_serial_probe.service" data = """ @@ -283,10 +215,5 @@ data = """ [[files]] path = "/etc/pcid.d/00_text_mode_gpu_mask.toml" data = """ -# redbear-live-mini: force text-only mode by consuming all display-class PCI devices -# with a no-op command, before any graphics-capable driver rules are evaluated. -[[drivers]] -name = "Text-only live-mini display mask" -class = 0x03 -command = ["/bin/true"] +# redbear-live-mini: no display driver matched; class 0x03 devices are skipped """ diff --git a/config/redbear-minimal.toml b/config/redbear-minimal.toml index 9c1691ac..26a54410 100644 --- a/config/redbear-minimal.toml +++ b/config/redbear-minimal.toml @@ -48,59 +48,7 @@ redbear-info = {} path = "/etc/netctl/active" data = "wired-dhcp\n" -# minimal.toml: "inputd -A 2", "nowait getty 2", "nowait getty /scheme/debug/no-preserve -J" -[[files]] -path = "/usr/lib/init.d/30_console" -data = """ -requires_weak 10_net -inputd -A 2 -nowait getty 2 -nowait getty /scheme/debug/no-preserve -J -""" - -[[files]] -path = "/usr/lib/init.d/29_activate_console.service" -data = """ -[unit] -description = "Activate console VT" -requires_weak = [ - "10_net.target", -] - -[service] -cmd = "inputd" -args = ["-A", "2"] -type = "oneshot" -""" - -[[files]] -path = "/usr/lib/init.d/30_console.service" -data = """ -[unit] -description = "Console terminals" -requires_weak = [ - "29_activate_console.service", -] - -[service] -cmd = "getty" -args = ["2"] -type = "oneshot_async" -respawn = true -""" - -[[files]] -path = "/usr/lib/init.d/31_debug_console.service" -data = """ -[unit] -description = "Debug console" -requires_weak = [ - "10_net.target", -] - -[service] -cmd = "getty" -args = ["/scheme/debug/no-preserve", "-J"] -type = "oneshot_async" -respawn = true -""" +# VT/getty/login chain is handled by the combination of: +# 1. initfs (phase 1): inputd daemon, vesad, fbcond — register input/display/fbcon schemes +# 2. minimal.toml legacy 30_console: inputd -A 2 + nowait getty 2 + nowait getty /scheme/debug +# No additional rootfs service files needed — initfs + legacy script covers the full chain. diff --git a/local/patches/base/P2-acpi-defer-aml.patch b/local/patches/base/P2-acpi-defer-aml.patch new file mode 100644 index 00000000..942861aa --- /dev/null +++ b/local/patches/base/P2-acpi-defer-aml.patch @@ -0,0 +1,46 @@ +Defer AML initialization until PCI registration completes. + +When acpid starts before pcid has registered the PCI fd, AML +initialization fails with a misleading ERROR-level message. This is +expected on every boot because the service ordering requires acpid to +start before pcid-spawner. The AML interpreter initializes successfully +after pcid registers via /scheme/acpi/register_pci. + +Changes: +- aml_context_mut(): log at DEBUG instead of ERROR when PCI fd is None + (expected pre-registration state, not a fault) +- Fadt::init(): skip \\_S5 evaluation when PCI is not yet registered, + since refresh_s5_values() is retried in register_pci_fd() after PCI + registration completes + +diff -urN a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs +--- a/drivers/acpid/src/acpi.rs ++++ b/drivers/acpid/src/acpi.rs +@@ -896,7 +896,11 @@ + match self.init(pci_fd) { + Ok(()) => (), + Err(err) => { +- log::error!("failed to initialize AML context: {}", err); ++ if pci_fd.is_none() { ++ log::debug!("AML init deferred until PCI registration: {}", err); ++ } else { ++ log::error!("failed to initialize AML context: {}", err); ++ } + } + } + } +@@ -2004,8 +2008,12 @@ + + context.tables.push(dsdt_sdt); + +- if let Err(error) = context.refresh_s5_values() { +- log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); ++ if context.pci_ready() { ++ if let Err(error) = context.refresh_s5_values() { ++ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); ++ } ++ } else { ++ log::debug!("Deferring \\_S5 evaluation until PCI registration"); + } + } + } diff --git a/local/patches/base/P2-boot-logging.patch b/local/patches/base/P2-boot-logging.patch new file mode 100644 index 00000000..9f422843 --- /dev/null +++ b/local/patches/base/P2-boot-logging.patch @@ -0,0 +1,308 @@ +diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs +index 82ec2bd0..a531edd9 100644 +--- a/drivers/common/src/logger.rs ++++ b/drivers/common/src/logger.rs +@@ -44,6 +44,7 @@ pub fn setup_logging( + Ok(b) => { + logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build()) + } ++ Err(error) if error.raw_os_error() == Some(19) => {} + Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error), + } + +@@ -61,6 +62,7 @@ pub fn setup_logging( + .build(), + ) + } ++ Err(error) if error.raw_os_error() == Some(19) => {} + Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error), + } + +diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs +index a968f4d4..a39b2af8 100644 +--- a/drivers/pcid-spawner/src/main.rs ++++ b/drivers/pcid-spawner/src/main.rs +@@ -1,11 +1,40 @@ ++use std::env; + use std::fs; + use std::process::Command; ++use std::thread; + + use anyhow::{anyhow, Context, Result}; + + use pcid_interface::config::Config; + use pcid_interface::PciFunctionHandle; + ++fn strict_usb_boot() -> bool { ++ matches!( ++ env::var("REDBEAR_STRICT_USB_BOOT") ++ .ok() ++ .as_deref() ++ .map(str::to_ascii_lowercase) ++ .as_deref(), ++ Some("1" | "true" | "yes" | "on") ++ ) ++} ++ ++fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool { ++ if !initfs { ++ return false; ++ } ++ ++ if class == 0x01 { ++ return false; ++ } ++ ++ if strict_usb_boot && class == 0x0C && subclass == 0x03 { ++ return false; ++ } ++ ++ true ++} ++ + fn main() -> Result<()> { + let mut args = pico_args::Arguments::from_env(); + let initfs = args.contains("--initfs"); +@@ -30,6 +59,12 @@ fn main() -> Result<()> { + } + + let config: Config = toml::from_str(&config_data)?; ++ let strict_usb_boot = strict_usb_boot(); ++ ++ log::info!( ++ "pcid-spawner: starting (initfs={}, strict_usb_boot={})", ++ initfs, strict_usb_boot ++ ); + + for entry in fs::read_dir("/scheme/pci")? { + let entry = entry.context("failed to get entry")?; +@@ -55,10 +90,11 @@ fn main() -> Result<()> { + }; + + let full_device_id = handle.config().func.full_device_id; ++ let device_addr = handle.config().func.addr; + + log::debug!( + "pcid-spawner enumerated: PCI {} {}", +- handle.config().func.addr, ++ device_addr, + full_device_id.display() + ); + +@@ -67,7 +103,7 @@ fn main() -> Result<()> { + .iter() + .find(|driver| driver.match_function(&full_device_id)) + else { +- log::debug!("no driver for {}, continuing", handle.config().func.addr); ++ log::debug!("no driver for {}, continuing", device_addr); + continue; + }; + +@@ -85,16 +121,93 @@ fn main() -> Result<()> { + let mut command = Command::new(program); + command.args(args); + +- log::info!("pcid-spawner: spawn {:?}", command); +- +- handle.enable_device(); ++ log::info!( ++ "pcid-spawner: matched {} to driver {:?}", ++ device_addr, ++ driver.command ++ ); ++ log::info!("pcid-spawner: enabling {} before spawn", device_addr); ++ ++ if let Err(err) = handle.try_enable_device() { ++ log::error!( ++ "pcid-spawner: failed to enable {} before spawn: {}", ++ device_addr, ++ err ++ ); ++ continue; ++ } + + let channel_fd = handle.into_inner_fd(); + command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); + ++ log::info!("pcid-spawner: spawn {:?}", command); + #[allow(deprecated, reason = "we can't yet move this to init")] +- daemon::Daemon::spawn(command); +- syscall::close(channel_fd as usize).unwrap(); ++ if should_detach_in_initfs( ++ initfs, ++ full_device_id.class, ++ full_device_id.subclass, ++ strict_usb_boot, ++ ) { ++ log::warn!( ++ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot", ++ device_addr ++ ); ++ ++ let device_addr = device_addr.to_string(); ++ thread::spawn(move || { ++ #[allow(deprecated, reason = "we can't yet move this to init")] ++ if let Err(err) = daemon::Daemon::spawn(command) { ++ log::error!( ++ "pcid-spawner: spawn/readiness failed for {}: {}", ++ device_addr, ++ err ++ ); ++ log::error!( ++ "pcid-spawner: {} remains enabled without a confirmed ready driver", ++ device_addr ++ ); ++ } ++ if let Err(err) = syscall::close(channel_fd as usize) { ++ log::error!( ++ "pcid-spawner: failed to close channel fd {} for {}: {}", ++ channel_fd, ++ device_addr, ++ err ++ ); ++ } ++ }); ++ } else { ++ log::info!( ++ "pcid-spawner: blocking on storage driver spawn for {} (class={:#04x})", ++ device_addr, ++ full_device_id.class ++ ); ++ #[allow(deprecated, reason = "we can't yet move this to init")] ++ if let Err(err) = daemon::Daemon::spawn(command) { ++ log::error!( ++ "pcid-spawner: spawn/readiness failed for {}: {}", ++ device_addr, ++ err ++ ); ++ log::error!( ++ "pcid-spawner: {} remains enabled without a confirmed ready driver", ++ device_addr ++ ); ++ } else { ++ log::info!( ++ "pcid-spawner: storage driver ready for {}", ++ device_addr ++ ); ++ } ++ if let Err(err) = syscall::close(channel_fd as usize) { ++ log::error!( ++ "pcid-spawner: failed to close channel fd {} for {}: {}", ++ channel_fd, ++ device_addr, ++ err ++ ); ++ } ++ } + } + + Ok(()) +diff --git a/init/src/main.rs b/init/src/main.rs +index 5682cf44..ed436619 100644 +--- a/init/src/main.rs ++++ b/init/src/main.rs +@@ -117,6 +117,8 @@ fn main() { + let mut unit_store = UnitStore::new(); + let mut scheduler = Scheduler::new(); + ++ eprintln!("init: phase 1 — initfs boot"); ++ + switch_root( + &mut unit_store, + &mut init_config, +@@ -125,6 +127,7 @@ fn main() { + ); + + // Start logd first such that we can pass /scheme/log as stdio to all other services ++ eprintln!("init: starting logd"); + scheduler + .schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned())); + scheduler.step(&mut unit_store, &mut init_config); +@@ -132,14 +135,18 @@ fn main() { + eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); + } + ++ eprintln!("init: starting runtime target"); + let runtime_target = UnitId("00_runtime.target".to_owned()); + scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); + unit_store.set_runtime_target(runtime_target); + ++ eprintln!("init: starting initfs drivers target"); + scheduler + .schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned())); + scheduler.step(&mut unit_store, &mut init_config); ++ eprintln!("init: initfs drivers target step() complete"); + ++ eprintln!("init: phase 2 — switchroot to /usr"); + switch_root( + &mut unit_store, + &mut init_config, +@@ -162,23 +169,64 @@ fn main() { + .collect::>() + .join(", ") + ); +- return; ++ Vec::new() + } + }; ++ eprintln!("init: scheduling {} rootfs units", entries.len()); + for entry in entries { ++ let name = match entry.file_name().and_then(|n| n.to_str()) { ++ Some(name) => name, ++ None => { ++ eprintln!("init: skipping config entry with non-UTF-8 filename"); ++ continue; ++ } ++ }; + scheduler.schedule_start_and_report_errors( + &mut unit_store, +- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), ++ UnitId(name.to_owned()), + ); + } + }; + + scheduler.step(&mut unit_store, &mut init_config); ++ eprintln!("init: phase 3 — rootfs services started"); ++ ++ if let Err(err) = libredox::call::setrens(0, 0) { ++ eprintln!("init: failed to enter null namespace: {err}"); ++ } + +- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); ++ eprintln!("init: boot complete — entering waitpid loop"); ++ ++ let mut respawn_map: BTreeMap = BTreeMap::new(); ++ for (unit_id, pid) in scheduler.respawn_pids { ++ respawn_map.insert(pid, unit_id); ++ } + + loop { + let mut status = 0; +- libredox::call::waitpid(0, &mut status, 0).unwrap(); ++ match libredox::call::waitpid(0, &mut status, 0) { ++ Ok(pid) => { ++ if let Some(unit_id) = respawn_map.remove(&(pid as u32)) { ++ eprintln!("init: respawning {} (pid {} exited)", unit_id.0, pid); ++ let mut resp_scheduler = Scheduler::new(); ++ resp_scheduler.schedule_start_and_report_errors( ++ &mut unit_store, ++ unit_id.clone(), ++ ); ++ resp_scheduler.step(&mut unit_store, &mut init_config); ++ for (uid, new_pid) in resp_scheduler.respawn_pids { ++ respawn_map.insert(new_pid, uid); ++ } ++ } ++ } ++ Err(err) => { ++ // EAGAIN is normal (no child exited yet). Other errors are ++ // unexpected but init must never crash — log and continue. ++ if err.errno() != syscall::EAGAIN { ++ eprintln!("init: waitpid error: {err}"); ++ } ++ std::thread::sleep(std::time::Duration::from_millis(100)); ++ } ++ } + } + } diff --git a/local/patches/base/redox.patch b/local/patches/base/redox.patch index 834dde9b..9261f43d 100644 --- a/local/patches/base/redox.patch +++ b/local/patches/base/redox.patch @@ -1,8 +1,29 @@ diff --git a/Cargo.lock b/Cargo.lock -index 9934cd8f..b3923c52 100644 +index 9fcbd662..b4ea6b1d 100644 --- a/Cargo.lock +++ b/Cargo.lock -@@ -54,6 +54,7 @@ dependencies = [ +@@ -31,11 +31,20 @@ dependencies = [ + "spinning_top", + ] + ++[[package]] ++name = "acpi-resource" ++version = "0.0.1" ++dependencies = [ ++ "serde", ++ "thiserror 2.0.18", ++] ++ + [[package]] + name = "acpid" + version = "0.1.0" + dependencies = [ + "acpi", ++ "acpi-resource", + "amlserde", + "arrayvec", + "common", +@@ -54,6 +63,7 @@ dependencies = [ "scheme-utils", "serde", "thiserror 2.0.18", @@ -10,17 +31,335 @@ index 9934cd8f..b3923c52 100644 ] [[package]] +@@ -80,6 +90,23 @@ dependencies = [ + "memchr 2.8.0", + ] + ++[[package]] ++name = "amd-mp2-i2cd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "pcid", ++ "redox_syscall 0.7.4", ++ "ron", ++ "serde", ++] ++ + [[package]] + name = "amlserde" + version = "0.0.1" +@@ -584,6 +611,7 @@ dependencies = [ + "inputd", + "libredox", + "log", ++ "nom", + "redox-ioctl", + "redox-scheme", + "redox_syscall 0.7.4", +@@ -642,6 +670,22 @@ dependencies = [ + "linux-raw-sys 0.9.4", + ] + ++[[package]] ++name = "dw-acpi-i2cd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "redox_syscall 0.7.4", ++ "ron", ++ "serde", ++] ++ + [[package]] + name = "e1000d" + version = "0.1.0" +@@ -909,6 +953,22 @@ dependencies = [ + "wasip3", + ] + ++[[package]] ++name = "gpiod" ++version = "0.1.0" ++dependencies = [ ++ "anyhow", ++ "common", ++ "daemon", ++ "libredox", ++ "log", ++ "redox-scheme", ++ "redox_syscall 0.7.4", ++ "ron", ++ "scheme-utils", ++ "serde", ++] ++ + [[package]] + name = "gpt" + version = "3.1.0" +@@ -1004,6 +1064,68 @@ dependencies = [ + "ron", + ] + ++[[package]] ++name = "i2c-gpio-expanderd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "redox_syscall 0.7.4", ++ "ron", ++ "serde", ++] ++ ++[[package]] ++name = "i2c-hidd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "amlserde", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "inputd", ++ "libredox", ++ "log", ++ "orbclient", ++ "redox-scheme", ++ "redox_syscall 0.7.4", ++ "ron", ++ "scheme-utils", ++ "serde", ++] ++ ++[[package]] ++name = "i2c-interface" ++version = "0.1.0" ++dependencies = [ ++ "redox_syscall 0.7.4", ++ "serde", ++] ++ ++[[package]] ++name = "i2cd" ++version = "0.1.0" ++dependencies = [ ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "redox-scheme", ++ "redox_syscall 0.7.4", ++ "ron", ++ "scheme-utils", ++ "serde", ++] ++ + [[package]] + name = "iana-time-zone" + version = "0.1.65" +@@ -1128,6 +1250,58 @@ dependencies = [ + "scheme-utils", + ] + ++[[package]] ++name = "intel-gpiod" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "libredox", ++ "log", ++ "redox_syscall 0.7.4", ++ "ron", ++ "serde", ++] ++ ++[[package]] ++name = "intel-lpss-i2cd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "redox_syscall 0.7.4", ++ "ron", ++ "serde", ++] ++ ++[[package]] ++name = "intel-thc-hidd" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "amlserde", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "pci_types", ++ "pcid", ++ "redox-scheme", ++ "redox_syscall 0.7.4", ++ "ron", ++ "scheme-utils", ++ "serde", ++] ++ + [[package]] + name = "ioslice" + version = "0.6.0" +@@ -2390,6 +2564,24 @@ version = "1.19.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + ++[[package]] ++name = "ucsid" ++version = "0.1.0" ++dependencies = [ ++ "acpi-resource", ++ "anyhow", ++ "common", ++ "daemon", ++ "i2c-interface", ++ "libredox", ++ "log", ++ "redox-scheme", ++ "redox_syscall 0.7.4", ++ "ron", ++ "scheme-utils", ++ "serde", ++] ++ + [[package]] + name = "unicode-ident" + version = "1.0.24" +diff --git a/Cargo.toml b/Cargo.toml +index 9e776232..36d87870 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -20,8 +20,17 @@ members = [ + "drivers/common", + "drivers/executor", + ++ "drivers/acpi-resource", + "drivers/acpid", ++ "drivers/gpio/gpiod", ++ "drivers/gpio/i2c-gpio-expanderd", ++ "drivers/gpio/intel-gpiod", + "drivers/hwd", ++ "drivers/i2c/amd-mp2-i2cd", ++ "drivers/i2c/dw-acpi-i2cd", ++ "drivers/i2c/i2c-interface", ++ "drivers/i2c/i2cd", ++ "drivers/i2c/intel-lpss-i2cd", + "drivers/pcid", + "drivers/pcid-spawner", + "drivers/rtcd", +@@ -43,6 +52,8 @@ members = [ + "drivers/graphics/virtio-gpud", + + "drivers/input/ps2d", ++ "drivers/input/i2c-hidd", ++ "drivers/input/intel-thc-hidd", + "drivers/input/usbhidd", + + "drivers/net/driver-network", +@@ -63,6 +74,7 @@ members = [ + "drivers/storage/usbscsid", + "drivers/storage/virtio-blkd", + ++ "drivers/usb/ucsid", + "drivers/usb/xhcid", + "drivers/usb/usbctl", + "drivers/usb/usbhubd", +@@ -81,6 +93,7 @@ drm = "0.15.0" + drm-sys = "0.8.1" + edid = "0.3.0" #TODO: edid is abandoned, fork it and maintain? + fdt = "0.1.5" ++nom = "3.2.0" # transitive dep via edid; needed to match on IResult variants + libc = "0.2.181" + log = "0.4" + libredox = "0.1.16" diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs -index 9f507221..f74fe715 100644 +index 9f507221..a0ba9d88 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs -@@ -57,25 +57,28 @@ impl Daemon { +@@ -11,12 +11,23 @@ use redox_scheme::Socket; + use redox_scheme::scheme::{SchemeAsync, SchemeSync}; + + unsafe fn get_fd(var: &str) -> RawFd { +- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); ++ let fd: RawFd = match std::env::var(var) ++ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}")) ++ .ok() ++ .and_then(|val| { ++ val.parse() ++ .map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}")) ++ .ok() ++ }) { ++ Some(fd) => fd, ++ None => return -1, ++ }; + if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 { +- panic!( ++ eprintln!( + "daemon: failed to set CLOEXEC flag for {var} fd: {}", + io::Error::last_os_error() + ); ++ return -1; + } + fd + } +@@ -51,31 +62,40 @@ impl Daemon { + + /// Notify the process that the daemon is ready to accept requests. + pub fn ready(mut self) { +- self.write_pipe.write_all(&[0]).unwrap(); ++ if let Err(err) = self.write_pipe.write_all(&[0]) { ++ if err.kind() != io::ErrorKind::BrokenPipe { ++ eprintln!("daemon::ready write failed: {err}"); ++ } ++ } + } + /// Executes `Command` as a child process. // FIXME remove once the service spawning of hwd and pcid-spawner is moved to init #[deprecated] - pub fn spawn(mut cmd: Command) { +- let (mut read_pipe, write_pipe) = io::pipe().unwrap(); + pub fn spawn(mut cmd: Command) -> io::Result<()> { - let (mut read_pipe, write_pipe) = io::pipe().unwrap(); ++ let (mut read_pipe, write_pipe) = io::pipe().map_err(|err| { ++ io::Error::new(err.kind(), format!("daemon: failed to create readiness pipe: {err}")) ++ })?; unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) }; @@ -54,10 +393,18 @@ index 9f507221..f74fe715 100644 } } diff --git a/drivers/acpid/Cargo.toml b/drivers/acpid/Cargo.toml -index 2d22a8f9..f03a4ccb 100644 +index 2d22a8f9..712b6d6e 100644 --- a/drivers/acpid/Cargo.toml +++ b/drivers/acpid/Cargo.toml -@@ -21,6 +21,7 @@ rustc-hash = "1.1.0" +@@ -8,6 +8,7 @@ edition = "2018" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + [dependencies] ++acpi-resource = { path = "../acpi-resource" } + acpi = { git = "https://github.com/jackpot51/acpi.git" } + arrayvec = "0.7.6" + log.workspace = true +@@ -21,6 +22,7 @@ rustc-hash = "1.1.0" thiserror.workspace = true ron.workspace = true serde.workspace = true @@ -66,7 +413,7 @@ index 2d22a8f9..f03a4ccb 100644 amlserde = { path = "../amlserde" } common = { path = "../common" } diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs -index 94a1eb17..dad44d1d 100644 +index 94a1eb17..a7cde5d6 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -1,13 +1,15 @@ @@ -763,7 +1110,20 @@ index 94a1eb17..dad44d1d 100644 self.aml_context = Some(interpreter); Ok(()) } -@@ -316,7 +928,7 @@ impl AmlSymbols { +@@ -284,7 +896,11 @@ impl AmlSymbols { + match self.init(pci_fd) { + Ok(()) => (), + Err(err) => { +- log::error!("failed to initialize AML context: {}", err); ++ if pci_fd.is_none() { ++ log::debug!("AML init deferred until PCI registration: {}", err); ++ } else { ++ log::error!("failed to initialize AML context: {}", err); ++ } + } + } + } +@@ -316,7 +932,7 @@ impl AmlSymbols { .namespace .lock() .traverse(|level_aml_name, level| { @@ -772,7 +1132,7 @@ index 94a1eb17..dad44d1d 100644 if let Ok(aml_name) = AmlName::from_name_seg(child_seg.to_owned()).resolve(level_aml_name) { -@@ -343,7 +955,18 @@ impl AmlSymbols { +@@ -343,7 +959,18 @@ impl AmlSymbols { for (aml_name, name) in &symbol_list { // create an empty entry, in case something goes wrong with serialization symbol_cache.insert(name.to_owned(), "".to_owned()); @@ -792,7 +1152,7 @@ index 94a1eb17..dad44d1d 100644 if let Ok(ser_string) = ron::ser::to_string_pretty(&ser_value, Default::default()) { // replace the empty entry symbol_cache.insert(name.to_owned(), ser_string); -@@ -368,6 +991,10 @@ pub enum AmlEvalError { +@@ -368,6 +995,10 @@ pub enum AmlEvalError { DeserializationError, #[error("AML not initialized")] NotInitialized, @@ -803,7 +1163,7 @@ index 94a1eb17..dad44d1d 100644 } impl From for AmlEvalError { fn from(value: AmlError) -> Self { -@@ -375,10 +1002,169 @@ impl From for AmlEvalError { +@@ -375,10 +1006,169 @@ impl From for AmlEvalError { } } @@ -973,7 +1333,7 @@ index 94a1eb17..dad44d1d 100644 aml_symbols: RwLock, -@@ -397,7 +1183,8 @@ impl AcpiContext { +@@ -397,7 +1187,8 @@ impl AcpiContext { args: Vec, ) -> Result { let mut symbols = self.aml_symbols.write(); @@ -983,7 +1343,7 @@ index 94a1eb17..dad44d1d 100644 interpreter.acquire_global_lock(16)?; let args = args -@@ -410,43 +1197,120 @@ impl AcpiContext { +@@ -410,43 +1201,120 @@ impl AcpiContext { }) .collect::, AmlEvalError>>()?; @@ -1123,7 +1483,7 @@ index 94a1eb17..dad44d1d 100644 next_ctx: RwLock::new(0), -@@ -458,7 +1322,8 @@ impl AcpiContext { +@@ -458,7 +1326,8 @@ impl AcpiContext { } Fadt::init(&mut this); @@ -1133,7 +1493,7 @@ index 94a1eb17..dad44d1d 100644 this } -@@ -525,18 +1390,143 @@ impl AcpiContext { +@@ -525,18 +1394,143 @@ impl AcpiContext { self.sdt_order.write().push(Some(*signature)); } @@ -1282,7 +1642,7 @@ index 94a1eb17..dad44d1d 100644 // return the cached value if it exists let symbols = self.aml_symbols.read(); if !symbols.symbols_cache().is_empty() { -@@ -550,7 +1540,7 @@ impl AcpiContext { +@@ -550,7 +1544,7 @@ impl AcpiContext { let mut aml_symbols = self.aml_symbols.write(); @@ -1291,7 +1651,7 @@ index 94a1eb17..dad44d1d 100644 // return the cached value Ok(RwLockWriteGuard::downgrade(aml_symbols)) -@@ -562,95 +1552,223 @@ impl AcpiContext { +@@ -562,95 +1556,223 @@ impl AcpiContext { aml_symbols.symbol_cache = FxHashMap::default(); } @@ -1586,7 +1946,7 @@ index 94a1eb17..dad44d1d 100644 } } -@@ -707,7 +1825,7 @@ unsafe impl plain::Plain for FadtStruct {} +@@ -707,7 +1829,7 @@ unsafe impl plain::Plain for FadtStruct {} #[repr(C, packed)] #[derive(Clone, Copy, Debug, Default)] @@ -1595,7 +1955,7 @@ index 94a1eb17..dad44d1d 100644 address_space: u8, bit_width: u8, bit_offset: u8, -@@ -715,11 +1833,68 @@ pub struct GenericAddressStructure { +@@ -715,11 +1837,68 @@ pub struct GenericAddressStructure { address: u64, } @@ -1665,7 +2025,7 @@ index 94a1eb17..dad44d1d 100644 pub reset_value: u8, reserved3: [u8; 3], -@@ -728,14 +1903,14 @@ pub struct FadtAcpi2Struct { +@@ -728,14 +1907,14 @@ pub struct FadtAcpi2Struct { pub x_firmware_control: u64, pub x_dsdt: u64, @@ -1688,7 +2048,7 @@ index 94a1eb17..dad44d1d 100644 } unsafe impl plain::Plain for FadtAcpi2Struct {} -@@ -774,9 +1949,10 @@ impl Fadt { +@@ -774,9 +1953,10 @@ impl Fadt { } pub fn init(context: &mut AcpiContext) { @@ -1702,7 +2062,7 @@ index 94a1eb17..dad44d1d 100644 let fadt = match Fadt::new(fadt_sdt) { Some(fadt) => fadt, -@@ -793,9 +1969,25 @@ impl Fadt { +@@ -793,9 +1973,25 @@ impl Fadt { None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), }; @@ -1730,7 +2090,7 @@ index 94a1eb17..dad44d1d 100644 Ok(dsdt) => dsdt, Err(error) => { log::error!("Failed to load DSDT: {}", error); -@@ -805,8 +1997,16 @@ impl Fadt { +@@ -805,8 +2001,20 @@ impl Fadt { context.fadt = Some(fadt.clone()); context.dsdt = Some(Dsdt(dsdt_sdt.clone())); @@ -1741,8 +2101,12 @@ index 94a1eb17..dad44d1d 100644 context.tables.push(dsdt_sdt); + -+ if let Err(error) = context.refresh_s5_values() { -+ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); ++ if context.pci_ready() { ++ if let Err(error) = context.refresh_s5_values() { ++ log::warn!("Failed to evaluate \\_S5 during FADT init: {error}"); ++ } ++ } else { ++ log::debug!("Deferring \\_S5 evaluation until PCI registration"); + } } } @@ -2272,10 +2636,10 @@ index c322790a..99842586 100644 } } diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs -index 059254b3..e388cd46 100644 +index 059254b3..3b0deeab 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs -@@ -5,117 +5,189 @@ use std::ops::ControlFlow; +@@ -5,107 +5,182 @@ use std::ops::ControlFlow; use std::os::unix::io::AsRawFd; use std::sync::Arc; @@ -2290,8 +2654,9 @@ index 059254b3..e388cd46 100644 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] mod ec; -+mod sleep; ++mod resources; mod scheme; ++mod sleep; -fn daemon(daemon: daemon::Daemon) -> ! { - common::setup_logging( @@ -2508,26 +2873,25 @@ index 059254b3..e388cd46 100644 - .next() - .transpose() - .expect("acpid: failed to read event file") -- else { + let Some(event) = event_queue.next().transpose().map_err(|error| { + StartupError::runtime("failed to read event file", error.to_string()) -+ })? else { ++ })? + else { break; }; - - if event.fd == socket.inner().raw() { +@@ -114,8 +189,9 @@ fn daemon(daemon: daemon::Daemon) -> ! { loop { -- match handler -- .process_requests_nonblocking(&mut scheme) + match handler + .process_requests_nonblocking(&mut scheme) - .expect("acpid: failed to process requests") - { -+ match handler.process_requests_nonblocking(&mut scheme).map_err(|error| { -+ StartupError::runtime("failed to process requests", error.to_string()) -+ })? { ++ .map_err(|error| { ++ StartupError::runtime("failed to process requests", error.to_string()) ++ })? { ControlFlow::Continue(()) => {} ControlFlow::Break(()) => break, } -@@ -125,19 +197,136 @@ fn daemon(daemon: daemon::Daemon) -> ! { +@@ -125,19 +201,139 @@ fn daemon(daemon: daemon::Daemon) -> ! { mounted = false; } else { log::debug!("Received request to unknown fd: {}", event.fd); @@ -2540,7 +2904,10 @@ index 059254b3..e388cd46 100644 - acpi_context.set_global_s_state(5); + acpi_context.set_global_s_state(5).map_err(|error| { -+ StartupError::runtime("failed to shut down after kernel request", error.to_string()) ++ StartupError::runtime( ++ "failed to shut down after kernel request", ++ error.to_string(), ++ ) + }) +} + @@ -2626,8 +2993,8 @@ index 059254b3..e388cd46 100644 + #[test] + fn xsdt_physaddrs_parse_without_panic() { + let payload = [ -+ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, -+ 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, ++ 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, ++ 0xAA, 0x99, + ]; + let sdt = parse_root_sdt(make_sdt(*b"XSDT", &payload)) + .unwrap() @@ -2668,7 +3035,7 @@ index 059254b3..e388cd46 100644 + } +} diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs -index 5a5040c3..7070e8b9 100644 +index 5a5040c3..4fe3b8d8 100644 --- a/drivers/acpid/src/scheme.rs +++ b/drivers/acpid/src/scheme.rs @@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName; @@ -2679,12 +3046,14 @@ index 5a5040c3..7070e8b9 100644 use parking_lot::RwLockReadGuard; use redox_scheme::scheme::SchemeSync; use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket}; -@@ -16,17 +15,19 @@ use syscall::FobtainFdFlags; +@@ -16,17 +15,22 @@ use syscall::FobtainFdFlags; use syscall::data::Stat; use syscall::error::{Error, Result}; -use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -+use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP}; ++use syscall::error::{ ++ EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP, ++}; use syscall::flag::{MODE_DIR, MODE_FILE}; use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK}; use syscall::{EOVERFLOW, EPERM}; @@ -2694,6 +3063,7 @@ index 5a5040c3..7070e8b9 100644 + AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo, + SdtSignature, +}; ++use crate::resources::{decode_resource_template, ResourceDescriptor}; pub struct AcpiScheme<'acpi, 'sock> { ctx: &'acpi AcpiContext, @@ -2702,10 +3072,12 @@ index 5a5040c3..7070e8b9 100644 socket: &'sock Socket, } -@@ -41,10 +42,170 @@ enum HandleKind<'a> { +@@ -41,10 +45,204 @@ enum HandleKind<'a> { Table(SdtSignature), Symbols(RwLockReadGuard<'a, AmlSymbols>), Symbol { name: String, description: String }, ++ ResourcesDir, ++ Resources(String), + Reboot, + DmiDir, + Dmi(String), @@ -2861,6 +3233,7 @@ index 5a5040c3..7070e8b9 100644 + let mut entries = vec![ + ("tables", DirentKind::Directory), + ("symbols", DirentKind::Directory), ++ ("resources", DirentKind::Directory), + ("dmi", DirentKind::Directory), + ("reboot", DirentKind::Regular), + ]; @@ -2869,14 +3242,47 @@ index 5a5040c3..7070e8b9 100644 + } + entries +} ++ ++fn resource_symbol_path(path: &str) -> Option { ++ let normalized = path.trim_matches('/').trim_start_matches('\\'); ++ if normalized.is_empty() { ++ return None; ++ } ++ ++ let normalized = normalized.replace('/', "."); ++ if normalized.is_empty() { ++ None ++ } else { ++ Some(format!("{normalized}._CRS")) ++ } ++} ++ ++fn resource_entry_name(symbol: &str) -> Option { ++ symbol ++ .strip_suffix("._CRS") ++ .map(str::to_string) ++ .filter(|path| !path.is_empty()) ++} ++ ++fn resource_dir_entries<'a>(symbols: impl IntoIterator) -> Vec { ++ let mut entries = symbols ++ .into_iter() ++ .filter_map(resource_entry_name) ++ .collect::>(); ++ entries.sort_unstable(); ++ entries.dedup(); ++ entries ++} + impl HandleKind<'_> { fn is_dir(&self) -> bool { match self { -@@ -53,6 +214,15 @@ impl HandleKind<'_> { +@@ -53,6 +251,17 @@ impl HandleKind<'_> { Self::Table(_) => false, Self::Symbols(_) => true, Self::Symbol { .. } => false, ++ Self::ResourcesDir => true, ++ Self::Resources(_) => false, + Self::Reboot => false, + Self::DmiDir => true, + Self::Dmi(_) => false, @@ -2889,10 +3295,11 @@ index 5a5040c3..7070e8b9 100644 Self::SchemeRoot => false, Self::RegisterPci => false, } -@@ -65,8 +235,19 @@ impl HandleKind<'_> { +@@ -65,8 +274,21 @@ impl HandleKind<'_> { .ok_or(Error::new(EBADFD))? .length(), Self::Symbol { description, .. } => description.len(), ++ Self::Resources(contents) => contents.len(), + Self::Reboot => 0, + Self::Dmi(contents) => contents.len(), + Self::PowerFile(contents) => contents.len(), @@ -2900,6 +3307,7 @@ index 5a5040c3..7070e8b9 100644 - Self::TopLevel | Self::Symbols(_) | Self::Tables => 0, + Self::TopLevel + | Self::Symbols(_) ++ | Self::ResourcesDir + | Self::Tables + | Self::DmiDir + | Self::PowerDir @@ -2910,7 +3318,7 @@ index 5a5040c3..7070e8b9 100644 Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)), }) } -@@ -77,10 +258,111 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { +@@ -77,10 +299,163 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { Self { ctx, handles: HandleMap::new(), @@ -2937,6 +3345,58 @@ index 5a5040c3..7070e8b9 100644 + matches!(self.ctx.power_snapshot(), Ok(_)) + } + ++ fn resources_handle(&self, path: &str) -> Result> { ++ if !self.ctx.pci_ready() { ++ let display_path = if path.is_empty() { "resources" } else { path }; ++ log::warn!( ++ "Deferring ACPI resource lookup for {display_path} until PCI registration is ready" ++ ); ++ return Err(Error::new(EAGAIN)); ++ } ++ ++ let normalized = path.trim_matches('/'); ++ if normalized.is_empty() { ++ return Ok(HandleKind::ResourcesDir); ++ } ++ ++ let symbol_path = resource_symbol_path(normalized).ok_or(Error::new(ENOENT))?; ++ if self.ctx.aml_lookup(&symbol_path).is_none() { ++ return Err(Error::new(ENOENT)); ++ } ++ ++ let aml_name = ++ AmlName::from_str(&format!("\\{symbol_path}")).map_err(|_| Error::new(ENOENT))?; ++ let buffer = match self.ctx.aml_eval(aml_name, Vec::new()) { ++ Ok(AmlSerdeValue::Buffer(bytes)) => bytes, ++ Ok(other) => { ++ log::debug!( ++ "Skipping ACPI resources for {normalized} due to unexpected _CRS value: {:?}", ++ other ++ ); ++ return Err(Error::new(ENOENT)); ++ } ++ Err(error) => { ++ log::debug!( ++ "Failed to evaluate ACPI resources for {symbol_path}: {:?}", ++ error ++ ); ++ return Err(Error::new(ENOENT)); ++ } ++ }; ++ ++ let descriptors: Vec = ++ decode_resource_template(&buffer).map_err(|error| { ++ log::warn!("Failed to decode ACPI _CRS for {symbol_path}: {error}"); ++ Error::new(EIO) ++ })?; ++ let serialized = ron::ser::to_string(&descriptors).map_err(|error| { ++ log::warn!("Failed to serialize decoded ACPI resources for {symbol_path}: {error}"); ++ Error::new(EIO) ++ })?; ++ ++ Ok(HandleKind::Resources(serialized)) ++ } ++ + fn power_handle(&self, path: &str) -> Result> { + let normalized = path.trim_matches('/'); + self.power_snapshot()?; @@ -3023,74 +3483,125 @@ index 5a5040c3..7070e8b9 100644 } fn parse_hex_digit(hex: u8) -> Option { -@@ -184,9 +466,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -182,49 +557,97 @@ impl SchemeSync for AcpiScheme<'_, '_> { + + let kind = match handle.kind { HandleKind::SchemeRoot => { - // TODO: arrayvec - let components = { +- // TODO: arrayvec +- let components = { - let mut v = arrayvec::ArrayVec::<&str, 3>::new(); -+ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); - let it = path.split('/'); +- let it = path.split('/'); - for component in it.take(3) { -+ for component in it.take(4) { - v.push(component); - } - -@@ -195,6 +477,25 @@ impl SchemeSync for AcpiScheme<'_, '_> { - - match &*components { - [""] => HandleKind::TopLevel, -+ ["reboot"] => HandleKind::Reboot, -+ ["dmi"] => { -+ if flag_dir || flag_stat || path.ends_with('/') { -+ HandleKind::DmiDir -+ } else { -+ HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), "match_all") -+ .expect("match_all should always resolve"), -+ ) +- v.push(component); +- } +- +- v +- }; +- +- match &*components { +- [""] => HandleKind::TopLevel, +- ["register_pci"] => HandleKind::RegisterPci, +- ["tables"] => HandleKind::Tables, ++ if path == "resources" || path == "resources/" { ++ self.resources_handle("")? ++ } else if let Some(rest) = path.strip_prefix("resources/") { ++ self.resources_handle(rest)? ++ } else { ++ // TODO: arrayvec ++ let components = { ++ let mut v = arrayvec::ArrayVec::<&str, 4>::new(); ++ let it = path.split('/'); ++ for component in it.take(4) { ++ v.push(component); + } -+ } -+ ["dmi", ""] => HandleKind::DmiDir, -+ ["dmi", field] => HandleKind::Dmi( -+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, -+ ), -+ ["power"] => self.power_handle("")?, -+ ["power", tail] => self.power_handle(tail)?, -+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, -+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, - ["register_pci"] => HandleKind::RegisterPci, - ["tables"] => HandleKind::Tables, -@@ -204,7 +505,11 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } +- ["tables", table] => { +- let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; +- HandleKind::Table(signature) +- } ++ v ++ }; ++ ++ match &*components { ++ [""] => HandleKind::TopLevel, ++ ["reboot"] => HandleKind::Reboot, ++ ["dmi"] => { ++ if flag_dir || flag_stat || path.ends_with('/') { ++ HandleKind::DmiDir ++ } else { ++ HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), "match_all") ++ .expect("match_all should always resolve"), ++ ) ++ } ++ } ++ ["dmi", ""] => HandleKind::DmiDir, ++ ["dmi", field] => HandleKind::Dmi( ++ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?, ++ ), ++ ["power"] => self.power_handle("")?, ++ ["power", tail] => self.power_handle(tail)?, ++ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?, ++ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?, ++ ["register_pci"] => HandleKind::RegisterPci, ++ ["tables"] => HandleKind::Tables, ++ ++ ["tables", table] => { ++ let signature = ++ parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; ++ HandleKind::Table(signature) ++ } - ["symbols"] => { +- ["symbols"] => { - if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) { -+ if !self.ctx.pci_ready() { -+ log::warn!("Deferring AML symbol scan until PCI registration is ready"); -+ return Err(Error::new(EAGAIN)); -+ } -+ if let Ok(aml_symbols) = self.ctx.aml_symbols() { - HandleKind::Symbols(aml_symbols) - } else { - return Err(Error::new(EIO)); -@@ -212,6 +517,12 @@ impl SchemeSync for AcpiScheme<'_, '_> { - } +- HandleKind::Symbols(aml_symbols) +- } else { +- return Err(Error::new(EIO)); ++ ["symbols"] => { ++ if !self.ctx.pci_ready() { ++ log::warn!( ++ "Deferring AML symbol scan until PCI registration is ready" ++ ); ++ return Err(Error::new(EAGAIN)); ++ } ++ if let Ok(aml_symbols) = self.ctx.aml_symbols() { ++ HandleKind::Symbols(aml_symbols) ++ } else { ++ return Err(Error::new(EIO)); ++ } + } +- } - ["symbols", symbol] => { -+ if !self.ctx.pci_ready() { -+ log::warn!( -+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" -+ ); -+ return Err(Error::new(EAGAIN)); -+ } - if let Some(description) = self.ctx.aml_lookup(symbol) { - HandleKind::Symbol { - name: (*symbol).to_owned(), -@@ -225,6 +536,15 @@ impl SchemeSync for AcpiScheme<'_, '_> { - _ => return Err(Error::new(ENOENT)), - } - } +- ["symbols", symbol] => { +- if let Some(description) = self.ctx.aml_lookup(symbol) { +- HandleKind::Symbol { +- name: (*symbol).to_owned(), +- description, ++ ["symbols", symbol] => { ++ if !self.ctx.pci_ready() { ++ log::warn!( ++ "Deferring AML symbol lookup for {symbol} until PCI registration is ready" ++ ); ++ return Err(Error::new(EAGAIN)); ++ } ++ if let Some(description) = self.ctx.aml_lookup(symbol) { ++ HandleKind::Symbol { ++ name: (*symbol).to_owned(), ++ description, ++ } ++ } else { ++ return Err(Error::new(ENOENT)); + } +- } else { +- return Err(Error::new(ENOENT)); + } +- } + +- _ => return Err(Error::new(ENOENT)), ++ _ => return Err(Error::new(ENOENT)), ++ } ++ } ++ } + HandleKind::DmiDir => { + if path.is_empty() { + HandleKind::DmiDir @@ -3098,12 +3609,13 @@ index 5a5040c3..7070e8b9 100644 + HandleKind::Dmi( + dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?, + ) -+ } -+ } + } + } ++ HandleKind::ResourcesDir => self.resources_handle(path)?, HandleKind::Symbols(ref aml_symbols) => { if let Some(description) = aml_symbols.lookup(path) { HandleKind::Symbol { -@@ -235,6 +555,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -235,6 +658,23 @@ impl SchemeSync for AcpiScheme<'_, '_> { return Err(Error::new(ENOENT)); } } @@ -3127,7 +3639,7 @@ index 5a5040c3..7070e8b9 100644 _ => return Err(Error::new(EACCES)), }; -@@ -296,7 +633,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -296,7 +736,7 @@ impl SchemeSync for AcpiScheme<'_, '_> { ) -> Result { let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?; @@ -3136,16 +3648,17 @@ index 5a5040c3..7070e8b9 100644 if handle.stat { return Err(Error::new(EBADF)); -@@ -309,6 +646,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -309,6 +749,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { .ok_or(Error::new(EBADFD))? .as_slice(), HandleKind::Symbol { description, .. } => description.as_bytes(), ++ HandleKind::Resources(contents) => contents.as_bytes(), + HandleKind::Dmi(contents) => contents.as_bytes(), + HandleKind::PowerFile(contents) => contents.as_bytes(), _ => return Err(Error::new(EINVAL)), }; -@@ -328,13 +667,82 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -328,13 +771,95 @@ impl SchemeSync for AcpiScheme<'_, '_> { mut buf: DirentBuf<&'buf mut [u8]>, opaque_offset: u64, ) -> Result> { @@ -3182,6 +3695,19 @@ index 5a5040c3..7070e8b9 100644 + })?; + } + } ++ HandleKind::ResourcesDir => { ++ let aml_symbols = self.ctx.aml_symbols().map_err(|_| Error::new(EIO))?; ++ let entries = ++ resource_dir_entries(aml_symbols.symbols_cache().keys().map(String::as_str)); ++ for (idx, name) in entries.iter().enumerate().skip(opaque_offset as usize) { ++ buf.entry(DirEntry { ++ inode: 0, ++ next_opaque_id: idx as u64 + 1, ++ name, ++ kind: DirentKind::Regular, ++ })?; ++ } ++ } + HandleKind::PowerDir => { + const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[ + ("on_battery", DirentKind::Regular), @@ -3231,7 +3757,7 @@ index 5a5040c3..7070e8b9 100644 .iter() .enumerate() .skip(opaque_offset as usize) -@@ -343,10 +751,44 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -343,10 +868,44 @@ impl SchemeSync for AcpiScheme<'_, '_> { inode: 0, next_opaque_id: idx as u64 + 1, name, @@ -3276,7 +3802,7 @@ index 5a5040c3..7070e8b9 100644 HandleKind::Symbols(aml_symbols) => { for (idx, (symbol_name, _value)) in aml_symbols .symbols_cache() -@@ -444,6 +886,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -444,6 +1003,38 @@ impl SchemeSync for AcpiScheme<'_, '_> { Ok(result_len) } @@ -3315,7 +3841,7 @@ index 5a5040c3..7070e8b9 100644 fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { let id = sendfd_request.id(); let num_fds = sendfd_request.num_fds(); -@@ -470,10 +944,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -470,10 +1061,8 @@ impl SchemeSync for AcpiScheme<'_, '_> { } let new_fd = libredox::Fd::new(new_fd); @@ -3327,14 +3853,14 @@ index 5a5040c3..7070e8b9 100644 } Ok(num_fds) -@@ -483,3 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> { +@@ -483,3 +1072,90 @@ impl SchemeSync for AcpiScheme<'_, '_> { self.handles.remove(id); } } + +#[cfg(test)] +mod tests { -+ use super::{dmi_contents, top_level_entries}; ++ use super::{dmi_contents, resource_dir_entries, resource_symbol_path, top_level_entries}; + use crate::acpi::DmiInfo; + use syscall::dirent::DirentKind; + @@ -3370,9 +3896,9 @@ index 5a5040c3..7070e8b9 100644 + #[test] + fn top_level_entries_always_include_reboot() { + let entries = top_level_entries(false); -+ assert!(entries.iter().any(|(name, kind)| { -+ *name == "reboot" && matches!(kind, DirentKind::Regular) -+ })); ++ assert!(entries ++ .iter() ++ .any(|(name, kind)| { *name == "reboot" && matches!(kind, DirentKind::Regular) })); + } + + #[test] @@ -3381,9 +3907,41 @@ index 5a5040c3..7070e8b9 100644 + let visible = top_level_entries(true); + + assert!(!hidden.iter().any(|(name, _)| *name == "power")); -+ assert!(visible.iter().any(|(name, kind)| { -+ *name == "power" && matches!(kind, DirentKind::Directory) -+ })); ++ assert!(visible ++ .iter() ++ .any(|(name, kind)| { *name == "power" && matches!(kind, DirentKind::Directory) })); ++ } ++ ++ #[test] ++ fn top_level_entries_always_include_resources() { ++ let entries = top_level_entries(false); ++ assert!(entries ++ .iter() ++ .any(|(name, kind)| { *name == "resources" && matches!(kind, DirentKind::Directory) })); ++ } ++ ++ #[test] ++ fn resource_symbol_path_accepts_dotted_and_slash_paths() { ++ assert_eq!( ++ resource_symbol_path("_SB.PCI0.I2C0.TPD0").as_deref(), ++ Some("_SB.PCI0.I2C0.TPD0._CRS") ++ ); ++ assert_eq!( ++ resource_symbol_path("\\_SB/PCI0/I2C0/TPD0").as_deref(), ++ Some("_SB.PCI0.I2C0.TPD0._CRS") ++ ); ++ } ++ ++ #[test] ++ fn resource_dir_entries_list_devices_with_crs_suffix() { ++ assert_eq!( ++ resource_dir_entries([ ++ "_SB.PCI0.I2C0.TPD0._CRS", ++ "_SB.PCI0.I2C1.TPD1._HID", ++ "_SB.PCI0.I2C0.TPD0._CRS", ++ ]), ++ vec!["_SB.PCI0.I2C0.TPD0".to_string()] ++ ); + } +} diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs @@ -3663,6 +4221,1337 @@ index 31a2add7..11d80133 100755 }; let mut readiness_based = ReadinessBased::new(&socket, 16); +diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs +index 82ec2bd0..a531edd9 100644 +--- a/drivers/common/src/logger.rs ++++ b/drivers/common/src/logger.rs +@@ -44,6 +44,7 @@ pub fn setup_logging( + Ok(b) => { + logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build()) + } ++ Err(error) if error.raw_os_error() == Some(19) => {} + Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error), + } + +@@ -61,6 +62,7 @@ pub fn setup_logging( + .build(), + ) + } ++ Err(error) if error.raw_os_error() == Some(19) => {} + Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error), + } + +diff --git a/drivers/graphics/console-draw/src/lib.rs b/drivers/graphics/console-draw/src/lib.rs +index 5eb951df..0b959e5c 100644 +--- a/drivers/graphics/console-draw/src/lib.rs ++++ b/drivers/graphics/console-draw/src/lib.rs +@@ -59,19 +59,19 @@ pub struct V2DisplayMap { + + impl V2DisplayMap { + pub fn new(display_handle: V2GraphicsHandle) -> io::Result { +- let connector = display_handle.first_display().unwrap(); +- let connector_info = display_handle.get_connector(connector, true).unwrap(); ++ let connector = display_handle.first_display()?; ++ let connector_info = display_handle.get_connector(connector, true)?; + + let mode = connector_info.modes()[0]; + let (width, height) = mode.size(); + + // FIXME do something smarter that avoids conflicts +- let crtc = display_handle.resource_handles().unwrap().filter_crtcs( +- display_handle +- .get_encoder(connector_info.encoders()[0]) +- .unwrap() +- .possible_crtcs(), +- )[0]; ++ let crtc = { ++ let res_handles = display_handle.resource_handles()?; ++ let encoder = display_handle ++ .get_encoder(connector_info.encoders()[0])?; ++ res_handles.filter_crtcs(encoder.possible_crtcs())[0] ++ }; + + let buffer = CpuBackedBuffer::new( + &display_handle, +@@ -338,12 +338,12 @@ impl TextScreen { + line_changed(y); + } + +- let width = map.width.try_into().unwrap(); ++ let width: u32 = map.width.try_into().unwrap_or(0); + let damage = Damage { + x: 0, +- y: u32::try_from(min_changed).unwrap() * 16, ++ y: u32::try_from(min_changed).unwrap_or(0) * 16, + width, +- height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16, ++ height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap_or(0) * 16, + }; + + damage +@@ -445,7 +445,9 @@ impl TextBuffer { + } + + for &byte in buf { +- self.lines.back_mut().unwrap().push(byte); ++ if let Some(last) = self.lines.back_mut() { ++ last.push(byte); ++ } + + if byte == b'\n' { + self.lines.push_back(Vec::new()); +diff --git a/drivers/graphics/driver-graphics/Cargo.toml b/drivers/graphics/driver-graphics/Cargo.toml +index 31e02335..fc747cce 100644 +--- a/drivers/graphics/driver-graphics/Cargo.toml ++++ b/drivers/graphics/driver-graphics/Cargo.toml +@@ -9,6 +9,7 @@ drm-fourcc = "2.2.0" + drm-sys.workspace = true + edid.workspace = true #TODO: edid is abandoned, fork it and maintain? + log.workspace = true ++nom.workspace = true + redox-ioctl.workspace = true + redox-scheme.workspace = true + scheme-utils = { path = "../../../scheme-utils" } +diff --git a/drivers/graphics/driver-graphics/src/kms/connector.rs b/drivers/graphics/driver-graphics/src/kms/connector.rs +index c885f413..19037fec 100644 +--- a/drivers/graphics/driver-graphics/src/kms/connector.rs ++++ b/drivers/graphics/driver-graphics/src/kms/connector.rs +@@ -21,7 +21,14 @@ impl KmsObjects { + ) -> KmsObjectId { + let mut possible_crtcs = 0; + for &crtc in crtcs { +- possible_crtcs = 1 << self.get_crtc(crtc).unwrap().lock().unwrap().crtc_index; ++ if let Ok(crtc_guard) = self.get_crtc(crtc) { ++ match crtc_guard.lock() { ++ Ok(locked) => possible_crtcs = 1 << locked.crtc_index, ++ Err(e) => log::error!("add_connector: crtc lock poisoned: {e}"), ++ } ++ } else { ++ log::error!("add_connector: failed to get crtc {}", crtc.0); ++ } + } + + let encoder_id = self.add(KmsEncoder { +@@ -61,7 +68,7 @@ impl KmsObjects { + pub fn connectors(&self) -> impl Iterator>> + use<'_, T> { + self.connectors + .iter() +- .map(|&id| self.get_connector(id).unwrap()) ++ .filter_map(|&id| self.get_connector(id).ok()) + } + + pub fn get_connector(&self, id: KmsObjectId) -> Result<&Mutex>> { +@@ -136,10 +143,16 @@ impl KmsConnector { + } + + pub fn update_from_edid(&mut self, edid: &[u8]) { +- let edid = edid::parse(edid).unwrap().1; ++ let edid_data = match edid::parse(edid) { ++ nom::IResult::Done(_, data) => data, ++ _ => { ++ log::error!("failed to parse EDID: parse returned error or incomplete"); ++ return; ++ } ++ }; + + if let Some(first_detailed_timing) = +- edid.descriptors ++ edid_data.descriptors + .iter() + .find_map(|descriptor| match descriptor { + edid::Descriptor::DetailedTiming(detailed_timing) => Some(detailed_timing), +@@ -152,7 +165,7 @@ impl KmsConnector { + log::error!("No edid timing descriptor detected"); + } + +- self.modes = edid ++ self.modes = edid_data + .descriptors + .iter() + .filter_map(|descriptor| { +diff --git a/drivers/graphics/driver-graphics/src/kms/objects.rs b/drivers/graphics/driver-graphics/src/kms/objects.rs +index 1daf3221..55c60167 100644 +--- a/drivers/graphics/driver-graphics/src/kms/objects.rs ++++ b/drivers/graphics/driver-graphics/src/kms/objects.rs +@@ -95,7 +95,7 @@ impl KmsObjects { + pub fn crtcs(&self) -> impl Iterator>> + use<'_, T> { + self.crtcs + .iter() +- .map(|&id| self.get::>>(id).unwrap()) ++ .filter_map(|&id| self.get::>>(id).ok()) + } + + pub fn get_crtc(&self, id: KmsObjectId) -> Result<&Mutex>> { +@@ -115,7 +115,12 @@ impl KmsObjects { + let KmsObject::Framebuffer(_) = object else { + return Err(Error::new(EINVAL)); + }; +- self.objects.remove(&id).unwrap(); ++ self.objects ++ .remove(&id) ++ .ok_or_else(|| { ++ log::error!("remove_framebuffer: object {} vanished during removal", id.0); ++ Error::new(EINVAL) ++ })?; + + Ok(()) + } +diff --git a/drivers/graphics/driver-graphics/src/kms/properties.rs b/drivers/graphics/driver-graphics/src/kms/properties.rs +index e22527a7..c75df3b0 100644 +--- a/drivers/graphics/driver-graphics/src/kms/properties.rs ++++ b/drivers/graphics/driver-graphics/src/kms/properties.rs +@@ -21,7 +21,11 @@ impl KmsObjects { + kind: KmsPropertyKind, + ) -> KmsObjectId { + match &kind { +- KmsPropertyKind::Range(start, end) => assert!(start < end), ++ KmsPropertyKind::Range(start, end) => { ++ if start >= end { ++ log::error!("Range property '{name}' has invalid range: start ({start}) >= end ({end})"); ++ } ++ } + KmsPropertyKind::Enum(_variants) => { + // FIXME check duplicate variant numbers + } +@@ -30,7 +34,11 @@ impl KmsObjects { + // FIXME check overlapping flag numbers + } + KmsPropertyKind::Object { type_: _ } => {} +- KmsPropertyKind::SignedRange(start, end) => assert!(start < end), ++ KmsPropertyKind::SignedRange(start, end) => { ++ if start >= end { ++ log::error!("SignedRange property '{name}' has invalid range: start ({start}) >= end ({end})"); ++ } ++ } + } + + let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; +@@ -54,7 +62,13 @@ impl KmsObjects { + let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?; + match object { + KmsObject::Crtc(crtc) => { +- let crtc = crtc.lock().unwrap(); ++ let crtc = match crtc.lock() { ++ Ok(g) => g, ++ Err(e) => { ++ log::error!("get_object_properties_data: crtc lock poisoned: {e}"); ++ return Err(Error::new(EINVAL)); ++ } ++ }; + let props = &crtc.properties; + Ok(( + props.iter().map(|prop| prop.id.0).collect::>(), +@@ -65,7 +79,13 @@ impl KmsObjects { + )) + } + KmsObject::Connector(connector) => { +- let connector = connector.lock().unwrap(); ++ let connector = match connector.lock() { ++ Ok(g) => g, ++ Err(e) => { ++ log::error!("get_object_properties_data: connector lock poisoned: {e}"); ++ return Err(Error::new(EINVAL)); ++ } ++ }; + let props = &connector.properties; + Ok(( + props.iter().map(|prop| prop.id.0).collect::>(), +@@ -97,7 +117,7 @@ pub struct KmsPropertyName(pub [c_char; DRM_PROP_NAME_LEN as usize]); + impl KmsPropertyName { + fn new(context: &str, name: &str) -> KmsPropertyName { + if name.len() > DRM_PROP_NAME_LEN as usize { +- panic!("{context} {name} is too long"); ++ log::error!("{context} {name} is too long, truncating"); + } + + let mut name_bytes = [0; DRM_PROP_NAME_LEN as usize]; +@@ -151,12 +171,16 @@ macro_rules! define_properties { + + pub(super) fn init_standard_props(objects: &mut KmsObjects) { + $( +- assert_eq!(objects.add_property( ++ let prop_id = objects.add_property( + define_properties!(@prop_name $prop $($prop_name)?), + define_properties!(@is_immutable $($prop_flag)?), + define_properties!(@is_atomic $($prop_flag)?), + define_properties!(@prop_kind $prop_type $({$($prop_content)*})?), +- ), $prop); ++ ); ++ if prop_id != $prop { ++ log::error!("property ID mismatch for {}: expected {:?}, got {:?}", ++ stringify!($prop), $prop, prop_id); ++ } + )* + } + }; +diff --git a/drivers/graphics/driver-graphics/src/lib.rs b/drivers/graphics/driver-graphics/src/lib.rs +index eab0be9c..4fe7ecb6 100644 +--- a/drivers/graphics/driver-graphics/src/lib.rs ++++ b/drivers/graphics/driver-graphics/src/lib.rs +@@ -136,13 +136,20 @@ pub struct GraphicsScheme { + + impl GraphicsScheme { + pub fn new(mut adapter: T, scheme_name: String, early: bool) -> Self { +- assert!(scheme_name.starts_with("display")); +- let socket = Socket::nonblock().expect("failed to create graphics scheme"); ++ if !scheme_name.starts_with("display") { ++ log::error!("graphics scheme name must start with 'display': {scheme_name}"); ++ std::process::exit(1); ++ } ++ let socket = match Socket::nonblock() { ++ Ok(s) => s, ++ Err(e) => { ++ log::error!("failed to create graphics scheme: {e}"); ++ std::process::exit(1); ++ } ++ }; + +- let disable_graphical_debug = Some( +- File::open("/scheme/debug/disable-graphical-debug") +- .expect("vesad: Failed to open /scheme/debug/disable-graphical-debug"), +- ); ++ let disable_graphical_debug = ++ File::open("/scheme/debug/disable-graphical-debug").ok(); + + let mut objects = KmsObjects::new(); + adapter.init(&mut objects); +@@ -161,14 +168,34 @@ impl GraphicsScheme { + vts: HashMap::new(), + }; + +- let cap_id = inner.scheme_root().expect("failed to get this scheme root"); +- register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) +- .expect("failed to register graphics scheme root"); ++ let cap_id = match inner.scheme_root() { ++ Ok(id) => id, ++ Err(e) => { ++ log::error!("failed to get this scheme root: {e}"); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(e) = register_scheme_inner(&inner.socket, &inner.scheme_name, cap_id) { ++ log::error!("failed to register graphics scheme root: {e}"); ++ std::process::exit(1); ++ } + + let display_handle = if early { +- DisplayHandle::new_early(&inner.scheme_name).unwrap() ++ match DisplayHandle::new_early(&inner.scheme_name) { ++ Ok(h) => h, ++ Err(e) => { ++ log::error!("failed to create early display handle: {e}"); ++ std::process::exit(1); ++ } ++ } + } else { +- DisplayHandle::new(&inner.scheme_name).unwrap() ++ match DisplayHandle::new(&inner.scheme_name) { ++ Ok(h) => h, ++ Err(e) => { ++ log::error!("failed to create display handle: {e}"); ++ std::process::exit(1); ++ } ++ } + }; + + Self { +@@ -207,11 +234,15 @@ impl GraphicsScheme { + } + + pub fn handle_vt_events(&mut self) { +- while let Some(vt_event) = self +- .inputd_handle +- .read_vt_event() +- .expect("driver-graphics: failed to read display handle") +- { ++ loop { ++ let vt_event = match self.inputd_handle.read_vt_event() { ++ Ok(Some(event)) => event, ++ Ok(None) => break, ++ Err(e) => { ++ log::error!("driver-graphics: failed to read display handle: {e}"); ++ break; ++ } ++ }; + match vt_event.kind { + VtEventKind::Activate => self.inner.activate_vt(vt_event.vt), + } +@@ -235,16 +266,26 @@ impl GraphicsScheme { + std::process::exit(0); + } + Err(err) if err.errno == EAGAIN => break, +- Err(err) => panic!("driver-graphics: failed to read display scheme: {err}"), ++ Err(err) => { ++ log::error!("driver-graphics: failed to read display scheme: {err}"); ++ break; ++ } + }; + + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_sync(&mut self.inner, &mut self.state); +- self.inner ++ if let Err(e) = self ++ .inner + .socket + .write_response(response, SignalBehavior::Restart) +- .expect("driver-graphics: failed to write response"); ++ { ++ log::error!("driver-graphics: failed to write response: {e}"); ++ return Err(io::Error::new( ++ io::ErrorKind::Other, ++ format!("driver-graphics: failed to write response: {e}"), ++ )); ++ } + } + RequestKind::OnClose { id } => { + self.inner.on_close(id); +@@ -294,11 +335,28 @@ impl GraphicsSchemeInner { + vts.entry(vt).or_insert_with(|| VtState { + connector_state: objects + .connectors() +- .map(|connector| connector.lock().unwrap().state.clone()) ++ .map(|connector| { ++ connector ++ .lock() ++ .unwrap_or_else(|e| { ++ log::error!("get_or_create_vt: connector lock poisoned: {e}"); ++ e.into_inner() ++ }) ++ .state ++ .clone() ++ }) + .collect(), + crtc_state: objects + .crtcs() +- .map(|crtc| crtc.lock().unwrap().state.clone()) ++ .map(|crtc| { ++ crtc.lock() ++ .unwrap_or_else(|e| { ++ log::error!("get_or_create_vt: crtc lock poisoned: {e}"); ++ e.into_inner() ++ }) ++ .state ++ .clone() ++ }) + .collect(), + cursor_plane: CursorPlane { + x: 0, +@@ -327,47 +385,71 @@ impl GraphicsSchemeInner { + + for (connector_idx, connector_state) in vt_state.connector_state.iter().enumerate() { + let connector_id = self.objects.connector_ids()[connector_idx]; +- let mut connector = self +- .objects +- .get_connector(connector_id) +- .unwrap() +- .lock() +- .unwrap(); ++ let connector_guard = match self.objects.get_connector(connector_id) { ++ Ok(g) => g, ++ Err(e) => { ++ log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); ++ continue; ++ } ++ }; ++ let mut connector = match connector_guard.lock() { ++ Ok(g) => g, ++ Err(e) => { ++ log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); ++ e.into_inner() ++ } ++ }; + connector.state = connector_state.clone(); + } + + for (crtc_idx, crtc_state) in vt_state.crtc_state.iter().enumerate() { + let crtc_id = self.objects.crtc_ids()[crtc_idx]; +- let crtc = self.objects.get_crtc(crtc_id).unwrap(); ++ let crtc = match self.objects.get_crtc(crtc_id) { ++ Ok(c) => c, ++ Err(e) => { ++ log::error!("activate_vt: failed to get crtc {}: {e}", crtc_id.0); ++ continue; ++ } ++ }; + let connector_id = self.objects.connector_ids()[crtc_idx]; + +- let fb = crtc_state.fb_id.map(|fb_id| { ++ let fb = crtc_state.fb_id.and_then(|fb_id| { + self.objects + .get_framebuffer(fb_id) +- .expect("removed framebuffers should be unset") ++ .map_err(|e| { ++ log::error!("activate_vt: framebuffer {} missing: {e}", fb_id.0); ++ e ++ }) ++ .ok() + }); + +- self.adapter +- .set_crtc( +- &self.objects, +- crtc, +- crtc_state.clone(), +- Damage { +- x: 0, +- y: 0, +- width: fb.map_or(0, |fb| fb.width), +- height: fb.map_or(0, |fb| fb.height), +- }, +- ) +- .unwrap(); +- +- self.objects +- .get_connector(connector_id) +- .unwrap() +- .lock() +- .unwrap() +- .state +- .crtc_id = crtc_id; ++ if let Err(e) = self.adapter.set_crtc( ++ &self.objects, ++ crtc, ++ crtc_state.clone(), ++ Damage { ++ x: 0, ++ y: 0, ++ width: fb.as_ref().map_or(0, |fb| fb.width), ++ height: fb.as_ref().map_or(0, |fb| fb.height), ++ }, ++ ) { ++ log::error!("activate_vt: set_crtc failed for crtc {}: {e}", crtc_id.0); ++ continue; ++ } ++ ++ match self.objects.get_connector(connector_id) { ++ Ok(conn_guard) => match conn_guard.lock() { ++ Ok(mut conn) => conn.state.crtc_id = crtc_id, ++ Err(e) => { ++ log::error!("activate_vt: connector {} lock poisoned: {e}", connector_id.0); ++ e.into_inner().state.crtc_id = crtc_id; ++ } ++ }, ++ Err(e) => { ++ log::error!("activate_vt: failed to get connector {}: {e}", connector_id.0); ++ } ++ } + } + + if self.adapter.hw_cursor_size().is_some() { +@@ -430,7 +512,12 @@ impl SchemeSync for GraphicsSchemeInner { + vt, + next_id: _, + buffers: _, +- } => write!(w, "v2/{vt}").unwrap(), ++ } => { ++ if let Err(e) = write!(w, "v2/{vt}") { ++ log::error!("fpath: write failed: {e}"); ++ return Err(Error::new(EINVAL)); ++ } ++ } + Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), + }; + Ok(()) +@@ -531,7 +618,10 @@ impl SchemeSync for GraphicsSchemeInner { + .objects + .get_crtc(KmsObjectId(data.crtc_id()))? + .lock() +- .unwrap(); ++ .map_err(|e| { ++ log::error!("MODE_GET_CRTC: crtc lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?; + // Don't touch set_connectors, that is only used by MODE_SET_CRTC + data.set_fb_id(crtc.state.fb_id.unwrap_or(KmsObjectId::INVALID).0); + // FIXME fill x and y with the data from the primary plane +@@ -565,7 +655,10 @@ impl SchemeSync for GraphicsSchemeInner { + } else { + None + }; +- let mut new_state = crtc.lock().unwrap().state.clone(); ++ let mut new_state = crtc.lock().map_err(|e| { ++ log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?.state.clone(); + new_state.fb_id = fb_id; + new_state.mode = mode; + if *vt == self.active_vt { +@@ -582,20 +675,34 @@ impl SchemeSync for GraphicsSchemeInner { + )?; + + for connector in connector_ids { +- self.objects +- .get_connector(connector)? +- .lock() +- .unwrap() +- .state +- .crtc_id = KmsObjectId(data.crtc_id()); ++ let conn_guard = self.objects.get_connector(connector)?; ++ match conn_guard.lock() { ++ Ok(mut conn) => conn.state.crtc_id = KmsObjectId(data.crtc_id()), ++ Err(e) => { ++ log::error!("MODE_SET_CRTC: connector lock poisoned: {e}"); ++ e.into_inner().state.crtc_id = KmsObjectId(data.crtc_id()); ++ } ++ } + } + } +- self.vts.get_mut(vt).unwrap().crtc_state +- [crtc.lock().unwrap().crtc_index as usize] = new_state; ++ { ++ let crtc_index = crtc.lock().map_err(|e| { ++ log::error!("MODE_SET_CRTC: crtc lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?.crtc_index as usize; ++ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { ++ log::error!("MODE_SET_CRTC: vt {} not found", vt); ++ Error::new(EINVAL) ++ })?; ++ vt_state.crtc_state[crtc_index] = new_state; ++ } + Ok(0) + }), + ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| { +- let vt_state = self.vts.get_mut(vt).unwrap(); ++ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { ++ log::error!("MODE_CURSOR: vt {} not found", vt); ++ Error::new(EINVAL) ++ })?; + + let cursor_plane = &mut vt_state.cursor_plane; + +@@ -635,7 +742,10 @@ impl SchemeSync for GraphicsSchemeInner { + .objects + .get_connector(KmsObjectId(data.connector_id()))? + .lock() +- .unwrap(); ++ .map_err(|e| { ++ log::error!("MODE_GET_CONNECTOR: connector lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?; + data.set_encoders_ptr(&[connector.encoder_id.0]); + data.set_modes_ptr(&connector.modes); + data.set_connector_type(data.connector_type()); +@@ -772,20 +882,23 @@ impl SchemeSync for GraphicsSchemeInner { + if *vt != self.active_vt { + continue; + } +- let crtc = self.objects.crtcs().nth(crtc_idx).unwrap(); +- self.adapter +- .set_crtc( +- &self.objects, +- crtc, +- crtc_state.clone(), +- Damage { +- x: 0, +- y: 0, +- width: 0, +- height: 0, +- }, +- ) +- .unwrap(); ++ let Some(crtc) = self.objects.crtcs().nth(crtc_idx) else { ++ log::error!("MODE_RM_FB: crtc index {crtc_idx} out of bounds"); ++ continue; ++ }; ++ if let Err(e) = self.adapter.set_crtc( ++ &self.objects, ++ crtc, ++ crtc_state.clone(), ++ Damage { ++ x: 0, ++ y: 0, ++ width: 0, ++ height: 0, ++ }, ++ ) { ++ log::error!("MODE_RM_FB: set_crtc failed for crtc {crtc_idx}: {e}"); ++ } + } + } + +@@ -813,7 +926,10 @@ impl SchemeSync for GraphicsSchemeInner { + + if *vt == self.active_vt { + for crtc in self.objects.crtcs() { +- let state = crtc.lock().unwrap().state.clone(); ++ let state = crtc.lock().map_err(|e| { ++ log::error!("MODE_DIRTYFB: crtc lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?.state.clone(); + if state.fb_id == Some(KmsObjectId(data.fb_id())) { + self.adapter.set_crtc(&self.objects, crtc, state, damage)?; + } +@@ -850,7 +966,13 @@ impl SchemeSync for GraphicsSchemeInner { + } + + // FIXME use a better scheme for creating map offsets +- assert!(buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER); ++ let buf_size = buffers[&buffer_id].size(); ++ if buf_size >= MAP_FAKE_OFFSET_MULTIPLIER { ++ log::error!( ++ "MODE_MAP_DUMB: buffer size {buf_size} exceeds offset multiplier {MAP_FAKE_OFFSET_MULTIPLIER}" ++ ); ++ return Err(Error::new(EINVAL)); ++ } + + data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64); + +@@ -874,11 +996,14 @@ impl SchemeSync for GraphicsSchemeInner { + ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| { + let i = id_index(data.plane_id()); + let crtc_id = self.objects.crtc_ids()[i as usize]; +- let crtc = self.objects.get_crtc(crtc_id).unwrap(); ++ let crtc = self.objects.get_crtc(crtc_id)?; + data.set_crtc_id(crtc_id.0); ++ let crtc_locked = crtc.lock().map_err(|e| { ++ log::error!("MODE_GET_PLANE: crtc lock poisoned: {e}"); ++ Error::new(EINVAL) ++ })?; + data.set_fb_id( +- crtc.lock() +- .unwrap() ++ crtc_locked + .state + .fb_id + .unwrap_or(KmsObjectId::INVALID) +@@ -907,7 +1032,10 @@ impl SchemeSync for GraphicsSchemeInner { + }) + } + ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| { +- let vt_state = self.vts.get_mut(vt).unwrap(); ++ let vt_state = self.vts.get_mut(vt).ok_or_else(|| { ++ log::error!("MODE_CURSOR2: vt {} not found", vt); ++ Error::new(EINVAL) ++ })?; + + let cursor_plane = &mut vt_state.cursor_plane; + +@@ -970,8 +1098,7 @@ impl SchemeSync for GraphicsSchemeInner { + } => ( + buffers + .get(&((offset as usize / MAP_FAKE_OFFSET_MULTIPLIER) as u32)) +- .ok_or(Error::new(EINVAL)) +- .unwrap(), ++ .ok_or(Error::new(EINVAL))?, + offset & (MAP_FAKE_OFFSET_MULTIPLIER as u64 - 1), + ), + Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)), +diff --git a/drivers/graphics/fbbootlogd/src/main.rs b/drivers/graphics/fbbootlogd/src/main.rs +index 3e42d590..62749577 100644 +--- a/drivers/graphics/fbbootlogd/src/main.rs ++++ b/drivers/graphics/fbbootlogd/src/main.rs +@@ -24,7 +24,13 @@ fn main() { + daemon::SchemeDaemon::new(daemon); + } + fn daemon(daemon: daemon::SchemeDaemon) -> ! { +- let event_queue = EventQueue::new().expect("fbbootlogd: failed to create event queue"); ++ let event_queue = match EventQueue::new() { ++ Ok(eq) => eq, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to create event queue: {err}"); ++ std::process::exit(1); ++ } ++ }; + + event::user_data! { + enum Source { +@@ -33,78 +39,105 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { + } + } + +- let socket = Socket::nonblock().expect("fbbootlogd: failed to create fbbootlog scheme"); ++ let socket = match Socket::nonblock() { ++ Ok(s) => s, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to create fbbootlog scheme: {err}"); ++ std::process::exit(1); ++ } ++ }; + + let mut scheme = FbbootlogScheme::new(); + let mut handler = Blocking::new(&socket, 16); + +- event_queue +- .subscribe( +- socket.inner().raw(), +- Source::Scheme, +- event::EventFlags::READ, +- ) +- .expect("fbbootlogd: failed to subscribe to scheme events"); ++ if let Err(err) = event_queue.subscribe( ++ socket.inner().raw(), ++ Source::Scheme, ++ event::EventFlags::READ, ++ ) { ++ eprintln!("fbbootlogd: failed to subscribe to scheme events: {err}"); ++ std::process::exit(1); ++ } + +- event_queue +- .subscribe( +- scheme.input_handle.event_handle().as_raw_fd() as usize, +- Source::Input, +- event::EventFlags::READ, +- ) +- .expect("fbbootlogd: failed to subscribe to scheme events"); ++ if let Err(err) = event_queue.subscribe( ++ scheme.input_handle.event_handle().as_raw_fd() as usize, ++ Source::Input, ++ event::EventFlags::READ, ++ ) { ++ eprintln!("fbbootlogd: failed to subscribe to input events: {err}"); ++ std::process::exit(1); ++ } + + { +- let log_fd = socket +- .create_this_scheme_fd(0, 0, 0, 0) +- .expect("fbbootlogd: failed to create log fd"); ++ let log_fd = match socket.create_this_scheme_fd(0, 0, 0, 0) { ++ Ok(fd) => fd, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to create log fd: {err}"); ++ std::process::exit(1); ++ } ++ }; + // Add ourself as log sink +- let log_file = libredox::Fd::open( ++ let log_file = match libredox::Fd::open( + "/scheme/log/add_sink", + libredox::flag::O_WRONLY | libredox::flag::O_CLOEXEC, + 0, +- ) +- .expect("fbbootlogd: failed to open log/add_sink"); +- log_file +- .call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) +- .expect("fbbootlogd: failed to send log fd to log scheme."); ++ ) { ++ Ok(fd) => fd, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to open log/add_sink: {err}"); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(err) = ++ log_file.call_wo(&log_fd.to_ne_bytes(), syscall::CallFlags::FD, &[]) ++ { ++ eprintln!("fbbootlogd: failed to send log fd to log scheme: {err}"); ++ std::process::exit(1); ++ } + } + + let _ = daemon.ready_sync_scheme(&socket, &mut scheme); + + // This is not possible for now as fbbootlogd needs to open new displays at runtime for graphics + // driver handoff. In the future inputd may directly pass a handle to the display instead. +- //libredox::call::setrens(0, 0).expect("fbbootlogd: failed to enter null namespace"); + + for event in event_queue { +- match event.expect("fbbootlogd: failed to get event").user_data { ++ let event = match event { ++ Ok(e) => e, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to get event: {err}"); ++ continue; ++ } ++ }; ++ match event.user_data { + Source::Scheme => loop { +- match handler +- .process_requests_nonblocking(&mut scheme) +- .expect("fbbootlogd: failed to process requests") +- { +- ControlFlow::Continue(()) => {} +- ControlFlow::Break(()) => break, ++ match handler.process_requests_nonblocking(&mut scheme) { ++ Ok(ControlFlow::Continue(())) => {} ++ Ok(ControlFlow::Break(())) => break, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to process requests: {err}"); ++ break; ++ } + } + }, + Source::Input => { + let mut events = [Event::new(); 16]; + loop { +- match scheme +- .input_handle +- .read_events(&mut events) +- .expect("fbbootlogd: error while reading events") +- { +- ConsumerHandleEvent::Events(&[]) => break, +- ConsumerHandleEvent::Events(events) => { ++ match scheme.input_handle.read_events(&mut events) { ++ Ok(ConsumerHandleEvent::Events(&[])) => break, ++ Ok(ConsumerHandleEvent::Events(events)) => { + for event in events { + scheme.handle_input(&event); + } + } +- ConsumerHandleEvent::Handoff => { ++ Ok(ConsumerHandleEvent::Handoff) => { + eprintln!("fbbootlogd: handoff requested"); + scheme.handle_handoff(); + } ++ Err(err) => { ++ eprintln!("fbbootlogd: error while reading events: {err}"); ++ break; ++ } + } + } + } +diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs +index 812c4a5b..9e1869c3 100644 +--- a/drivers/graphics/fbbootlogd/src/scheme.rs ++++ b/drivers/graphics/fbbootlogd/src/scheme.rs +@@ -26,7 +26,13 @@ pub struct FbbootlogScheme { + impl FbbootlogScheme { + pub fn new() -> FbbootlogScheme { + let mut scheme = FbbootlogScheme { +- input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"), ++ input_handle: match ConsumerHandle::bootlog_vt() { ++ Ok(handle) => handle, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to open vt: {err}"); ++ std::process::exit(1); ++ } ++ }, + display_map: None, + text_screen: console_draw::TextScreen::new(), + text_buffer: console_draw::TextBuffer::new(1000), +@@ -42,7 +48,13 @@ impl FbbootlogScheme { + + pub fn handle_handoff(&mut self) { + let new_display_handle = match self.input_handle.open_display_v2() { +- Ok(display) => V2GraphicsHandle::from_file(display).unwrap(), ++ Ok(display) => match V2GraphicsHandle::from_file(display) { ++ Ok(handle) => handle, ++ Err(err) => { ++ eprintln!("fbbootlogd: failed to create graphics handle: {err}"); ++ return; ++ } ++ }, + Err(err) => { + eprintln!("fbbootlogd: No display present yet: {err}"); + return; +@@ -140,7 +152,9 @@ impl FbbootlogScheme { + total_damage = total_damage.merge(damage); + } + } +- map.dirty_fb(total_damage).unwrap(); ++ if let Err(err) = map.dirty_fb(total_damage) { ++ eprintln!("fbbootlogd: failed to flush scrollback damage: {err}"); ++ } + } + + fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) { +@@ -234,7 +248,9 @@ impl SchemeSync for FbbootlogScheme { + let damage = self.text_screen.write(map, buf, &mut VecDeque::new()); + + if let Some(map) = &mut self.display_map { +- map.dirty_fb(damage).unwrap(); ++ if let Err(err) = map.dirty_fb(damage) { ++ eprintln!("fbbootlogd: failed to flush write damage: {err}"); ++ } + } + } + } +diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs +index eb09b97e..957a6d88 100644 +--- a/drivers/graphics/fbcond/src/display.rs ++++ b/drivers/graphics/fbcond/src/display.rs +@@ -31,7 +31,13 @@ impl Display { + return; + } + }; +- let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap(); ++ let new_display_handle = match V2GraphicsHandle::from_file(display_file) { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("fbcond: failed to create display handle: {err}"); ++ return; ++ } ++ }; + + log::debug!("fbcond: Opened new display"); + +@@ -77,7 +83,9 @@ impl Display { + + pub fn sync_rect(&mut self, damage: Damage) { + if let Some(map) = &mut self.map { +- map.dirty_fb(damage).unwrap(); ++ if let Err(err) = map.dirty_fb(damage) { ++ log::error!("fbcond: failed to sync display rect: {err}"); ++ } + } + } + } +diff --git a/drivers/graphics/fbcond/src/main.rs b/drivers/graphics/fbcond/src/main.rs +index eb4f9add..7acc488f 100644 +--- a/drivers/graphics/fbcond/src/main.rs ++++ b/drivers/graphics/fbcond/src/main.rs +@@ -21,7 +21,15 @@ fn main() { + fn daemon(daemon: daemon::SchemeDaemon) -> ! { + let vt_ids = env::args() + .skip(1) +- .map(|arg| arg.parse().expect("invalid vt number")) ++ .filter_map(|arg| { ++ match arg.parse() { ++ Ok(v) => Some(v), ++ Err(_) => { ++ eprintln!("fbcond: invalid vt number '{}', skipping", arg); ++ None ++ } ++ } ++ }) + .collect::>(); + + common::setup_logging( +@@ -31,18 +39,31 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { + common::output_level(), + common::file_level(), + ); +- let mut event_queue = EventQueue::new().expect("fbcond: failed to create event queue"); ++ let mut event_queue = match EventQueue::new() { ++ Ok(eq) => eq, ++ Err(err) => { ++ eprintln!("fbcond: failed to create event queue: {}", err); ++ std::process::exit(1); ++ } ++ }; + + // FIXME listen for resize events from inputd and handle them + +- let mut socket = Socket::nonblock().expect("fbcond: failed to create fbcon scheme"); +- event_queue +- .subscribe( +- socket.inner().raw(), +- VtIndex::SCHEMA_SENTINEL, +- event::EventFlags::READ, +- ) +- .expect("fbcond: failed to subscribe to scheme events"); ++ let mut socket = match Socket::nonblock() { ++ Ok(s) => s, ++ Err(err) => { ++ eprintln!("fbcond: failed to create fbcon scheme: {}", err); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(err) = event_queue.subscribe( ++ socket.inner().raw(), ++ VtIndex::SCHEMA_SENTINEL, ++ event::EventFlags::READ, ++ ) { ++ eprintln!("fbcond: failed to subscribe to scheme events: {}", err); ++ std::process::exit(1); ++ } + + let mut state = SchemeState::new(); + let mut scheme = FbconScheme::new(&vt_ids, &mut event_queue); +@@ -51,7 +72,6 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { + + // This is not possible for now as fbcond needs to open new displays at runtime for graphics + // driver handoff. In the future inputd may directly pass a handle to the display instead. +- // libredox::call::setrens(0, 0).expect("fbcond: failed to enter null namespace"); + + let mut blocked = Vec::new(); + +@@ -68,7 +88,13 @@ fn daemon(daemon: daemon::SchemeDaemon) -> ! { + } + + for event in event_queue { +- let event = event.expect("fbcond: failed to read event from event queue"); ++ let event = match event { ++ Ok(ev) => ev, ++ Err(err) => { ++ eprintln!("fbcond: failed to read event from event queue: {}", err); ++ continue; ++ } ++ }; + handle_event( + &mut socket, + &mut scheme, +@@ -99,7 +125,10 @@ fn handle_event( + Err(err) if err.errno == EAGAIN => { + break; + } +- Err(err) => panic!("fbcond: failed to read display scheme: {err}"), ++ Err(err) => { ++ eprintln!("fbcond: failed to read display scheme: {err}"); ++ break; ++ } + }; + + match request.kind() { +@@ -108,12 +137,12 @@ fn handle_event( + let mut op = match req.op() { + Ok(op) => op, + Err(req) => { +- let _ = socket +- .write_response( +- Response::err(EOPNOTSUPP, req), +- SignalBehavior::Restart, +- ) +- .expect("fbcond: failed to write responses to fbcon scheme"); ++ if let Err(err) = socket.write_response( ++ Response::err(EOPNOTSUPP, req), ++ SignalBehavior::Restart, ++ ) { ++ eprintln!("fbcond: failed to write response: {}", err); ++ } + continue; + } + }; +@@ -125,25 +154,27 @@ fn handle_event( + blocked.push((op, caller)); + } + SchemeResponse::Regular(r) => { +- let _ = socket ++ if let Err(err) = socket + .write_response(Response::new(r, op), SignalBehavior::Restart) +- .expect("fbcond: failed to write responses to fbcon scheme"); ++ { ++ eprintln!("fbcond: failed to write response: {}", err); ++ } + } + SchemeResponse::Opened(o) => { +- let _ = socket +- .write_response( +- Response::open_dup_like(o, op), +- SignalBehavior::Restart, +- ) +- .expect("fbcond: failed to write responses to fbcon scheme"); ++ if let Err(err) = socket.write_response( ++ Response::open_dup_like(o, op), ++ SignalBehavior::Restart, ++ ) { ++ eprintln!("fbcond: failed to write response: {}", err); ++ } + } + SchemeResponse::RegularAndNotifyOnDetach(status) => { +- let _ = socket +- .write_response( +- Response::new_notify_on_detach(status, op), +- SignalBehavior::Restart, +- ) +- .expect("fbcond: failed to write scheme"); ++ if let Err(err) = socket.write_response( ++ Response::new_notify_on_detach(status, op), ++ SignalBehavior::Restart, ++ ) { ++ eprintln!("fbcond: failed to write response: {}", err); ++ } + } + } + } +@@ -157,25 +188,32 @@ fn handle_event( + { + let (blocked_req, _) = blocked.remove(i); + let resp = Response::err(EINTR, blocked_req); +- socket +- .write_response(resp, SignalBehavior::Restart) +- .expect("vesad: failed to write display scheme"); ++ if let Err(err) = ++ socket.write_response(resp, SignalBehavior::Restart) ++ { ++ eprintln!("fbcond: failed to write cancellation response: {}", err); ++ } + } + } + _ => {} + } + }, + vt_i => { +- let vt = scheme.vts.get_mut(&vt_i).unwrap(); ++ let Some(vt) = scheme.vts.get_mut(&vt_i) else { ++ eprintln!("fbcond: unknown vt index {:?}", vt_i); ++ return; ++ }; + + let mut events = [Event::new(); 16]; + loop { +- match vt +- .display +- .input_handle +- .read_events(&mut events) +- .expect("fbcond: Error while reading events") +- { ++ let read_result = match vt.display.input_handle.read_events(&mut events) { ++ Ok(r) => r, ++ Err(err) => { ++ eprintln!("fbcond: error while reading events: {}", err); ++ break; ++ } ++ }; ++ match read_result { + ConsumerHandleEvent::Events(&[]) => break, + + ConsumerHandleEvent::Events(events) => { +@@ -193,9 +231,9 @@ fn handle_event( + { + let mut i = 0; + while i < blocked.len() { +- let (op, caller) = blocked +- .get_mut(i) +- .expect("vesad: Failed to get blocked request"); ++ let Some((op, caller)) = blocked.get_mut(i) else { ++ break; ++ }; + let resp = match op.handle_sync_dont_consume(&caller, scheme, state) { + SchemeResponse::Opened(Err(e)) | SchemeResponse::Regular(Err(e)) + if libredox::error::Error::from(e).is_wouldblock() +@@ -217,9 +255,9 @@ fn handle_event( + Response::new_notify_on_detach(status, op) + } + }; +- let _ = socket +- .write_response(resp, SignalBehavior::Restart) +- .expect("vesad: failed to write display scheme"); ++ if let Err(err) = socket.write_response(resp, SignalBehavior::Restart) { ++ eprintln!("fbcond: failed to write blocked response: {}", err); ++ } + } + } + +@@ -242,9 +280,9 @@ fn handle_event( + if !handle.notified_read { + handle.notified_read = true; + let response = Response::post_fevent(*handle_id, EVENT_READ.bits()); +- socket +- .write_response(response, SignalBehavior::Restart) +- .expect("fbcond: failed to write display event"); ++ if let Err(err) = socket.write_response(response, SignalBehavior::Restart) { ++ eprintln!("fbcond: failed to write display event: {}", err); ++ } + } + } else { + handle.notified_read = false; +diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs +index 1bee134e..973ff31e 100644 +--- a/drivers/graphics/fbcond/src/scheme.rs ++++ b/drivers/graphics/fbcond/src/scheme.rs +@@ -6,7 +6,7 @@ use redox_scheme::scheme::SchemeSync; + use redox_scheme::{CallerCtx, OpenResult}; + use scheme_utils::{FpathWriter, HandleMap}; + use syscall::schemev2::NewFdFlags; +-use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK}; ++use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, EINVAL, ENOENT, O_NONBLOCK}; + + use crate::display::Display; + use crate::text::TextScreen; +@@ -50,14 +50,21 @@ impl FbconScheme { + let mut vts = BTreeMap::new(); + + for &vt_i in vt_ids { +- let display = Display::open_new_vt().expect("Failed to open display for vt"); +- event_queue +- .subscribe( +- display.input_handle.event_handle().as_raw_fd() as usize, +- VtIndex(vt_i), +- event::EventFlags::READ, +- ) +- .expect("Failed to subscribe to input events for vt"); ++ let display = match Display::open_new_vt() { ++ Ok(d) => d, ++ Err(err) => { ++ eprintln!("fbcond: failed to open display for vt {}: {}", vt_i, err); ++ continue; ++ } ++ }; ++ if let Err(err) = event_queue.subscribe( ++ display.input_handle.event_handle().as_raw_fd() as usize, ++ VtIndex(vt_i), ++ event::EventFlags::READ, ++ ) { ++ eprintln!("fbcond: failed to subscribe to input events for vt {}: {}", vt_i, err); ++ continue; ++ } + vts.insert(VtIndex(vt_i), TextScreen::new(display)); + } + +@@ -127,7 +134,7 @@ impl SchemeSync for FbconScheme { + fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + FpathWriter::with_legacy(buf, "fbcon", |w| { + let handle = self.get_vt_handle_mut(id)?; +- write!(w, "{}", handle.vt_i.0).unwrap(); ++ write!(w, "{}", handle.vt_i.0).map_err(|_| Error::new(EINVAL))?; + Ok(()) + }) + } +diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs +index 8a24bbeb..c7272ab7 100644 +--- a/drivers/graphics/fbcond/src/text.rs ++++ b/drivers/graphics/fbcond/src/text.rs +@@ -113,7 +113,7 @@ impl TextScreen { + let mut i = 0; + + while i < buf.len() && !self.input.is_empty() { +- buf[i] = self.input.pop_front().unwrap(); ++ buf[i] = self.input.pop_front().unwrap_or(0); + i += 1; + } + +diff --git a/drivers/graphics/graphics-ipc/src/lib.rs b/drivers/graphics/graphics-ipc/src/lib.rs +index 285b3043..7451c90a 100644 +--- a/drivers/graphics/graphics-ipc/src/lib.rs ++++ b/drivers/graphics/graphics-ipc/src/lib.rs +@@ -29,12 +29,16 @@ impl drm::control::Device for V2GraphicsHandle {} + impl V2GraphicsHandle { + pub fn from_file(file: File) -> io::Result { + let handle = V2GraphicsHandle { file }; +- assert!(handle.get_driver_capability(DriverCapability::DumbBuffer)? == 1); ++ if handle.get_driver_capability(DriverCapability::DumbBuffer)? != 1 { ++ return Err(io::Error::other( ++ "graphics device does not support dumb buffers", ++ )); ++ } + Ok(handle) + } + + pub fn first_display(&self) -> io::Result { +- for &connector in self.resource_handles().unwrap().connectors() { ++ for &connector in self.resource_handles()?.connectors() { + if self.get_connector(connector, true)?.state() == State::Connected { + return Ok(connector); + } +@@ -95,13 +99,28 @@ impl CpuBackedBuffer { + return; // No shadow buffer; all writes are already propagated to the GPU. + }; + +- assert!(x.checked_add(width).unwrap() <= self.buffer.size().0); +- assert!(y.checked_add(height).unwrap() <= self.buffer.size().1); ++ let Some(x_end) = x.checked_add(width) else { ++ return; ++ }; ++ let Some(y_end) = y.checked_add(height) else { ++ return; ++ }; ++ if x_end > self.buffer.size().0 || y_end > self.buffer.size().1 { ++ return; ++ } + +- let start_x: usize = x.try_into().unwrap(); +- let start_y: usize = y.try_into().unwrap(); +- let w: usize = width.try_into().unwrap(); +- let h: usize = height.try_into().unwrap(); ++ let Ok(start_x) = usize::try_from(x) else { ++ return; ++ }; ++ let Ok(start_y) = usize::try_from(y) else { ++ return; ++ }; ++ let Ok(w) = usize::try_from(width) else { ++ return; ++ }; ++ let Ok(h) = usize::try_from(height) else { ++ return; ++ }; + + let offscreen_ptr = shadow.as_ptr().cast::(); + let onscreen_ptr = self.map.as_mut_ptr().cast::(); diff --git a/drivers/graphics/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml index acbb4e78..210731ae 100644 --- a/drivers/graphics/ihdgd/config.toml @@ -3694,11 +5583,228 @@ index acbb4e78..210731ae 100644 + 0x7D51, 0x7DD1, ] } command = ["ihdgd"] +diff --git a/drivers/graphics/ihdgd/src/device/ddi.rs b/drivers/graphics/ihdgd/src/device/ddi.rs +index ac4ce1bd..b851d169 100644 +--- a/drivers/graphics/ihdgd/src/device/ddi.rs ++++ b/drivers/graphics/ihdgd/src/device/ddi.rs +@@ -347,9 +347,12 @@ impl Ddi { + + // Last setting is the default + //TODO: get correct setting index from BIOS +- let setting = settings.last().unwrap(); ++ let Some(setting) = settings.last() else { ++ log::error!("no voltage swing settings available"); ++ return Err(Error::new(EIO)); ++ }; + +- // This allows unwraps on port functions below without panic ++ // All port registers below require port_base to be set (checked above) + if self.port_base.is_none() { + log::error!("HDMI voltage swing procedure only implemented on combo DDI"); + return Err(Error::new(EIO)); +@@ -358,9 +361,15 @@ impl Ddi { + // Clear cmnkeeper_enable for HDMI + { + // It is not possible to read from GRP register, so use LN0 as template +- let pcs_dw1_ln0 = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0).unwrap(); +- let mut pcs_dw1_grp = +- WriteOnly::new(self.port_pcs(PortPcsReg::Dw1, PortLane::Grp).unwrap()); ++ let Some(pcs_dw1_ln0) = self.port_pcs(PortPcsReg::Dw1, PortLane::Ln0) else { ++ log::error!("failed to get PCS_DW1_LN0 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; ++ let Some(pcs_dw1_grp_raw) = self.port_pcs(PortPcsReg::Dw1, PortLane::Grp) else { ++ log::error!("failed to get PCS_DW1_GRP for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; ++ let mut pcs_dw1_grp = WriteOnly::new(pcs_dw1_grp_raw); + let mut v = pcs_dw1_ln0.read(); + v &= !PORT_PCS_DW1_CMNKEEPER_ENABLE; + pcs_dw1_grp.write(v); +@@ -369,28 +378,50 @@ impl Ddi { + // Program loadgen select + //TODO: this assumes bit rate <= 6 GHz and 4 lanes enabled + { +- let mut tx_dw4_ln0 = self.port_tx(PortTxReg::Dw4, PortLane::Ln0).unwrap(); ++ let Some(mut tx_dw4_ln0) = self.port_tx(PortTxReg::Dw4, PortLane::Ln0) else { ++ log::error!("failed to get TX_DW4_LN0 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + tx_dw4_ln0.writef(PORT_TX_DW4_SELECT, false); + +- let mut tx_dw4_ln1 = self.port_tx(PortTxReg::Dw4, PortLane::Ln1).unwrap(); ++ let Some(mut tx_dw4_ln1) = self.port_tx(PortTxReg::Dw4, PortLane::Ln1) else { ++ log::error!("failed to get TX_DW4_LN1 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + tx_dw4_ln1.writef(PORT_TX_DW4_SELECT, true); + +- let mut tx_dw4_ln2 = self.port_tx(PortTxReg::Dw4, PortLane::Ln2).unwrap(); ++ let Some(mut tx_dw4_ln2) = self.port_tx(PortTxReg::Dw4, PortLane::Ln2) else { ++ log::error!("failed to get TX_DW4_LN2 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + tx_dw4_ln2.writef(PORT_TX_DW4_SELECT, true); + +- let mut tx_dw4_ln3 = self.port_tx(PortTxReg::Dw4, PortLane::Ln3).unwrap(); ++ let Some(mut tx_dw4_ln3) = self.port_tx(PortTxReg::Dw4, PortLane::Ln3) else { ++ log::error!("failed to get TX_DW4_LN3 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + tx_dw4_ln3.writef(PORT_TX_DW4_SELECT, true); + } + + // Set PORT_CL_DW5 sus clock config to 11b + { +- let mut cl_dw5 = self.port_cl(PortClReg::Dw5).unwrap(); ++ let Some(mut cl_dw5) = self.port_cl(PortClReg::Dw5) else { ++ log::error!("failed to get CL_DW5 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + cl_dw5.writef(PORT_CL_DW5_SUS_CLOCK_MASK, true); + } + + // Clear training enable to change swing values +- let tx_dw5_ln0 = self.port_tx(PortTxReg::Dw5, PortLane::Ln0).unwrap(); +- let mut tx_dw5_grp = WriteOnly::new(self.port_tx(PortTxReg::Dw5, PortLane::Grp).unwrap()); ++ let Some(tx_dw5_ln0) = self.port_tx(PortTxReg::Dw5, PortLane::Ln0) else { ++ log::error!("failed to get TX_DW5_LN0 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; ++ let Some(tx_dw5_grp_raw) = self.port_tx(PortTxReg::Dw5, PortLane::Grp) else { ++ log::error!("failed to get TX_DW5_GRP for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; ++ let mut tx_dw5_grp = WriteOnly::new(tx_dw5_grp_raw); + { + let mut v = tx_dw5_ln0.read(); + v &= !PORT_TX_DW5_TRAINING_ENABLE; +@@ -400,7 +431,10 @@ impl Ddi { + // Program swing and de-emphasis + + // Disable eDP bits in PORT_CL_DW10 +- let mut cl_dw10 = self.port_cl(PortClReg::Dw10).unwrap(); ++ let Some(mut cl_dw10) = self.port_cl(PortClReg::Dw10) else { ++ log::error!("failed to get CL_DW10 for DDI {}", self.name); ++ return Err(Error::new(EIO)); ++ }; + cl_dw10.writef( + PORT_CL_DW10_EDP4K2K_MODE_OVRD_EN | PORT_CL_DW10_EDP4K2K_MODE_OVRD_VAL, + false, +@@ -435,7 +469,10 @@ impl Ddi { + // - Set swing sel from settings + // - Set rcomp scalar to 0x98 + for lane in lanes { +- let mut tx_dw2 = self.port_tx(PortTxReg::Dw2, lane).unwrap(); ++ let Some(mut tx_dw2) = self.port_tx(PortTxReg::Dw2, lane) else { ++ log::error!("failed to get TX_DW2 for {:?} on DDI {}", lane, self.name); ++ continue; ++ }; + let mut v = tx_dw2.read(); + v &= !(PORT_TX_DW2_SWING_SEL_UPPER_MASK + | PORT_TX_DW2_SWING_SEL_LOWER_MASK +@@ -451,7 +488,10 @@ impl Ddi { + // - Set post cursor 2 to 0x0 + // - Set cursor coeff from settings + for lane in lanes { +- let mut tx_dw4 = self.port_tx(PortTxReg::Dw4, lane).unwrap(); ++ let Some(mut tx_dw4) = self.port_tx(PortTxReg::Dw4, lane) else { ++ log::error!("failed to get TX_DW4 for {:?} on DDI {}", lane, self.name); ++ continue; ++ }; + let mut v = tx_dw4.read(); + v &= !(PORT_TX_DW4_POST_CURSOR_1_MASK + | PORT_TX_DW4_POST_CURSOR_2_MASK +@@ -464,7 +504,10 @@ impl Ddi { + // For PORT_TX_DW7: + // - Set n scalar from settings + for lane in lanes { +- let mut tx_dw7 = self.port_tx(PortTxReg::Dw7, lane).unwrap(); ++ let Some(mut tx_dw7) = self.port_tx(PortTxReg::Dw7, lane) else { ++ log::error!("failed to get TX_DW7 for {:?} on DDI {}", lane, self.name); ++ continue; ++ }; + // All other bits are spare + tx_dw7.write(setting.dw7_n_scalar << PORT_TX_DW7_N_SCALAR_SHIFT); + } +diff --git a/drivers/graphics/ihdgd/src/device/ggtt.rs b/drivers/graphics/ihdgd/src/device/ggtt.rs +index 5e39827a..0ec5358b 100644 +--- a/drivers/graphics/ihdgd/src/device/ggtt.rs ++++ b/drivers/graphics/ihdgd/src/device/ggtt.rs +@@ -3,7 +3,7 @@ use std::{mem, ptr}; + + use pcid_interface::PciFunctionHandle; + use range_alloc::RangeAllocator; +-use syscall::{Error, EIO}; ++use syscall::{Error, EIO, EINVAL}; + + use crate::device::MmioRegion; + +@@ -88,20 +88,36 @@ impl GlobalGtt { + } + } + +- pub fn reserve(&mut self, surf: u32, surf_size: u32) { +- assert!(surf.is_multiple_of(GTT_PAGE_SIZE)); +- assert!(surf_size.is_multiple_of(GTT_PAGE_SIZE)); ++ pub fn reserve(&mut self, surf: u32, surf_size: u32) -> syscall::Result<()> { ++ if !surf.is_multiple_of(GTT_PAGE_SIZE) { ++ log::error!( ++ "reserve: surface address 0x{:x} is not aligned to GTT page size", ++ surf ++ ); ++ return Err(Error::new(EINVAL)); ++ } ++ if !surf_size.is_multiple_of(GTT_PAGE_SIZE) { ++ log::error!( ++ "reserve: surface size 0x{:x} is not aligned to GTT page size", ++ surf_size ++ ); ++ return Err(Error::new(EINVAL)); ++ } + +- self.gm_alloc +- .allocate_exact_range( +- surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, +- ) +- .unwrap_or_else(|err| { +- panic!( ++ match self.gm_alloc.allocate_exact_range( ++ surf / GTT_PAGE_SIZE..surf / GTT_PAGE_SIZE + surf_size / GTT_PAGE_SIZE, ++ ) { ++ Ok(_range) => Ok(()), ++ Err(err) => { ++ log::error!( + "failed to allocate pre-existing surface at 0x{:x} of size {}: {:?}", +- surf, surf_size, err ++ surf, ++ surf_size, ++ err + ); +- }); ++ Err(Error::new(EIO)) ++ } ++ } + } + + pub fn alloc_phys_mem(&mut self, size: u32) -> syscall::Result { diff --git a/drivers/graphics/ihdgd/src/device/mod.rs b/drivers/graphics/ihdgd/src/device/mod.rs -index ced9dd56..0dc2e659 100644 +index ced9dd56..fc2a1108 100644 --- a/drivers/graphics/ihdgd/src/device/mod.rs +++ b/drivers/graphics/ihdgd/src/device/mod.rs -@@ -246,7 +246,9 @@ impl Device { +@@ -51,8 +51,9 @@ impl<'a, T, F: FnOnce(&mut T)> CallbackGuard<'a, T, F> { + + impl<'a, T, F: FnOnce(&mut T)> Drop for CallbackGuard<'a, T, F> { + fn drop(&mut self) { +- let fini = self.fini.take().unwrap(); +- fini(&mut self.value); ++ if let Some(fini) = self.fini.take() { ++ fini(&mut self.value); ++ } + } + } + +@@ -246,7 +247,9 @@ impl Device { }; let gttmm = { @@ -3709,7 +5815,7 @@ index ced9dd56..0dc2e659 100644 Arc::new(MmioRegion::new( phys, size, -@@ -255,7 +257,9 @@ impl Device { +@@ -255,7 +258,9 @@ impl Device { }; log::info!("GTTMM {:X?}", gttmm); let gm = { @@ -3720,8 +5826,175 @@ index ced9dd56..0dc2e659 100644 MmioRegion::new(phys, size, common::MemoryType::WriteCombining)? }; log::info!("GM {:X?}", gm); +@@ -453,7 +458,12 @@ impl Device { + // Probe all DDIs + let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect(); + for ddi_name in ddi_names { +- self.probe_ddi(ddi_name).expect("failed to probe DDI"); ++ match self.probe_ddi(ddi_name) { ++ Ok(_) => {} ++ Err(err) => { ++ log::error!("failed to probe DDI {}: {}", ddi_name, err); ++ } ++ } + } + + self.dump(); +diff --git a/drivers/graphics/ihdgd/src/device/pipe.rs b/drivers/graphics/ihdgd/src/device/pipe.rs +index 0e99ffe4..779e4c5f 100644 +--- a/drivers/graphics/ihdgd/src/device/pipe.rs ++++ b/drivers/graphics/ihdgd/src/device/pipe.rs +@@ -76,14 +76,12 @@ impl Plane { + let buf_cfg = self.buf_cfg.read(); + let buffer_start = buf_cfg & 0x7FF; + let buffer_end = (buf_cfg >> 16) & 0x7FF; +- alloc_buffers +- .allocate_exact_range(buffer_start..(buffer_end + 1)) +- .unwrap_or_else(|err| { +- panic!( +- "failed to allocate pre-existing buffer blocks {} to {}: {:?}", +- buffer_start, buffer_end, err +- ); +- }); ++ if let Err(err) = alloc_buffers.allocate_exact_range(buffer_start..(buffer_end + 1)) { ++ log::error!( ++ "failed to allocate pre-existing buffer blocks {} to {}: {:?}", ++ buffer_start, buffer_end, err ++ ); ++ } + } + + pub fn modeset(&mut self, alloc_buffers: &mut RangeAllocator) -> syscall::Result<()> { +@@ -122,7 +120,13 @@ impl Plane { + let surf = self.surf.read() & 0xFFFFF000; + //TODO: read bits per pixel + let surf_size = (stride * height).next_multiple_of(4096); +- ggtt.reserve(surf, surf_size); ++ ggtt.reserve(surf, surf_size).unwrap_or_else(|err| { ++ log::warn!( ++ "failed to reserve GTT entries for existing framebuffer at 0x{:x}: {}", ++ surf, ++ err ++ ); ++ }); + + unsafe { DeviceFb::new(gm, surf, width, height, stride, true) } + } +diff --git a/drivers/graphics/ihdgd/src/device/scheme.rs b/drivers/graphics/ihdgd/src/device/scheme.rs +index 95db5bbf..3554a35e 100644 +--- a/drivers/graphics/ihdgd/src/device/scheme.rs ++++ b/drivers/graphics/ihdgd/src/device/scheme.rs +@@ -68,7 +68,20 @@ impl GraphicsAdapter for Device { + } + + fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { +- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); ++ let connector_guard = match objects.get_connector(id) { ++ Ok(guard) => guard, ++ Err(e) => { ++ log::error!("probe_connector: connector {:?} not found: {}", id, e); ++ return; ++ } ++ }; ++ let mut connector = match connector_guard.lock() { ++ Ok(guard) => guard, ++ Err(err) => { ++ log::error!("probe_connector: failed to lock connector {:?}: {}", id, err); ++ return; ++ } ++ }; + let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id]; + connector.connection = KmsConnectorStatus::Connected; + connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32); +@@ -94,7 +107,10 @@ impl GraphicsAdapter for Device { + state: KmsCrtcState, + damage: Damage, + ) -> syscall::Result<()> { +- let mut crtc = crtc.lock().unwrap(); ++ let mut crtc = crtc.lock().map_err(|err| { ++ log::error!("set_crtc: failed to lock crtc: {}", err); ++ syscall::Error::new(EINVAL) ++ })?; + let buffer = state + .fb_id + .map(|fb_id| objects.get_framebuffer(fb_id)) +@@ -102,7 +118,13 @@ impl GraphicsAdapter for Device { + crtc.state = state; + + for connector in objects.connectors() { +- let connector = connector.lock().unwrap(); ++ let connector = match connector.lock() { ++ Ok(c) => c, ++ Err(err) => { ++ log::error!("set_crtc: failed to lock connector: {}", err); ++ continue; ++ } ++ }; + + if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { + continue; +@@ -161,9 +183,9 @@ impl DumbFb { + fn layout(len: usize) -> Layout { + // optimizes to an integer mul + Layout::array::(len) +- .unwrap() ++ .unwrap_or_else(|_| Layout::from_size_align(len * 4, PAGE_SIZE).unwrap_or(Layout::new::())) + .align_to(PAGE_SIZE) +- .unwrap() ++ .unwrap_or_else(|_| Layout::new::().align_to(PAGE_SIZE).unwrap_or(Layout::new::())) + } + } + +@@ -182,15 +204,38 @@ impl Buffer for DumbFb { + + impl DumbFb { + fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) { +- let sync_rect = sync_rect.clip( +- self.width.try_into().unwrap(), +- self.height.try_into().unwrap(), +- ); +- +- let start_x: usize = sync_rect.x.try_into().unwrap(); +- let start_y: usize = sync_rect.y.try_into().unwrap(); +- let w: usize = sync_rect.width.try_into().unwrap(); +- let h: usize = sync_rect.height.try_into().unwrap(); ++ let fb_w: u32 = match self.width.try_into() { ++ Ok(v) => v, ++ Err(_) => { ++ log::error!("sync: framebuffer width {} overflow", self.width); ++ return; ++ } ++ }; ++ let fb_h: u32 = match self.height.try_into() { ++ Ok(v) => v, ++ Err(_) => { ++ log::error!("sync: framebuffer height {} overflow", self.height); ++ return; ++ } ++ }; ++ let sync_rect = sync_rect.clip(fb_w, fb_h); ++ ++ let start_x: usize = match sync_rect.x.try_into() { ++ Ok(v) => v, ++ Err(_) => return, ++ }; ++ let start_y: usize = match sync_rect.y.try_into() { ++ Ok(v) => v, ++ Err(_) => return, ++ }; ++ let w: usize = match sync_rect.width.try_into() { ++ Ok(v) => v, ++ Err(_) => return, ++ }; ++ let h: usize = match sync_rect.height.try_into() { ++ Ok(v) => v, ++ Err(_) => return, ++ }; + + let offscreen_ptr = self.ptr.as_ptr() as *mut u32; + let onscreen_ptr = framebuffer.buffer.virt.cast::(); diff --git a/drivers/graphics/ihdgd/src/main.rs b/drivers/graphics/ihdgd/src/main.rs -index a8b6cc60..e468aca7 100644 +index a8b6cc60..84d58a3e 100644 --- a/drivers/graphics/ihdgd/src/main.rs +++ b/drivers/graphics/ihdgd/src/main.rs @@ -1,6 +1,6 @@ @@ -3732,7 +6005,7 @@ index a8b6cc60..e468aca7 100644 use std::{ io::{Read, Write}, os::fd::AsRawFd, -@@ -29,10 +29,21 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { +@@ -29,16 +29,32 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { log::info!("IHDG {}", pci_config.func.display()); @@ -3757,8 +6030,415 @@ index a8b6cc60..e468aca7 100644 // Needs to be before GraphicsScheme::new to avoid a deadlock due to initnsmgr blocking on // /scheme/event as it is already blocked on opening /scheme/display.ihdg.*. + // FIXME change the initnsmgr to not block on openat for the target scheme. +- let event_queue: EventQueue = +- EventQueue::new().expect("ihdgd: failed to create event queue"); ++ let event_queue: EventQueue = match EventQueue::new() { ++ Ok(eq) => eq, ++ Err(err) => { ++ log::error!("ihdgd: failed to create event queue: {err}"); ++ std::process::exit(1); ++ } ++ }; + + let mut scheme = GraphicsScheme::new(device, format!("display.ihdg.{}", name), false); + +@@ -50,53 +66,69 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + } + } + +- event_queue +- .subscribe( +- scheme.inputd_event_handle().as_raw_fd() as usize, +- Source::Input, +- event::EventFlags::READ, +- ) +- .unwrap(); +- event_queue +- .subscribe( +- irq_file.irq_handle().as_raw_fd() as usize, +- Source::Irq, +- event::EventFlags::READ, +- ) +- .unwrap(); +- event_queue +- .subscribe( +- scheme.event_handle().raw(), +- Source::Scheme, +- event::EventFlags::READ, +- ) +- .unwrap(); +- +- libredox::call::setrens(0, 0).expect("ihdgd: failed to enter null namespace"); ++ if let Err(err) = event_queue.subscribe( ++ scheme.inputd_event_handle().as_raw_fd() as usize, ++ Source::Input, ++ event::EventFlags::READ, ++ ) { ++ log::error!("ihdgd: failed to subscribe to input events: {err}"); ++ } ++ if let Err(err) = event_queue.subscribe( ++ irq_file.irq_handle().as_raw_fd() as usize, ++ Source::Irq, ++ event::EventFlags::READ, ++ ) { ++ log::error!("ihdgd: failed to subscribe to IRQ events: {err}"); ++ } ++ if let Err(err) = event_queue.subscribe( ++ scheme.event_handle().raw(), ++ Source::Scheme, ++ event::EventFlags::READ, ++ ) { ++ log::error!("ihdgd: failed to subscribe to scheme events: {err}"); ++ } ++ ++ if let Err(err) = libredox::call::setrens(0, 0) { ++ log::error!("ihdgd: failed to enter null namespace: {err}"); ++ std::process::exit(1); ++ } + + daemon.ready(); + + let all = [Source::Input, Source::Irq, Source::Scheme]; +- for event in all +- .into_iter() +- .chain(event_queue.map(|e| e.expect("ihdgd: failed to get next event").user_data)) +- { ++ for event in all.into_iter().chain( ++ event_queue.filter_map(|e| match e { ++ Ok(event) => Some(event.user_data), ++ Err(err) => { ++ log::error!("ihdgd: failed to get next event: {err}"); ++ None ++ } ++ }), ++ ) { + match event { + Source::Input => scheme.handle_vt_events(), + Source::Irq => { + let mut irq = [0; 8]; +- irq_file.irq_handle().read(&mut irq).unwrap(); ++ if irq_file.irq_handle().read(&mut irq).is_err() { ++ log::error!("ihdgd: failed to read IRQ"); ++ continue; ++ } + if scheme.adapter_mut().handle_irq() { +- irq_file.irq_handle().write(&mut irq).unwrap(); ++ if let Err(err) = irq_file.irq_handle().write(&mut irq) { ++ log::error!("ihdgd: failed to write IRQ: {err}"); ++ continue; ++ } + + scheme.adapter_mut().handle_events(); +- scheme.tick().unwrap(); ++ if let Err(err) = scheme.tick() { ++ log::error!("ihdgd: failed to handle display events after IRQ: {err}"); ++ } + } + } + Source::Scheme => { +- scheme +- .tick() +- .expect("ihdgd: failed to handle scheme events"); ++ if let Err(err) = scheme.tick() { ++ log::error!("ihdgd: failed to handle scheme events: {err}"); ++ } + } + } + } +diff --git a/drivers/graphics/vesad/src/main.rs b/drivers/graphics/vesad/src/main.rs +index a4c07d1e..41faa0e2 100644 +--- a/drivers/graphics/vesad/src/main.rs ++++ b/drivers/graphics/vesad/src/main.rs +@@ -23,25 +23,49 @@ fn daemon(daemon: daemon::Daemon) -> ! { + } + + let width = usize::from_str_radix( +- &env::var("FRAMEBUFFER_WIDTH").expect("FRAMEBUFFER_WIDTH not set"), ++ &env::var("FRAMEBUFFER_WIDTH").unwrap_or_else(|_| { ++ eprintln!("vesad: FRAMEBUFFER_WIDTH not set"); ++ std::process::exit(1); ++ }), + 16, + ) +- .expect("failed to parse FRAMEBUFFER_WIDTH"); ++ .unwrap_or_else(|err| { ++ eprintln!("vesad: failed to parse FRAMEBUFFER_WIDTH: {}", err); ++ std::process::exit(1); ++ }); + let height = usize::from_str_radix( +- &env::var("FRAMEBUFFER_HEIGHT").expect("FRAMEBUFFER_HEIGHT not set"), ++ &env::var("FRAMEBUFFER_HEIGHT").unwrap_or_else(|_| { ++ eprintln!("vesad: FRAMEBUFFER_HEIGHT not set"); ++ std::process::exit(1); ++ }), + 16, + ) +- .expect("failed to parse FRAMEBUFFER_HEIGHT"); ++ .unwrap_or_else(|err| { ++ eprintln!("vesad: failed to parse FRAMEBUFFER_HEIGHT: {}", err); ++ std::process::exit(1); ++ }); + let phys = usize::from_str_radix( +- &env::var("FRAMEBUFFER_ADDR").expect("FRAMEBUFFER_ADDR not set"), ++ &env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| { ++ eprintln!("vesad: FRAMEBUFFER_ADDR not set"); ++ std::process::exit(1); ++ }), + 16, + ) +- .expect("failed to parse FRAMEBUFFER_ADDR"); ++ .unwrap_or_else(|err| { ++ eprintln!("vesad: failed to parse FRAMEBUFFER_ADDR: {}", err); ++ std::process::exit(1); ++ }); + let stride = usize::from_str_radix( +- &env::var("FRAMEBUFFER_STRIDE").expect("FRAMEBUFFER_STRIDE not set"), ++ &env::var("FRAMEBUFFER_STRIDE").unwrap_or_else(|_| { ++ eprintln!("vesad: FRAMEBUFFER_STRIDE not set"); ++ std::process::exit(1); ++ }), + 16, + ) +- .expect("failed to parse FRAMEBUFFER_STRIDE"); ++ .unwrap_or_else(|err| { ++ eprintln!("vesad: failed to parse FRAMEBUFFER_STRIDE: {}", err); ++ std::process::exit(1); ++ }); + + println!( + "vesad: {}x{} stride {} at 0x{:X}", +@@ -57,14 +81,20 @@ fn daemon(daemon: daemon::Daemon) -> ! { + let mut framebuffers = vec![unsafe { FrameBuffer::new(phys, width, height, stride) }]; + + //TODO: ideal maximum number of outputs? +- let bootloader_env = std::fs::read_to_string("/scheme/sys/env") +- .expect("failed to read env") +- .lines() +- .map(|line| { +- let (env, value) = line.split_once('=').unwrap(); +- (env.to_owned(), value.to_owned()) +- }) +- .collect::>(); ++ let bootloader_env: HashMap = ++ match std::fs::read_to_string("/scheme/sys/env") { ++ Ok(content) => content ++ .lines() ++ .filter_map(|line| { ++ let (env, value) = line.split_once('=')?; ++ Some((env.to_owned(), value.to_owned())) ++ }) ++ .collect(), ++ Err(err) => { ++ eprintln!("vesad: failed to read bootloader env: {}", err); ++ HashMap::new() ++ } ++ }; + for i in 1..1024 { + match bootloader_env.get(&format!("FRAMEBUFFER{}", i)) { + Some(var) => match unsafe { FrameBuffer::parse(&var) } { +@@ -93,38 +123,51 @@ fn daemon(daemon: daemon::Daemon) -> ! { + } + } + +- let event_queue: EventQueue = +- EventQueue::new().expect("vesad: failed to create event queue"); +- event_queue +- .subscribe( +- scheme.inputd_event_handle().as_raw_fd() as usize, +- Source::Input, +- event::EventFlags::READ, +- ) +- .unwrap(); +- event_queue +- .subscribe( +- scheme.event_handle().raw(), +- Source::Scheme, +- event::EventFlags::READ, +- ) +- .unwrap(); +- +- libredox::call::setrens(0, 0).expect("vesad: failed to enter null namespace"); ++ let event_queue: EventQueue = match EventQueue::new() { ++ Ok(eq) => eq, ++ Err(err) => { ++ eprintln!("vesad: failed to create event queue: {}", err); ++ daemon.ready(); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(err) = event_queue.subscribe( ++ scheme.inputd_event_handle().as_raw_fd() as usize, ++ Source::Input, ++ event::EventFlags::READ, ++ ) { ++ eprintln!("vesad: failed to subscribe to input events: {}", err); ++ } ++ if let Err(err) = event_queue.subscribe( ++ scheme.event_handle().raw(), ++ Source::Scheme, ++ event::EventFlags::READ, ++ ) { ++ eprintln!("vesad: failed to subscribe to scheme events: {}", err); ++ } ++ ++ if let Err(err) = libredox::call::setrens(0, 0) { ++ eprintln!("vesad: failed to enter null namespace: {}", err); ++ } + + daemon.ready(); + + let all = [Source::Input, Source::Scheme]; +- for event in all +- .into_iter() +- .chain(event_queue.map(|e| e.expect("vesad: failed to get next event").user_data)) +- { ++ for event in all.into_iter().chain(event_queue.filter_map(|e| { ++ match e { ++ Ok(ev) => Some(ev.user_data), ++ Err(err) => { ++ eprintln!("vesad: failed to get next event: {}", err); ++ None ++ } ++ } ++ })) { + match event { + Source::Input => scheme.handle_vt_events(), + Source::Scheme => { +- scheme +- .tick() +- .expect("vesad: failed to handle scheme events"); ++ if let Err(err) = scheme.tick() { ++ eprintln!("vesad: failed to handle scheme events: {}", err); ++ } + } + } + } +diff --git a/drivers/graphics/vesad/src/scheme.rs b/drivers/graphics/vesad/src/scheme.rs +index 5bf2be91..20d755d2 100644 +--- a/drivers/graphics/vesad/src/scheme.rs ++++ b/drivers/graphics/vesad/src/scheme.rs +@@ -74,7 +74,17 @@ impl GraphicsAdapter for FbAdapter { + } + + fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { +- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); ++ let connector_mutex = match objects.get_connector(id) { ++ Ok(c) => c, ++ Err(err) => { ++ eprintln!("vesad: probe_connector: connector {:?} not found: {}", id, err); ++ return; ++ } ++ }; ++ let mut connector = connector_mutex.lock().unwrap_or_else(|e| { ++ eprintln!("vesad: probe_connector: connector lock poisoned, recovering"); ++ e.into_inner() ++ }); + let connector = &mut *connector; + connector.connection = KmsConnectorStatus::Connected; + connector.update_from_size(connector.driver_data.width, connector.driver_data.height); +@@ -102,7 +112,10 @@ impl GraphicsAdapter for FbAdapter { + state: KmsCrtcState, + damage: Damage, + ) -> syscall::Result<()> { +- let mut crtc = crtc.lock().unwrap(); ++ let mut crtc = crtc.lock().unwrap_or_else(|e| { ++ eprintln!("vesad: set_crtc: crtc lock poisoned, recovering"); ++ e.into_inner() ++ }); + let buffer = state + .fb_id + .map(|fb_id| objects.get_framebuffer(fb_id)) +@@ -110,7 +123,10 @@ impl GraphicsAdapter for FbAdapter { + crtc.state = state; + + for connector in objects.connectors() { +- let connector = connector.lock().unwrap(); ++ let connector = connector.lock().unwrap_or_else(|e| { ++ eprintln!("vesad: set_crtc: connector lock poisoned, recovering"); ++ e.into_inner() ++ }); + + if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { + continue; +@@ -159,7 +175,7 @@ pub struct FrameBuffer { + impl FrameBuffer { + pub unsafe fn new(phys: usize, width: usize, height: usize, stride: usize) -> Self { + let size = stride * height; +- let virt = common::physmap( ++ let virt = match common::physmap( + phys, + size * 4, + common::Prot { +@@ -167,8 +183,13 @@ impl FrameBuffer { + write: true, + }, + common::MemoryType::WriteCombining, +- ) +- .expect("vesad: failed to map framebuffer") as *mut u32; ++ ) { ++ Ok(v) => v as *mut u32, ++ Err(err) => { ++ eprintln!("vesad: failed to map framebuffer at 0x{:X}: {}", phys, err); ++ std::process::exit(1); ++ } ++ }; + + let onscreen = ptr::slice_from_raw_parts_mut(virt, size); + +@@ -228,9 +249,11 @@ impl GraphicScreen { + fn layout(len: usize) -> Layout { + // optimizes to an integer mul + Layout::array::(len) +- .unwrap() +- .align_to(PAGE_SIZE) +- .unwrap() ++ .and_then(|l| l.align_to(PAGE_SIZE)) ++ .unwrap_or_else(|err| { ++ eprintln!("vesad: failed to compute buffer layout (len={}): {}", len, err); ++ std::process::exit(1); ++ }) + } + } + +@@ -249,15 +272,26 @@ impl Buffer for GraphicScreen { + + impl GraphicScreen { + fn sync(&self, framebuffer: &mut FrameBuffer, sync_rect: Damage) { +- let sync_rect = sync_rect.clip( +- self.width.try_into().unwrap(), +- self.height.try_into().unwrap(), +- ); +- +- let start_x: usize = sync_rect.x.try_into().unwrap(); +- let start_y: usize = sync_rect.y.try_into().unwrap(); +- let w: usize = sync_rect.width.try_into().unwrap(); +- let h: usize = sync_rect.height.try_into().unwrap(); ++ let Ok(fb_width) = u32::try_from(self.width) else { ++ return; ++ }; ++ let Ok(fb_height) = u32::try_from(self.height) else { ++ return; ++ }; ++ let sync_rect = sync_rect.clip(fb_width, fb_height); ++ ++ let Ok(start_x): Result = sync_rect.x.try_into() else { ++ return; ++ }; ++ let Ok(start_y): Result = sync_rect.y.try_into() else { ++ return; ++ }; ++ let Ok(w): Result = sync_rect.width.try_into() else { ++ return; ++ }; ++ let Ok(h): Result = sync_rect.height.try_into() else { ++ return; ++ }; + + let offscreen_ptr = self.ptr.as_ptr() as *mut u32; + let onscreen_ptr = framebuffer.onscreen as *mut u32; // FIXME use as_mut_ptr once stable diff --git a/drivers/graphics/virtio-gpud/src/main.rs b/drivers/graphics/virtio-gpud/src/main.rs -index b27f4c56..5e9d810d 100644 +index b27f4c56..0f1a9e4d 100644 --- a/drivers/graphics/virtio-gpud/src/main.rs +++ b/drivers/graphics/virtio-gpud/src/main.rs @@ -482,7 +482,10 @@ fn main() { @@ -3798,19 +6478,752 @@ index b27f4c56..5e9d810d 100644 let mut scheme = scheme::GpuScheme::new( config, +@@ -556,33 +564,40 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: + Source::Input, + event::EventFlags::READ, + ) +- .unwrap(); ++ .map_err(|err| anyhow::anyhow!("virtio-gpud: failed to subscribe to input events: {err}"))?; + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) +- .unwrap(); ++ .map_err(|err| { ++ anyhow::anyhow!("virtio-gpud: failed to subscribe to scheme events: {err}") ++ })?; + event_queue + .subscribe( + device.irq_handle.as_raw_fd() as usize, + Source::Interrupt, + event::EventFlags::READ, + ) +- .unwrap(); ++ .map_err(|err| { ++ anyhow::anyhow!("virtio-gpud: failed to subscribe to interrupt events: {err}") ++ })?; + + let all = [Source::Input, Source::Scheme, Source::Interrupt]; +- for event in all +- .into_iter() +- .chain(event_queue.map(|e| e.expect("virtio-gpud: failed to get next event").user_data)) +- { ++ for event in all.into_iter().chain(event_queue.filter_map(|e| match e { ++ Ok(ev) => Some(ev.user_data), ++ Err(err) => { ++ log::error!("virtio-gpud: failed to get next event: {err}"); ++ None ++ } ++ })) { + match event { + Source::Input => scheme.handle_vt_events(), + Source::Scheme => { +- scheme +- .tick() +- .expect("virtio-gpud: failed to process scheme events"); ++ if let Err(err) = scheme.tick() { ++ log::error!("virtio-gpud: failed to process scheme events: {err}"); ++ } + } + Source::Interrupt => loop { + let before_gen = device.transport.config_generation(); +@@ -591,7 +606,11 @@ fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow: + + if events & VIRTIO_GPU_EVENT_DISPLAY != 0 { + let (adapter, objects) = scheme.adapter_and_kms_objects_mut(); +- futures::executor::block_on(async { adapter.update_displays().await.unwrap() }); ++ futures::executor::block_on(async { ++ if let Err(err) = adapter.update_displays().await { ++ log::error!("virtio-gpud: failed to update displays: {err}"); ++ } ++ }); + for connector_id in objects.connector_ids().to_vec() { + adapter.probe_connector(objects, connector_id); + } +diff --git a/drivers/graphics/virtio-gpud/src/scheme.rs b/drivers/graphics/virtio-gpud/src/scheme.rs +index 22a985ee..075502a2 100644 +--- a/drivers/graphics/virtio-gpud/src/scheme.rs ++++ b/drivers/graphics/virtio-gpud/src/scheme.rs +@@ -10,7 +10,7 @@ use drm_sys::{ + DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, + }; + +-use syscall::{EINVAL, PAGE_SIZE}; ++use syscall::{EIO, EINVAL, PAGE_SIZE}; + + use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags}; + use virtio_core::transport::{Error, Queue, Transport}; +@@ -65,9 +65,21 @@ impl DrmBuffer for VirtGpuFramebuffer<'_> { + impl Drop for VirtGpuFramebuffer<'_> { + fn drop(&mut self) { + futures::executor::block_on(async { +- let request = Dma::new(ResourceUnref::new(self.id)).unwrap(); ++ let request = match Dma::new(ResourceUnref::new(self.id)) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for resource unref: {err}"); ++ return; ++ } ++ }; + +- let header = Dma::new(ControlHeader::default()).unwrap(); ++ let header = match Dma::new(ControlHeader::default()) { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for unref header: {err}"); ++ return; ++ } ++ }; + let command = ChainBuilder::new() + .chain(Buffer::new(&request)) + .chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY)) +@@ -182,7 +194,9 @@ impl VirtGpuAdapter<'_> { + .build(); + + self.control_queue.send(command).await; +- assert!(response.header.ty == CommandTy::RespOkDisplayInfo); ++ if response.header.ty != CommandTy::RespOkDisplayInfo { ++ return Err(Error::QueueSetup("unexpected response type for display info")); ++ } + + Ok(response) + } +@@ -197,7 +211,9 @@ impl VirtGpuAdapter<'_> { + .build(); + + self.control_queue.send(command).await; +- assert!(response.header.ty == CommandTy::RespOkEdid); ++ if response.header.ty != CommandTy::RespOkEdid { ++ return Err(Error::QueueSetup("unexpected response type for EDID")); ++ } + + Ok(response) + } +@@ -212,7 +228,7 @@ impl VirtGpuAdapter<'_> { + ) { + //Transfering cursor resource to host + futures::executor::block_on(async { +- let transfer_request = Dma::new(XferToHost2d::new( ++ let transfer_request = match Dma::new(XferToHost2d::new( + cursor.id, + GpuRect { + x: 0, +@@ -221,14 +237,38 @@ impl VirtGpuAdapter<'_> { + height: 64, + }, + 0, +- )) +- .unwrap(); +- let header = self.send_request_fenced(transfer_request).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to allocate DMA for cursor transfer: {err}" ++ ); ++ return; ++ } ++ }; ++ let header = match self.send_request_fenced(transfer_request).await { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to send cursor transfer request: {err}"); ++ return; ++ } ++ }; ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for cursor transfer: {:?}", ++ header.ty ++ ); ++ } + }); + + //Update the cursor position +- let request = Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap(); ++ let request = match Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for cursor update: {err}"); ++ return; ++ } ++ }; + futures::executor::block_on(async { + let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); + self.cursor_queue.send(command).await; +@@ -236,7 +276,13 @@ impl VirtGpuAdapter<'_> { + } + + fn move_cursor(&mut self, x: i32, y: i32) { +- let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap(); ++ let request = match Dma::new(MoveCursor::move_cursor(x, y)) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for cursor move: {err}"); ++ return; ++ } ++ }; + + futures::executor::block_on(async { + let command = ChainBuilder::new().chain(Buffer::new(&request)).build(); +@@ -246,7 +292,10 @@ impl VirtGpuAdapter<'_> { + + fn disable_cursor(&mut self) { + if self.hidden_cursor.is_none() { +- let (width, height) = self.hw_cursor_size().unwrap(); ++ let Some((width, height)) = self.hw_cursor_size() else { ++ log::error!("virtio-gpud: failed to get hardware cursor size"); ++ return; ++ }; + let (cursor, stride) = self.create_dumb_buffer(width, height); + unsafe { + core::ptr::write_bytes( +@@ -257,8 +306,9 @@ impl VirtGpuAdapter<'_> { + } + self.hidden_cursor = Some(Arc::new(cursor)); + } +- let hidden_cursor = self.hidden_cursor.as_ref().unwrap().clone(); +- ++ let Some(hidden_cursor) = self.hidden_cursor.clone() else { ++ return; ++ }; + self.update_cursor(&hidden_cursor, 0, 0, 0, 0); + } + } +@@ -280,7 +330,9 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + + fn init(&mut self, objects: &mut KmsObjects) { + futures::executor::block_on(async { +- self.update_displays().await.unwrap(); ++ if let Err(err) = self.update_displays().await { ++ log::error!("virtio-gpud: failed to update displays during init: {err}"); ++ } + }); + + for display_id in 0..self.config.num_scanouts.get() { +@@ -310,7 +362,19 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + + fn probe_connector(&mut self, objects: &mut KmsObjects, id: KmsObjectId) { + futures::executor::block_on(async { +- let mut connector = objects.get_connector(id).unwrap().lock().unwrap(); ++ let mut connector = match objects.get_connector(id) { ++ Ok(c) => match c.lock() { ++ Ok(guard) => guard, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to lock connector: {err}"); ++ return; ++ } ++ }, ++ Err(e) => { ++ log::error!("virtio-gpud: connector not found: {e}"); ++ return; ++ } ++ }; + let display = &self.displays[connector.driver_data.display_id as usize]; + + connector.connection = if display.enabled { +@@ -325,7 +389,19 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + drop(connector); + + let blob = objects.add_blob(display.edid.clone()); +- objects.get_connector(id).unwrap().lock().unwrap().edid = blob; ++ match objects.get_connector(id) { ++ Ok(c) => match c.lock() { ++ Ok(mut guard) => guard.edid = blob, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to lock connector for EDID update: {err}" ++ ); ++ } ++ }, ++ Err(e) => { ++ log::error!("virtio-gpud: connector not found for EDID update: {e}"); ++ } ++ } + } else { + connector.update_from_size(display.width, display.height); + } +@@ -336,7 +412,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + futures::executor::block_on(async { + let bpp = 32; + let fb_size = width as usize * height as usize * bpp / 8; +- let sgl = sgl::Sgl::new(fb_size).unwrap(); ++ let sgl = match sgl::Sgl::new(fb_size) { ++ Ok(s) => s, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate SGL for dumb buffer: {err}"); ++ std::process::exit(1); ++ } ++ }; + + unsafe { + core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size); +@@ -345,22 +427,61 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + let res_id = ResourceId::alloc(); + + // Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`. +- let request = Dma::new(ResourceCreate2d::new( ++ let request = match Dma::new(ResourceCreate2d::new( + res_id, + ResourceFormat::Bgrx, + width, + height, +- )) +- .unwrap(); ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for resource create: {err}"); ++ return ( ++ VirtGpuFramebuffer { ++ queue: self.control_queue.clone(), ++ id: res_id, ++ sgl, ++ width, ++ height, ++ }, ++ width * 4, ++ ); ++ } ++ }; + +- let header = self.send_request(request).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ match self.send_request(request).await { ++ Ok(header) => { ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for resource create: {:?}", ++ header.ty ++ ); ++ } ++ } ++ Err(err) => { ++ log::error!("virtio-gpud: failed to send resource create request: {err}"); ++ } ++ } + + // Use the allocated framebuffer from the guest ram, and attach it as backing + // storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`. + +- let mut mem_entries = +- unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() }; ++ let mut mem_entries = match unsafe { Dma::zeroed_slice(sgl.chunks().len()) } { ++ Ok(entries) => unsafe { entries.assume_init() }, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for memory entries: {err}"); ++ return ( ++ VirtGpuFramebuffer { ++ queue: self.control_queue.clone(), ++ id: res_id, ++ sgl, ++ width, ++ height, ++ }, ++ width * 4, ++ ); ++ } ++ }; + for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) { + *entry = MemEntry { + address: chunk.phys as u64, +@@ -369,9 +490,43 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + }; + } + +- let attach_request = +- Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap(); +- let header = Dma::new(ControlHeader::default()).unwrap(); ++ let attach_request = match Dma::new(AttachBacking::new( ++ res_id, ++ mem_entries.len() as u32, ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to allocate DMA for attach request: {err}" ++ ); ++ return ( ++ VirtGpuFramebuffer { ++ queue: self.control_queue.clone(), ++ id: res_id, ++ sgl, ++ width, ++ height, ++ }, ++ width * 4, ++ ); ++ } ++ }; ++ let header = match Dma::new(ControlHeader::default()) { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for attach header: {err}"); ++ return ( ++ VirtGpuFramebuffer { ++ queue: self.control_queue.clone(), ++ id: res_id, ++ sgl, ++ width, ++ height, ++ }, ++ width * 4, ++ ); ++ } ++ }; + let command = ChainBuilder::new() + .chain(Buffer::new(&attach_request)) + .chain(Buffer::new_unsized(&mem_entries)) +@@ -379,7 +534,12 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + .build(); + + self.control_queue.send(command).await; +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for attach backing: {:?}", ++ header.ty ++ ); ++ } + + ( + VirtGpuFramebuffer { +@@ -410,7 +570,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + damage: Damage, + ) -> syscall::Result<()> { + futures::executor::block_on(async { +- let mut crtc = crtc.lock().unwrap(); ++ let mut crtc = match crtc.lock() { ++ Ok(guard) => guard, ++ Err(err) => { ++ log::error!("virtio-gpud: crtc mutex poisoned: {err}"); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; + let framebuffer = state + .fb_id + .map(|fb_id| objects.get_framebuffer(fb_id)) +@@ -418,7 +584,13 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + crtc.state = state; + + for connector in objects.connectors() { +- let connector = connector.lock().unwrap(); ++ let connector = match connector.lock() { ++ Ok(guard) => guard, ++ Err(err) => { ++ log::error!("virtio-gpud: connector mutex poisoned: {err}"); ++ continue; ++ } ++ }; + + if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] { + continue; +@@ -427,19 +599,40 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + let display_id = connector.driver_data.display_id; + + let Some(framebuffer) = framebuffer else { +- let scanout_request = Dma::new(SetScanout::new( ++ let scanout_request = match Dma::new(SetScanout::new( + display_id, + ResourceId::NONE, + GpuRect::new(0, 0, 0, 0), +- )) +- .unwrap(); +- let header = self.send_request(scanout_request).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to allocate DMA for scanout: {err}" ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ let header = match self.send_request(scanout_request).await { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to send scanout request: {err}" ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for scanout: {:?}", ++ header.ty ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } + self.displays[display_id as usize].active_resource = None; + return Ok(()); + }; + +- let req = Dma::new(XferToHost2d::new( ++ let req = match Dma::new(XferToHost2d::new( + framebuffer.buffer.id, + GpuRect { + x: 0, +@@ -448,22 +641,61 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + height: framebuffer.height, + }, + 0, +- )) +- .unwrap(); +- let header = self.send_request(req).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for transfer: {err}"); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ let header = match self.send_request(req).await { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to send transfer request: {err}"); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for transfer: {:?}", ++ header.ty ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } + + // FIXME once we support resizing we also need to check that the current and target size match +- if self.displays[display_id as usize].active_resource != Some(framebuffer.buffer.id) ++ if self.displays[display_id as usize].active_resource ++ != Some(framebuffer.buffer.id) + { +- let scanout_request = Dma::new(SetScanout::new( ++ let scanout_request = match Dma::new(SetScanout::new( + display_id, + framebuffer.buffer.id, + GpuRect::new(0, 0, framebuffer.width, framebuffer.height), +- )) +- .unwrap(); +- let header = self.send_request(scanout_request).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ )) { ++ Ok(r) => r, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to allocate DMA for scanout: {err}" ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ let header = match self.send_request(scanout_request).await { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!( ++ "virtio-gpud: failed to send scanout request: {err}" ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for scanout: {:?}", ++ header.ty ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } + self.displays[display_id as usize].active_resource = + Some(framebuffer.buffer.id); + } +@@ -472,8 +704,27 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> { + framebuffer.buffer.id, + damage.clip(framebuffer.width, framebuffer.height).into(), + ); +- let header = self.send_request(Dma::new(flush).unwrap()).await.unwrap(); +- assert_eq!(header.ty, CommandTy::RespOkNodata); ++ let flush_dma = match Dma::new(flush) { ++ Ok(d) => d, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to allocate DMA for flush: {err}"); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ let header = match self.send_request(flush_dma).await { ++ Ok(h) => h, ++ Err(err) => { ++ log::error!("virtio-gpud: failed to send flush request: {err}"); ++ return Err(syscall::Error::new(EIO)); ++ } ++ }; ++ if header.ty != CommandTy::RespOkNodata { ++ log::error!( ++ "virtio-gpud: unexpected response for flush: {:?}", ++ header.ty ++ ); ++ return Err(syscall::Error::new(EIO)); ++ } + } + + Ok(()) diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs -index 3da41d63..c24dfc4b 100644 +index 3da41d63..12d26261 100644 --- a/drivers/hwd/src/backend/acpi.rs +++ b/drivers/hwd/src/backend/acpi.rs -@@ -14,7 +14,7 @@ impl Backend for AcpiBackend { - // Spawn acpid - //TODO: pass rxsdt data to acpid? - #[allow(deprecated, reason = "we can't yet move this to init")] -- daemon::Daemon::spawn(Command::new("acpid")); -+ let _ = daemon::Daemon::spawn(Command::new("acpid")); +@@ -1,27 +1,36 @@ + use amlserde::{AmlSerde, AmlSerdeValue}; +-use std::{error::Error, fs, process::Command}; ++use std::{error::Error, fs}; - Ok(Self { rxsdt }) + use super::Backend; + + pub struct AcpiBackend { +- rxsdt: Vec, ++ _rxsdt: Vec, + } + + impl Backend for AcpiBackend { + fn new() -> Result> { + let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?; + +- // Spawn acpid +- //TODO: pass rxsdt data to acpid? +- #[allow(deprecated, reason = "we can't yet move this to init")] +- daemon::Daemon::spawn(Command::new("acpid")); +- +- Ok(Self { rxsdt }) ++ Ok(Self { _rxsdt: rxsdt }) } + + fn probe(&mut self) -> Result<(), Box> { ++ let mut boot_critical_input_candidates = 0usize; ++ let mut thc_candidates = 0usize; ++ let mut non_hid_i2c_candidates = 0usize; ++ + // Read symbols from acpi scheme +- let entries = fs::read_dir("/scheme/acpi/symbols")?; ++ let entries = match fs::read_dir("/scheme/acpi/symbols") { ++ Ok(entries) => entries, ++ Err(err) ++ if err.kind() == std::io::ErrorKind::WouldBlock ++ || err.raw_os_error() == Some(11) => ++ { ++ log::debug!("hwd: ACPI symbols are not ready yet"); ++ return Ok(()); ++ } ++ Err(err) => return Err(Box::new(err)), ++ }; + // TODO: Reimplement with getdents? + let symbols_fd = libredox::Fd::open( + "/scheme/acpi/symbols", +@@ -100,12 +109,103 @@ impl Backend for AcpiBackend { + "PNP0C0F" => "PCI interrupt link", + "PNP0C50" => "I2C HID", + "PNP0F13" => "PS/2 port for PS/2-style mouse", ++ "80860F41" | "808622C1" => "DesignWare I2C controller", ++ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller", ++ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => { ++ "Intel LPSS/SerialIO I2C controller" ++ } ++ "INT34C5" | "INTC1055" => "Intel GPIO controller", ++ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => { ++ "Intel THC companion (QuickI2C/QuickSPI path)" ++ } ++ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)", ++ _ if is_cypress_touchpad_id(&id) => "Cypress/Trackpad (non-HID I2C path)", ++ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)", + _ => "?", + }; + log::debug!("{}: {} ({})", name, id, what); ++ if is_boot_critical_i2c_surface(&id) { ++ boot_critical_input_candidates += 1; ++ log::info!("{}: {} is boot-critical for laptop input path", name, id); ++ } ++ if is_thc_companion(&id) { ++ thc_candidates += 1; ++ log::warn!( ++ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet", ++ name, ++ id ++ ); ++ } ++ if is_non_hid_i2c_input_id(&id) { ++ non_hid_i2c_candidates += 1; ++ } + } + } + } ++ ++ if boot_critical_input_candidates == 0 { ++ log::warn!( ++ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support" ++ ); ++ } else { ++ log::info!( ++ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}", ++ boot_critical_input_candidates, ++ thc_candidates, ++ non_hid_i2c_candidates ++ ); ++ } ++ + Ok(()) + } + } ++ ++fn is_boot_critical_i2c_surface(id: &str) -> bool { ++ matches!( ++ id, ++ "PNP0C50" ++ | "ACPI0C50" ++ | "80860F41" ++ | "808622C1" ++ | "AMDI0010" ++ | "AMDI0019" ++ | "AMDI0510" ++ | "INT33C2" ++ | "INT33C3" ++ | "INT3432" ++ | "INT3433" ++ | "INTC10EF" ++ | "INT34C5" ++ | "INTC1055" ++ | "INTC1050" ++ | "INTC1051" ++ | "INTC1080" ++ | "INTC1081" ++ | "INTC1082" ++ ) || is_elan_touchpad_id(id) ++ || is_cypress_touchpad_id(id) ++ || is_synaptics_rmi_id(id) ++} ++ ++fn is_thc_companion(id: &str) -> bool { ++ matches!( ++ id, ++ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" ++ ) ++} ++ ++fn is_elan_touchpad_id(id: &str) -> bool { ++ id.starts_with("ELAN") ++} ++ ++fn is_cypress_touchpad_id(id: &str) -> bool { ++ id.starts_with("CYAP") ++} ++ ++fn is_synaptics_rmi_id(id: &str) -> bool { ++ id.starts_with("SYNA") ++} ++ ++fn is_non_hid_i2c_input_id(id: &str) -> bool { ++ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id) ++} diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs index 79360e34..4a2b9469 100644 --- a/drivers/hwd/src/main.rs @@ -3824,6 +7237,223 @@ index 79360e34..4a2b9469 100644 daemon.ready(); +diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs +index b68e8211..0627f301 100644 +--- a/drivers/inputd/src/lib.rs ++++ b/drivers/inputd/src/lib.rs +@@ -64,25 +64,53 @@ impl ConsumerHandle { + let fd = self.0.as_raw_fd(); + let written = libredox::call::fpath(fd as usize, &mut buffer)?; + +- assert!(written <= buffer.len()); +- +- let mut display_path = PathBuf::from( +- std::str::from_utf8(&buffer[..written]) +- .expect("init: display path UTF-8 check failed") +- .to_owned(), +- ); +- display_path.set_file_name(format!( +- "v2/{}", +- display_path.file_name().unwrap().to_str().unwrap() +- )); +- let display_path = display_path.to_str().unwrap(); ++ if written > buffer.len() { ++ return Err(io::Error::new( ++ io::ErrorKind::InvalidData, ++ "inputd: display path exceeded buffer size", ++ )); ++ } ++ ++ let path_str = std::str::from_utf8(&buffer[..written]).map_err(|e| { ++ io::Error::new( ++ io::ErrorKind::InvalidData, ++ format!("inputd: display path is not valid UTF-8: {e}"), ++ ) ++ })?; ++ let mut display_path = PathBuf::from(path_str.to_owned()); ++ ++ let file_name = display_path ++ .file_name() ++ .and_then(|n| n.to_str()) ++ .ok_or_else(|| { ++ io::Error::new( ++ io::ErrorKind::InvalidData, ++ format!( ++ "inputd: display path has no valid file name: {}", ++ display_path.display() ++ ), ++ ) ++ })?; ++ display_path.set_file_name(format!("v2/{file_name}")); ++ let display_path_str = display_path.to_str().ok_or_else(|| { ++ io::Error::new( ++ io::ErrorKind::InvalidData, ++ format!( ++ "inputd: constructed display path is not valid UTF-8: {}", ++ display_path.display() ++ ), ++ ) ++ })?; + + let display_file = +- libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) ++ libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) +- .unwrap_or_else(|err| { +- panic!("failed to open display {}: {}", display_path, err); +- }); ++ .map_err(|err| { ++ io::Error::new( ++ io::ErrorKind::Other, ++ format!("inputd: failed to open display {display_path_str}: {err}"), ++ ) ++ })?; + + Ok(display_file) + } +@@ -152,8 +180,12 @@ impl DisplayHandle { + + if nread == 0 { + Ok(None) ++ } else if nread != size_of::() { ++ Err(io::Error::new( ++ io::ErrorKind::InvalidData, ++ format!("inputd: partial vt event read: got {nread}, expected {}", size_of::()), ++ )) + } else { +- assert_eq!(nread, size_of::()); + Ok(Some(event)) + } + } +diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs +index 07aa943e..61641b9f 100644 +--- a/drivers/inputd/src/main.rs ++++ b/drivers/inputd/src/main.rs +@@ -274,7 +274,7 @@ impl SchemeSync for InputScheme { + let handle = self.handles.get(id)?; + + if let Handle::Consumer { vt, .. } = handle { +- write!(w, "{vt}").unwrap(); ++ write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?; + Ok(()) + } else { + Err(SysError::new(EINVAL)) +@@ -438,7 +438,9 @@ impl SchemeSync for InputScheme { + } + + let handle = self.handles.get_mut(id)?; +- assert!(matches!(handle, Handle::Producer)); ++ if !matches!(handle, Handle::Producer) { ++ return Err(SysError::new(EBADF)); ++ } + + let buf = unsafe { + core::slice::from_raw_parts( +@@ -505,8 +507,8 @@ impl SchemeSync for InputScheme { + } + + fn on_close(&mut self, id: usize) { +- match self.handles.remove(id).unwrap() { +- Handle::Consumer { vt, .. } => { ++ match self.handles.remove(id) { ++ Some(Handle::Consumer { vt, .. }) => { + self.vts.remove(&vt); + if self.active_vt == Some(vt) { + if let Some(&new_vt) = self.vts.last() { +@@ -516,7 +518,10 @@ impl SchemeSync for InputScheme { + } + } + } +- _ => {} ++ Some(_) => {} ++ None => { ++ log::warn!("inputd: on_close called with unknown handle id {id}"); ++ } + } + } + } +@@ -589,8 +594,11 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { + } + + fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { +- deamon(daemon).unwrap(); +- unreachable!(); ++ if let Err(err) = deamon(daemon) { ++ log::error!("inputd: scheme daemon failed: {err}"); ++ std::process::exit(1); ++ } ++ unreachable!() + } + + const HELP: &str = r#" +@@ -608,13 +616,26 @@ fn main() { + match val.as_ref() { + // Activates a VT. + "-A" => { +- let vt = args.next().unwrap().parse::().unwrap(); ++ let vt_str = args.next().unwrap_or_else(|| { ++ eprintln!("inputd: -A requires a VT number argument"); ++ std::process::exit(1); ++ }); ++ let vt = vt_str.parse::().unwrap_or_else(|_| { ++ eprintln!("inputd: invalid VT number: {vt_str}"); ++ std::process::exit(1); ++ }); + +- let mut handle = +- inputd::ControlHandle::new().expect("inputd: failed to open control handle"); +- handle +- .activate_vt(vt) +- .expect("inputd: failed to activate VT"); ++ let mut handle = match inputd::ControlHandle::new() { ++ Ok(h) => h, ++ Err(e) => { ++ eprintln!("inputd: failed to open control handle: {e}"); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(e) = handle.activate_vt(vt) { ++ eprintln!("inputd: failed to activate VT {vt}: {e}"); ++ std::process::exit(1); ++ } + } + // Activates a keymap. + "-K" => { +@@ -630,11 +651,17 @@ fn main() { + std::process::exit(1); + }); + +- let mut handle = +- inputd::ControlHandle::new().expect("inputd: failed to open control handle"); +- handle +- .activate_keymap(vt as usize) +- .expect("inputd: failed to activate keymap"); ++ let mut handle = match inputd::ControlHandle::new() { ++ Ok(h) => h, ++ Err(e) => { ++ eprintln!("inputd: failed to open control handle: {e}"); ++ std::process::exit(1); ++ } ++ }; ++ if let Err(e) = handle.activate_keymap(vt as usize) { ++ eprintln!("inputd: failed to activate keymap: {e}"); ++ std::process::exit(1); ++ } + } + // List available keymaps + "--keymaps" => { +@@ -647,7 +674,10 @@ fn main() { + println!("{}", HELP); + } + +- _ => panic!("inputd: invalid argument: {}", val), ++ _ => { ++ eprintln!("inputd: invalid argument: {val}"); ++ std::process::exit(1); ++ } + } + } else { + common::setup_logging( diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs index 373ea9b3..d971c0a1 100644 --- a/drivers/net/e1000d/src/main.rs @@ -4302,10 +7932,64 @@ index 17d168ef..56f2c045 100644 scheme.tick()?; diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs -index a968f4d4..e41caee0 100644 +index a968f4d4..a39b2af8 100644 --- a/drivers/pcid-spawner/src/main.rs +++ b/drivers/pcid-spawner/src/main.rs -@@ -55,10 +55,11 @@ fn main() -> Result<()> { +@@ -1,11 +1,40 @@ ++use std::env; + use std::fs; + use std::process::Command; ++use std::thread; + + use anyhow::{anyhow, Context, Result}; + + use pcid_interface::config::Config; + use pcid_interface::PciFunctionHandle; + ++fn strict_usb_boot() -> bool { ++ matches!( ++ env::var("REDBEAR_STRICT_USB_BOOT") ++ .ok() ++ .as_deref() ++ .map(str::to_ascii_lowercase) ++ .as_deref(), ++ Some("1" | "true" | "yes" | "on") ++ ) ++} ++ ++fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool { ++ if !initfs { ++ return false; ++ } ++ ++ if class == 0x01 { ++ return false; ++ } ++ ++ if strict_usb_boot && class == 0x0C && subclass == 0x03 { ++ return false; ++ } ++ ++ true ++} ++ + fn main() -> Result<()> { + let mut args = pico_args::Arguments::from_env(); + let initfs = args.contains("--initfs"); +@@ -30,6 +59,12 @@ fn main() -> Result<()> { + } + + let config: Config = toml::from_str(&config_data)?; ++ let strict_usb_boot = strict_usb_boot(); ++ ++ log::info!( ++ "pcid-spawner: starting (initfs={}, strict_usb_boot={})", ++ initfs, strict_usb_boot ++ ); + + for entry in fs::read_dir("/scheme/pci")? { + let entry = entry.context("failed to get entry")?; +@@ -55,10 +90,11 @@ fn main() -> Result<()> { }; let full_device_id = handle.config().func.full_device_id; @@ -4318,7 +8002,7 @@ index a968f4d4..e41caee0 100644 full_device_id.display() ); -@@ -67,7 +68,7 @@ fn main() -> Result<()> { +@@ -67,7 +103,7 @@ fn main() -> Result<()> { .iter() .find(|driver| driver.match_function(&full_device_id)) else { @@ -4327,7 +8011,7 @@ index a968f4d4..e41caee0 100644 continue; }; -@@ -85,16 +86,46 @@ fn main() -> Result<()> { +@@ -85,16 +121,93 @@ fn main() -> Result<()> { let mut command = Command::new(program); command.args(args); @@ -4357,24 +8041,71 @@ index a968f4d4..e41caee0 100644 #[allow(deprecated, reason = "we can't yet move this to init")] - daemon::Daemon::spawn(command); - syscall::close(channel_fd as usize).unwrap(); -+ if let Err(err) = daemon::Daemon::spawn(command) { -+ log::error!( -+ "pcid-spawner: spawn/readiness failed for {}: {}", -+ device_addr, -+ err -+ ); -+ log::error!( -+ "pcid-spawner: {} remains enabled without a confirmed ready driver", ++ if should_detach_in_initfs( ++ initfs, ++ full_device_id.class, ++ full_device_id.subclass, ++ strict_usb_boot, ++ ) { ++ log::warn!( ++ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot", + device_addr + ); -+ } -+ if let Err(err) = syscall::close(channel_fd as usize) { -+ log::error!( -+ "pcid-spawner: failed to close channel fd {} for {}: {}", -+ channel_fd, ++ ++ let device_addr = device_addr.to_string(); ++ thread::spawn(move || { ++ #[allow(deprecated, reason = "we can't yet move this to init")] ++ if let Err(err) = daemon::Daemon::spawn(command) { ++ log::error!( ++ "pcid-spawner: spawn/readiness failed for {}: {}", ++ device_addr, ++ err ++ ); ++ log::error!( ++ "pcid-spawner: {} remains enabled without a confirmed ready driver", ++ device_addr ++ ); ++ } ++ if let Err(err) = syscall::close(channel_fd as usize) { ++ log::error!( ++ "pcid-spawner: failed to close channel fd {} for {}: {}", ++ channel_fd, ++ device_addr, ++ err ++ ); ++ } ++ }); ++ } else { ++ log::info!( ++ "pcid-spawner: blocking on storage driver spawn for {} (class={:#04x})", + device_addr, -+ err ++ full_device_id.class + ); ++ #[allow(deprecated, reason = "we can't yet move this to init")] ++ if let Err(err) = daemon::Daemon::spawn(command) { ++ log::error!( ++ "pcid-spawner: spawn/readiness failed for {}: {}", ++ device_addr, ++ err ++ ); ++ log::error!( ++ "pcid-spawner: {} remains enabled without a confirmed ready driver", ++ device_addr ++ ); ++ } else { ++ log::info!( ++ "pcid-spawner: storage driver ready for {}", ++ device_addr ++ ); ++ } ++ if let Err(err) = syscall::close(channel_fd as usize) { ++ log::error!( ++ "pcid-spawner: failed to close channel fd {} for {}: {}", ++ channel_fd, ++ device_addr, ++ err ++ ); ++ } + } } @@ -5486,6 +9217,40 @@ index 0ca68ec5..6934ad49 100644 #[repr(C, packed)] pub struct MsixTableEntry { pub addr_lo: Mmio, +diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs +index 61cd9a78..6da034ef 100644 +--- a/drivers/pcid/src/main.rs ++++ b/drivers/pcid/src/main.rs +@@ -12,6 +12,7 @@ use pci_types::{ + }; + use redox_scheme::scheme::register_sync_scheme; + use scheme_utils::Blocking; ++use syscall::{sendfd, SendFdFlags}; + + use crate::cfg_access::Pcie; + use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom}; +@@ -262,14 +263,13 @@ fn daemon(daemon: daemon::Daemon) -> ! { + let access_fd = socket + .create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0) + .expect("failed to issue this resource"); +- let access_bytes = access_fd.to_ne_bytes(); +- let _ = register_pci +- .call_wo( +- &access_bytes, +- syscall::CallFlags::WRITE | syscall::CallFlags::FD, +- &[], +- ) +- .expect("failed to send pci_fd to acpid"); ++ sendfd( ++ register_pci.raw(), ++ access_fd as usize, ++ SendFdFlags::empty().bits(), ++ 0, ++ ) ++ .expect("failed to send pci_fd to acpid"); + } + Err(err) => { + if err.errno() == libredox::errno::ENODEV { diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index bb9f39a3..df026ab4 100644 --- a/drivers/pcid/src/scheme.rs @@ -7206,7 +10971,7 @@ index f2143676..9ce15161 100644 struct DriverConfig { name: String, diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs -index b524f5d8..aac4fa82 100644 +index ca27b3fe..468a98ae 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -20,6 +20,7 @@ use std::convert::TryFrom; @@ -7217,7 +10982,7 @@ index b524f5d8..aac4fa82 100644 use std::{cmp, fmt, io, mem, str}; use common::dma::Dma; -@@ -32,9 +33,9 @@ use common::io::Io; +@@ -33,9 +34,9 @@ use common::io::Io; use redox_scheme::{CallerCtx, OpenResult}; use syscall::schemev2::NewFdFlags; use syscall::{ @@ -7230,7 +10995,7 @@ index b524f5d8..aac4fa82 100644 }; use super::{port, usb}; -@@ -60,10 +61,16 @@ lazy_static! { +@@ -61,10 +62,16 @@ lazy_static! { .expect("Failed to create the regex for the port/attach scheme."); static ref REGEX_PORT_DETACH: Regex = Regex::new(r"^port([\d\.]+)/detach$") .expect("Failed to create the regex for the port/detach scheme."); @@ -7247,7 +11012,7 @@ index b524f5d8..aac4fa82 100644 static ref REGEX_PORT_REQUEST: Regex = Regex::new(r"^port([\d\.]+)/request$") .expect("Failed to create the regex for the port/request scheme"); static ref REGEX_PORT_ENDPOINTS: Regex = Regex::new(r"^port([\d\.]+)/endpoints$") -@@ -137,12 +144,15 @@ pub enum Handle { +@@ -138,12 +145,15 @@ pub enum Handle { Port(PortId, Vec), // port, contents PortDesc(PortId, Vec), // port, contents PortState(PortId), // port @@ -7263,7 +11028,7 @@ index b524f5d8..aac4fa82 100644 SchemeRoot, } -@@ -172,6 +182,8 @@ enum SchemeParameters { +@@ -173,6 +183,8 @@ enum SchemeParameters { PortDesc(PortId), // port number /// /port/state PortState(PortId), // port number @@ -7272,7 +11037,7 @@ index b524f5d8..aac4fa82 100644 /// /port/request PortReq(PortId), // port number /// /port/endpoints -@@ -187,6 +199,10 @@ enum SchemeParameters { +@@ -188,6 +200,10 @@ enum SchemeParameters { AttachDevice(PortId), // port number /// /port/detach DetachDevice(PortId), // port number @@ -7283,7 +11048,7 @@ index b524f5d8..aac4fa82 100644 } impl Handle { -@@ -209,6 +225,9 @@ impl Handle { +@@ -210,6 +226,9 @@ impl Handle { Handle::PortState(port_num) => { format!("port{}/state", port_num) } @@ -7293,7 +11058,7 @@ index b524f5d8..aac4fa82 100644 Handle::PortReq(port_num, _) => { format!("port{}/request", port_num) } -@@ -235,6 +254,12 @@ impl Handle { +@@ -236,6 +255,12 @@ impl Handle { Handle::DetachDevice(port_num) => { format!("port{}/detach", port_num) } @@ -7306,7 +11071,7 @@ index b524f5d8..aac4fa82 100644 Handle::SchemeRoot => String::from(""), } } -@@ -258,10 +283,13 @@ impl Handle { +@@ -259,10 +284,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => unreachable!(), &Handle::PortReq(_, PortReqState::TmpSetup(_)) => unreachable!(), &Handle::PortState(_) => HandleType::Character, @@ -7320,7 +11085,7 @@ index b524f5d8..aac4fa82 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => HandleType::Character, EndpointHandleTy::Ctl => HandleType::Character, -@@ -289,10 +317,13 @@ impl Handle { +@@ -290,10 +318,13 @@ impl Handle { &Handle::PortReq(_, PortReqState::Tmp) => None, &Handle::PortReq(_, PortReqState::TmpSetup(_)) => None, &Handle::PortState(_) => None, @@ -7334,7 +11099,7 @@ index b524f5d8..aac4fa82 100644 &Handle::Endpoint(_, _, ref st) => match st { EndpointHandleTy::Data => None, EndpointHandleTy::Ctl => None, -@@ -383,6 +414,14 @@ impl SchemeParameters { +@@ -384,6 +415,14 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_DETACH, scheme, 0)?; Ok(Self::DetachDevice(port_num)) @@ -7349,7 +11114,7 @@ index b524f5d8..aac4fa82 100644 } else if REGEX_PORT_DESCRIPTORS.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_DESCRIPTORS, scheme, 0)?; -@@ -391,6 +430,10 @@ impl SchemeParameters { +@@ -392,6 +431,10 @@ impl SchemeParameters { let port_num = get_port_id_from_regex(®EX_PORT_STATE, scheme, 0)?; Ok(Self::PortState(port_num)) @@ -7360,7 +11125,7 @@ index b524f5d8..aac4fa82 100644 } else if REGEX_PORT_REQUEST.is_match(scheme) { let port_num = get_port_id_from_regex(®EX_PORT_REQUEST, scheme, 0)?; -@@ -523,6 +566,39 @@ pub enum AnyDescriptor { +@@ -524,6 +567,39 @@ pub enum AnyDescriptor { SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor), } @@ -7400,7 +11165,7 @@ index b524f5d8..aac4fa82 100644 impl AnyDescriptor { fn parse(bytes: &[u8]) -> Option<(Self, usize)> { if bytes.len() < 2 { -@@ -639,6 +715,8 @@ impl Xhci { +@@ -640,6 +716,8 @@ impl Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { @@ -7409,7 +11174,7 @@ index b524f5d8..aac4fa82 100644 let future = { let mut port_state = self.port_state_mut(port_num)?; let slot = port_state.slot; -@@ -709,6 +787,8 @@ impl Xhci { +@@ -710,6 +788,8 @@ impl Xhci { where D: FnMut(&mut Trb, bool) -> ControlFlow, { @@ -7418,7 +11183,7 @@ index b524f5d8..aac4fa82 100644 let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; let mut port_state = self.port_state_mut(port_num)?; -@@ -949,35 +1029,102 @@ impl Xhci { +@@ -950,35 +1030,102 @@ impl Xhci { self.port_states.get_mut(&port).ok_or(Error::new(EBADF)) } @@ -7539,7 +11304,7 @@ index b524f5d8..aac4fa82 100644 } } new_context_entries += 1; -@@ -988,74 +1135,22 @@ impl Xhci { +@@ -989,74 +1136,22 @@ impl Xhci { } ( @@ -7623,7 +11388,7 @@ index b524f5d8..aac4fa82 100644 let usb_log_max_streams = endp_desc.log_max_streams(); -@@ -1077,20 +1172,20 @@ impl Xhci { +@@ -1078,20 +1173,20 @@ impl Xhci { let mult = endp_desc.isoch_mult(lec); @@ -7649,7 +11414,7 @@ index b524f5d8..aac4fa82 100644 let max_error_count = 3; let ep_ty = endp_desc.xhci_ep_type()?; -@@ -1113,7 +1208,7 @@ impl Xhci { +@@ -1114,7 +1209,7 @@ impl Xhci { assert_eq!(max_error_count & 0x3, max_error_count); assert_ne!(ep_ty, 0); // 0 means invalid. @@ -7658,7 +11423,7 @@ index b524f5d8..aac4fa82 100644 let mut array = StreamContextArray::new::(self.cap.ac64(), 1 << (primary_streams + 1))?; -@@ -1126,15 +1221,13 @@ impl Xhci { +@@ -1127,15 +1222,13 @@ impl Xhci { array_ptr, "stream ctx ptr not aligned to 16 bytes" ); @@ -7677,7 +11442,7 @@ index b524f5d8..aac4fa82 100644 } else { let ring = Ring::new::(self.cap.ac64(), 16, true)?; let ring_ptr = ring.register(); -@@ -1144,68 +1237,185 @@ impl Xhci { +@@ -1145,68 +1238,185 @@ impl Xhci { ring_ptr, "ring pointer not aligned to 16 bytes" ); @@ -7900,7 +11665,7 @@ index b524f5d8..aac4fa82 100644 Ok(()) } -@@ -1856,7 +2066,7 @@ impl Xhci { +@@ -1857,7 +2067,7 @@ impl Xhci { if (flags & O_DIRECTORY != 0) || (flags & O_STAT != 0) { let mut contents = Vec::new(); @@ -7909,7 +11674,7 @@ index b524f5d8..aac4fa82 100644 if self.slot_state( self.port_states -@@ -1893,6 +2103,14 @@ impl Xhci { +@@ -1894,6 +2104,14 @@ impl Xhci { Ok(Handle::PortState(port_num)) } @@ -7924,7 +11689,7 @@ index b524f5d8..aac4fa82 100644 /// implements open() for /port/endpoints /// /// # Arguments -@@ -2087,6 +2305,30 @@ impl Xhci { +@@ -2088,6 +2306,30 @@ impl Xhci { Ok(Handle::DetachDevice(port_num)) } @@ -7955,7 +11720,7 @@ index b524f5d8..aac4fa82 100644 /// implements open() for /port/request /// /// # Arguments -@@ -2155,6 +2397,9 @@ impl SchemeSync for &Xhci { +@@ -2156,6 +2398,9 @@ impl SchemeSync for &Xhci { SchemeParameters::PortState(port_number) => { self.open_handle_port_state(port_number, flags)? } @@ -7965,7 +11730,7 @@ index b524f5d8..aac4fa82 100644 SchemeParameters::PortReq(port_number) => { self.open_handle_port_request(port_number, flags)? } -@@ -2173,6 +2418,12 @@ impl SchemeSync for &Xhci { +@@ -2174,6 +2419,12 @@ impl SchemeSync for &Xhci { SchemeParameters::DetachDevice(port_number) => { self.open_handle_detach_device(port_number, flags)? } @@ -7978,7 +11743,7 @@ index b524f5d8..aac4fa82 100644 }; let fd = self.next_handle.fetch_add(1, atomic::Ordering::Relaxed); -@@ -2203,7 +2454,11 @@ impl SchemeSync for &Xhci { +@@ -2204,7 +2455,11 @@ impl SchemeSync for &Xhci { //If we have a handle to the configure scheme, we need to mark it as write only. match &*guard { @@ -7991,7 +11756,7 @@ index b524f5d8..aac4fa82 100644 stat.st_mode = stat.st_mode | 0o200; } _ => {} -@@ -2263,6 +2518,8 @@ impl SchemeSync for &Xhci { +@@ -2254,6 +2509,8 @@ impl SchemeSync for &Xhci { Handle::ConfigureEndpoints(_) => Err(Error::new(EBADF)), Handle::AttachDevice(_) => Err(Error::new(EBADF)), Handle::DetachDevice(_) => Err(Error::new(EBADF)), @@ -8000,7 +11765,7 @@ index b524f5d8..aac4fa82 100644 Handle::SchemeRoot => Err(Error::new(EBADF)), &mut Handle::Endpoint(port_num, endp_num, ref mut st) => match st { -@@ -2294,6 +2551,10 @@ impl SchemeSync for &Xhci { +@@ -2285,6 +2542,10 @@ impl SchemeSync for &Xhci { Ok(Xhci::::write_dyn_string(string, buf, offset)) } @@ -8011,7 +11776,7 @@ index b524f5d8..aac4fa82 100644 &mut Handle::PortReq(port_num, ref mut st) => { let state = std::mem::replace(st, PortReqState::Tmp); drop(guard); // release the lock -@@ -2333,6 +2594,14 @@ impl SchemeSync for &Xhci { +@@ -2324,6 +2585,14 @@ impl SchemeSync for &Xhci { block_on(self.detach_device(port_num))?; Ok(buf.len()) } @@ -8026,7 +11791,7 @@ index b524f5d8..aac4fa82 100644 &mut Handle::Endpoint(port_num, endp_num, ref ep_file_ty) => match ep_file_ty { EndpointHandleTy::Ctl => block_on(self.on_write_endp_ctl(port_num, endp_num, buf)), EndpointHandleTy::Data => { -@@ -2357,6 +2626,54 @@ impl SchemeSync for &Xhci { +@@ -2348,6 +2617,54 @@ impl SchemeSync for &Xhci { } impl Xhci { @@ -8081,7 +11846,7 @@ index b524f5d8..aac4fa82 100644 pub fn get_endp_status(&self, port_num: PortId, endp_num: u8) -> Result { let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; -@@ -2407,6 +2724,8 @@ impl Xhci { +@@ -2398,6 +2715,8 @@ impl Xhci { endp_num: u8, clear_feature: bool, ) -> Result<()> { @@ -8517,3 +12282,676 @@ index d3445d2d..4e116d2e 100644 // Enable the queue. common.queue_enable.set(1); +diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target +index 8ddb4795..029583a1 100644 +--- a/init.initfs.d/40_drivers.target ++++ b/init.initfs.d/40_drivers.target +@@ -7,4 +7,5 @@ requires_weak = [ + "40_bcm2835-sdhcid.service", + "40_hwd.service", + "40_pcid-spawner-initfs.service", ++ "41_acpid.service", + ] +diff --git a/init.initfs.d/40_hwd.service b/init.initfs.d/40_hwd.service +index cba12dde..cf34a51b 100644 +--- a/init.initfs.d/40_hwd.service ++++ b/init.initfs.d/40_hwd.service +@@ -1,6 +1,6 @@ + [unit] + description = "Hardware manager" +-requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"] ++requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "41_acpid.service"] + + [service] + cmd = "hwd" +diff --git a/init.initfs.d/40_pcid-spawner-initfs.service b/init.initfs.d/40_pcid-spawner-initfs.service +index 6945b9ea..ba1ee0bb 100644 +--- a/init.initfs.d/40_pcid-spawner-initfs.service ++++ b/init.initfs.d/40_pcid-spawner-initfs.service +@@ -1,6 +1,6 @@ + [unit] + description = "PCI driver spawner" +-requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"] ++requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service", "41_acpid.service"] + + [service] + cmd = "pcid-spawner" +diff --git a/init/src/main.rs b/init/src/main.rs +index 5682cf44..ed436619 100644 +--- a/init/src/main.rs ++++ b/init/src/main.rs +@@ -117,6 +117,8 @@ fn main() { + let mut unit_store = UnitStore::new(); + let mut scheduler = Scheduler::new(); + ++ eprintln!("init: phase 1 — initfs boot"); ++ + switch_root( + &mut unit_store, + &mut init_config, +@@ -125,6 +127,7 @@ fn main() { + ); + + // Start logd first such that we can pass /scheme/log as stdio to all other services ++ eprintln!("init: starting logd"); + scheduler + .schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned())); + scheduler.step(&mut unit_store, &mut init_config); +@@ -132,14 +135,18 @@ fn main() { + eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); + } + ++ eprintln!("init: starting runtime target"); + let runtime_target = UnitId("00_runtime.target".to_owned()); + scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone()); + unit_store.set_runtime_target(runtime_target); + ++ eprintln!("init: starting initfs drivers target"); + scheduler + .schedule_start_and_report_errors(&mut unit_store, UnitId("90_initfs.target".to_owned())); + scheduler.step(&mut unit_store, &mut init_config); ++ eprintln!("init: initfs drivers target step() complete"); + ++ eprintln!("init: phase 2 — switchroot to /usr"); + switch_root( + &mut unit_store, + &mut init_config, +@@ -162,23 +169,64 @@ fn main() { + .collect::>() + .join(", ") + ); +- return; ++ Vec::new() + } + }; ++ eprintln!("init: scheduling {} rootfs units", entries.len()); + for entry in entries { ++ let name = match entry.file_name().and_then(|n| n.to_str()) { ++ Some(name) => name, ++ None => { ++ eprintln!("init: skipping config entry with non-UTF-8 filename"); ++ continue; ++ } ++ }; + scheduler.schedule_start_and_report_errors( + &mut unit_store, +- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()), ++ UnitId(name.to_owned()), + ); + } + }; + + scheduler.step(&mut unit_store, &mut init_config); ++ eprintln!("init: phase 3 — rootfs services started"); ++ ++ if let Err(err) = libredox::call::setrens(0, 0) { ++ eprintln!("init: failed to enter null namespace: {err}"); ++ } + +- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace"); ++ eprintln!("init: boot complete — entering waitpid loop"); ++ ++ let mut respawn_map: BTreeMap = BTreeMap::new(); ++ for (unit_id, pid) in scheduler.respawn_pids { ++ respawn_map.insert(pid, unit_id); ++ } + + loop { + let mut status = 0; +- libredox::call::waitpid(0, &mut status, 0).unwrap(); ++ match libredox::call::waitpid(0, &mut status, 0) { ++ Ok(pid) => { ++ if let Some(unit_id) = respawn_map.remove(&(pid as u32)) { ++ eprintln!("init: respawning {} (pid {} exited)", unit_id.0, pid); ++ let mut resp_scheduler = Scheduler::new(); ++ resp_scheduler.schedule_start_and_report_errors( ++ &mut unit_store, ++ unit_id.clone(), ++ ); ++ resp_scheduler.step(&mut unit_store, &mut init_config); ++ for (uid, new_pid) in resp_scheduler.respawn_pids { ++ respawn_map.insert(new_pid, uid); ++ } ++ } ++ } ++ Err(err) => { ++ // EAGAIN is normal (no child exited yet). Other errors are ++ // unexpected but init must never crash — log and continue. ++ if err.errno() != syscall::EAGAIN { ++ eprintln!("init: waitpid error: {err}"); ++ } ++ std::thread::sleep(std::time::Duration::from_millis(100)); ++ } ++ } + } + } +diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs +index d42a4e57..782ca1ba 100644 +--- a/init/src/scheduler.rs ++++ b/init/src/scheduler.rs +@@ -5,6 +5,7 @@ use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; + + pub struct Scheduler { + pending: VecDeque, ++ pub respawn_pids: Vec<(UnitId, u32)>, + } + + struct Job { +@@ -20,6 +21,7 @@ impl Scheduler { + pub fn new() -> Scheduler { + Scheduler { + pending: VecDeque::new(), ++ respawn_pids: Vec::new(), + } + } + +@@ -43,7 +45,10 @@ impl Scheduler { + ) { + let loaded_units = unit_store.load_units(unit_id.clone(), errors); + for unit_id in loaded_units { +- if !unit_store.unit(&unit_id).conditions_met() { ++ let Some(unit) = unit_store.unit(&unit_id) else { ++ continue; ++ }; ++ if !unit.conditions_met() { + continue; + } + +@@ -62,7 +67,10 @@ impl Scheduler { + + match job.kind { + JobKind::Start => { +- let unit = unit_store.unit_mut(&job.unit); ++ let Some(unit) = unit_store.unit_mut(&job.unit) else { ++ eprintln!("init: unit {} not found in store, skipping", job.unit.0); ++ continue 'a; ++ }; + + for dep in &unit.info.requires_weak { + for pending_job in &self.pending { +@@ -73,14 +81,17 @@ impl Scheduler { + } + } + +- run(unit, init_config); ++ let pid = run(unit, init_config); ++ if let Some(pid) = pid { ++ self.respawn_pids.push((job.unit.clone(), pid)); ++ } + } + } + } + } + } + +-fn run(unit: &mut Unit, config: &mut InitConfig) { ++fn run(unit: &mut Unit, config: &mut InitConfig) -> Option { + match &unit.kind { + UnitKind::LegacyScript { script } => { + for cmd in script.clone() { +@@ -93,7 +104,7 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { + UnitKind::Service { service } => { + if config.skip_cmd.contains(&service.cmd) { + eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); +- return; ++ return None; + } + if config.log_debug { + eprintln!( +@@ -102,7 +113,7 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { + service.cmd, + ); + } +- service.spawn(&config.envs); ++ return service.spawn(&config.envs); + } + UnitKind::Target {} => { + if config.log_debug { +@@ -113,4 +124,5 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { + } + } + } ++ None + } +diff --git a/init/src/service.rs b/init/src/service.rs +index ed0023e9..e06e1b16 100644 +--- a/init/src/service.rs ++++ b/init/src/service.rs +@@ -22,6 +22,8 @@ pub struct Service { + pub inherit_envs: BTreeSet, + #[serde(rename = "type")] + pub type_: ServiceType, ++ #[serde(default)] ++ pub respawn: bool, + } + + #[derive(Clone, Debug, Default, Deserialize)] +@@ -35,7 +37,7 @@ pub enum ServiceType { + } + + impl Service { +- pub fn spawn(&self, base_envs: &BTreeMap) { ++ pub fn spawn(&self, base_envs: &BTreeMap) -> Option { + let mut command = Command::new(&self.cmd); + command.args(self.args.iter().map(|arg| subst_env(arg))); + command.env_clear(); +@@ -46,14 +48,20 @@ impl Service { + } + command.envs(base_envs).envs(&self.envs); + +- let (mut read_pipe, write_pipe) = io::pipe().unwrap(); ++ let (mut read_pipe, write_pipe) = match io::pipe() { ++ Ok(pair) => pair, ++ Err(err) => { ++ eprintln!("init: failed to create readiness pipe for {:?}: {err}", command); ++ return None; ++ } ++ }; + unsafe { pass_fd(&mut command, "INIT_NOTIFY", write_pipe.into()) }; + + let mut child = match command.spawn() { + Ok(child) => child, + Err(err) => { + eprintln!("init: failed to execute {:?}: {}", command, err); +- return; ++ return None; + } + }; + +@@ -81,23 +89,32 @@ impl Service { + }) => continue, + Ok(0) => { + eprintln!("init: {command:?} exited without notifying readiness"); +- return; ++ return None; + } + Ok(1) => break, + Ok(n) => { + eprintln!("init: incorrect amount of fds {n} returned"); +- return; ++ return None; + } + Err(err) => { + eprintln!("init: failed to wait for {command:?}: {err}"); +- return; ++ return None; + } + } + } + +- let current_namespace_fd = libredox::call::getns().expect("TODO"); +- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) +- .expect("TODO"); ++ let current_namespace_fd = match libredox::call::getns() { ++ Ok(fd) => fd, ++ Err(err) => { ++ eprintln!("init: failed to get current namespace for {command:?}: {err}"); ++ return None; ++ } ++ }; ++ if let Err(err) = ++ libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) ++ { ++ eprintln!("init: failed to register scheme {scheme:?} for {command:?}: {err}"); ++ } + } + ServiceType::Oneshot => { + drop(read_pipe); +@@ -112,8 +129,13 @@ impl Service { + } + } + } +- ServiceType::OneshotAsync => {} ++ ServiceType::OneshotAsync => { ++ if self.respawn { ++ return Some(child.id()); ++ } ++ } + } ++ None + } + } + +diff --git a/init/src/unit.rs b/init/src/unit.rs +index 98053cb2..a58bfb96 100644 +--- a/init/src/unit.rs ++++ b/init/src/unit.rs +@@ -23,8 +23,14 @@ impl UnitStore { + } + + pub fn set_runtime_target(&mut self, unit_id: UnitId) { +- assert!(self.runtime_target.is_none()); +- assert!(self.units.contains_key(&unit_id)); ++ if self.runtime_target.is_some() { ++ eprintln!("init: runtime target already set, ignoring {}", unit_id.0); ++ return; ++ } ++ if !self.units.contains_key(&unit_id) { ++ eprintln!("init: runtime target {} not found in unit store", unit_id.0); ++ return; ++ } + self.runtime_target = Some(unit_id); + } + +@@ -85,8 +91,10 @@ impl UnitStore { + let unit = self.load_single_unit(unit_id, errors); + if let Some(unit) = unit { + loaded_units.push(unit.clone()); +- for dep in &self.unit(&unit).info.requires_weak { +- pending_units.push(dep.clone()); ++ if let Some(u) = self.unit(&unit) { ++ for dep in &u.info.requires_weak { ++ pending_units.push(dep.clone()); ++ } + } + } + } +@@ -94,12 +102,12 @@ impl UnitStore { + loaded_units + } + +- pub fn unit(&self, unit: &UnitId) -> &Unit { +- self.units.get(unit).unwrap() ++ pub fn unit(&self, unit: &UnitId) -> Option<&Unit> { ++ self.units.get(unit) + } + +- pub fn unit_mut(&mut self, unit: &UnitId) -> &mut Unit { +- self.units.get_mut(unit).unwrap() ++ pub fn unit_mut(&mut self, unit: &UnitId) -> Option<&mut Unit> { ++ self.units.get_mut(unit) + } + } + +@@ -180,7 +188,7 @@ impl Unit { + ) -> io::Result { + let config = fs::read_to_string(config_path)?; + +- let Some(ext) = config_path.extension().map(|ext| ext.to_str().unwrap()) else { ++ let Some(ext) = config_path.extension().and_then(|ext| ext.to_str()) else { + let script = Script::from_str(&config, errors)?; + return Ok(Unit { + id, +diff --git a/logd/src/main.rs b/logd/src/main.rs +index 3636f1fa..559d8993 100644 +--- a/logd/src/main.rs ++++ b/logd/src/main.rs +@@ -6,18 +6,30 @@ use crate::scheme::LogScheme; + mod scheme; + + fn daemon(daemon: daemon::SchemeDaemon) -> ! { +- let socket = Socket::create().expect("logd: failed to create log scheme"); ++ let socket = match Socket::create() { ++ Ok(s) => s, ++ Err(e) => { ++ eprintln!("logd: failed to create log scheme: {e}"); ++ std::process::exit(1); ++ } ++ }; + + let mut scheme = LogScheme::new(&socket); + let handler = Blocking::new(&socket, 16); + + let _ = daemon.ready_sync_scheme(&socket, &mut scheme); + +- libredox::call::setrens(0, 0).expect("logd: failed to enter null namespace"); +- +- handler +- .process_requests_blocking(scheme) +- .expect("logd: failed to process requests"); ++ if let Err(e) = libredox::call::setrens(0, 0) { ++ eprintln!("logd: failed to enter null namespace: {e}"); ++ } ++ ++ match handler.process_requests_blocking(scheme) { ++ Ok(never) => match never {}, ++ Err(e) => { ++ eprintln!("logd: failed to process requests: {e}"); ++ std::process::exit(1); ++ } ++ } + } + + fn main() { +diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs +index 070de3d6..ef3e175c 100644 +--- a/logd/src/scheme.rs ++++ b/logd/src/scheme.rs +@@ -22,7 +22,7 @@ pub enum LogHandle { + + pub struct LogScheme<'sock> { + socket: &'sock Socket, +- kernel_debug: File, ++ kernel_debug: Option, + output_tx: Sender, + handles: HandleMap, + } +@@ -34,12 +34,24 @@ enum OutputCmd { + + impl<'sock> LogScheme<'sock> { + pub fn new(socket: &'sock Socket) -> Self { +- let kernel_debug = OpenOptions::new() ++ let kernel_debug = match OpenOptions::new() + .write(true) + .open("/scheme/debug") +- .unwrap(); ++ { ++ Ok(f) => Some(f), ++ Err(e) => { ++ eprintln!("logd: failed to open /scheme/debug: {e}"); ++ None ++ } ++ }; + +- let mut kernel_sys_log = std::fs::File::open("/scheme/sys/log").unwrap(); ++ let kernel_sys_log = match std::fs::File::open("/scheme/sys/log") { ++ Ok(f) => Some(f), ++ Err(e) => { ++ eprintln!("logd: failed to open /scheme/sys/log: {e}"); ++ None ++ } ++ }; + + let (output_tx, output_rx) = mpsc::channel::(); + +@@ -72,20 +84,28 @@ impl<'sock> LogScheme<'sock> { + } + }); + +- let output_tx2 = output_tx.clone(); +- std::thread::spawn(move || { +- let mut handle_buf = vec![]; +- let mut buf = [0; 4096]; +- buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); +- loop { +- let n = kernel_sys_log.read(&mut buf["kernel: ".len()..]).unwrap(); +- if n == 0 { +- // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue +- break; ++ if let Some(mut kernel_sys_log) = kernel_sys_log { ++ let output_tx2 = output_tx.clone(); ++ std::thread::spawn(move || { ++ let mut handle_buf = vec![]; ++ let mut buf = [0; 4096]; ++ buf[.."kernel: ".len()].copy_from_slice(b"kernel: "); ++ loop { ++ let n = match kernel_sys_log.read(&mut buf["kernel: ".len()..]) { ++ Ok(n) => n, ++ Err(e) => { ++ eprintln!("logd: error reading kernel log: {e}"); ++ break; ++ } ++ }; ++ if n == 0 { ++ // FIXME currently possible as /scheme/log/kernel presents a snapshot of the log queue ++ break; ++ } ++ Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); + } +- Self::write_logs(&output_tx2, &mut handle_buf, "kernel", &buf, None); +- } +- }); ++ }); ++ } + + LogScheme { + socket, +@@ -120,9 +140,9 @@ impl<'sock> LogScheme<'sock> { + let _ = kernel_debug.flush(); + } + +- output_tx +- .send(OutputCmd::Log(mem::take(handle_buf))) +- .unwrap(); ++ if let Err(e) = output_tx.send(OutputCmd::Log(mem::take(handle_buf))) { ++ eprintln!("logd: failed to send log output: {e}"); ++ } + } + + i += 1; +@@ -196,7 +216,7 @@ impl<'sock> SchemeSync for LogScheme<'sock> { + handle_buf, + context, + buf, +- Some(&mut self.kernel_debug), ++ self.kernel_debug.as_mut(), + ); + + Ok(buf.len()) +@@ -217,7 +237,10 @@ impl<'sock> SchemeSync for LogScheme<'sock> { + ) { + return Err(e); + } +- self.output_tx.send(OutputCmd::AddSink(new_fd)).unwrap(); ++ if let Err(e) = self.output_tx.send(OutputCmd::AddSink(new_fd)) { ++ eprintln!("logd: failed to add log sink: {e}"); ++ return Err(Error::new(EIO)); ++ } + + Ok(1) + } +diff --git a/randd/src/main.rs b/randd/src/main.rs +index d68dd732..5c330719 100644 +--- a/randd/src/main.rs ++++ b/randd/src/main.rs +@@ -41,7 +41,11 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { + let mut have_seeded = false; + #[cfg(target_arch = "x86_64")] + { +- if CpuId::new().get_feature_info().unwrap().has_rdrand() { ++ if CpuId::new() ++ .get_feature_info() ++ .map(|info| info.has_rdrand()) ++ .unwrap_or(false) ++ { + for i in 0..SEED_BYTES / 8 { + // We get 8 bytes at a time from rdrand instruction + let rand: u64; +@@ -81,7 +85,7 @@ fn create_rdrand_seed() -> [u8; SEED_BYTES] { + } + } // TODO integrate alternative entropy sources + if !have_seeded { +- println!("randd: Seeding failed, no entropy source. Random numbers on this platform are NOT SECURE"); ++ eprintln!("randd: no hardware entropy source, random numbers are NOT SECURE"); + } + rng + } +@@ -450,18 +454,32 @@ impl SchemeSync for RandScheme { + } + + fn daemon(daemon: daemon::SchemeDaemon) -> ! { +- let socket = Socket::create().expect("randd: failed to create rand scheme"); ++ let socket = match Socket::create() { ++ Ok(s) => s, ++ Err(e) => { ++ eprintln!("randd: failed to create rand scheme: {e}"); ++ std::process::exit(1); ++ } ++ }; + + let mut scheme = RandScheme::new(); +- let handler = Blocking::new(&socket, 16); + + let _ = daemon.ready_sync_scheme(&socket, &mut scheme); + +- libredox::call::setrens(0, 0).expect("randd: failed to enter null namespace"); ++ if let Err(e) = libredox::call::setrens(0, 0) { ++ eprintln!("randd: failed to enter null namespace: {e}"); ++ } + +- handler +- .process_requests_blocking(scheme) +- .expect("randd: failed to process events from zero scheme"); ++ loop { ++ let handler = Blocking::new(&socket, 16); ++ match handler.process_requests_blocking(scheme) { ++ Ok(never) => never, ++ Err(e) => { ++ eprintln!("randd: error processing requests: {e}"); ++ scheme = RandScheme::new(); ++ } ++ } ++ } + } + + fn main() { +diff --git a/zerod/src/main.rs b/zerod/src/main.rs +index c9bd1465..59f6b97c 100644 +--- a/zerod/src/main.rs ++++ b/zerod/src/main.rs +@@ -5,6 +5,7 @@ use scheme_utils::Blocking; + + mod scheme; + ++#[derive(Clone, Copy)] + enum Ty { + Null, + Zero, +@@ -15,21 +16,36 @@ fn main() { + } + + fn daemon(daemon: daemon::SchemeDaemon) -> ! { +- let ty = match &*std::env::args().nth(1).unwrap() { +- "null" => Ty::Null, +- "zero" => Ty::Zero, +- _ => panic!("needs to be called with either null or zero as argument"), ++ let ty = match std::env::args().nth(1).as_deref() { ++ Some("null") => Ty::Null, ++ Some("zero") | None => Ty::Zero, ++ Some(other) => { ++ eprintln!("zerod: unknown argument '{other}', use 'null' or 'zero'"); ++ std::process::exit(1); ++ } + }; + +- let socket = Socket::create().expect("zerod: failed to create zero scheme"); ++ let socket = match Socket::create() { ++ Ok(s) => s, ++ Err(e) => { ++ eprintln!("zerod: failed to create zero scheme: {e}"); ++ std::process::exit(1); ++ } ++ }; + let mut zero_scheme = ZeroScheme(ty); +- let zero_handler = Blocking::new(&socket, 16); + + let _ = daemon.ready_sync_scheme(&socket, &mut zero_scheme); + +- libredox::call::setrens(0, 0).expect("zerod: failed to enter null namespace"); +- +- zero_handler +- .process_requests_blocking(zero_scheme) +- .expect("zerod: failed to process events from zero scheme"); ++ if let Err(e) = libredox::call::setrens(0, 0) { ++ eprintln!("zerod: failed to enter null namespace: {e}"); ++ } ++ ++ loop { ++ let zero_handler = Blocking::new(&socket, 16); ++ let scheme = ZeroScheme(ty); ++ match zero_handler.process_requests_blocking(scheme) { ++ Ok(never) => never, ++ Err(e) => eprintln!("zerod: error processing requests: {e}"), ++ } ++ } + } diff --git a/local/patches/kernel/P1-ioapic-hpet-nmi.patch b/local/patches/kernel/P1-ioapic-hpet-nmi.patch new file mode 100644 index 00000000..c0cf400e --- /dev/null +++ b/local/patches/kernel/P1-ioapic-hpet-nmi.patch @@ -0,0 +1,1529 @@ +diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs +index 4dc23883..c52e0ab4 100644 +--- a/src/acpi/madt/arch/x86.rs ++++ b/src/acpi/madt/arch/x86.rs +@@ -10,7 +10,8 @@ use crate::{ + }, + cpu_set::LogicalCpuId, + memory::{ +- allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch, ++ allocate_p2frame, map_device_memory, Frame, KernelMapper, Page, PageFlags, ++ PhysicalAddress, RmmA, RmmArch, + VirtualAddress, PAGE_SIZE, + }, + startup::AP_READY, +@@ -20,6 +21,55 @@ use super::{Madt, MadtEntry}; + + const TRAMPOLINE: usize = 0x8000; + static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline")); ++const AP_STARTUP_TIMEOUT: u32 = 100_000_000; ++ ++fn wait_for_ap_flag(flag: *mut u64, description: &str, apic_id: u32) -> bool { ++ let mut timeout = AP_STARTUP_TIMEOUT; ++ while unsafe { (*flag.cast::()).load(Ordering::SeqCst) } == 0 { ++ hint::spin_loop(); ++ timeout -= 1; ++ if timeout == 0 { ++ debug!("AP {} {} timed out", apic_id, description); ++ return false; ++ } ++ } ++ true ++} ++ ++fn wait_for_kernel_ap_ready(apic_id: u32) -> bool { ++ let mut timeout = AP_STARTUP_TIMEOUT; ++ while !AP_READY.load(Ordering::SeqCst) { ++ hint::spin_loop(); ++ timeout -= 1; ++ if timeout == 0 { ++ debug!("AP {} kernel startup timed out", apic_id); ++ return false; ++ } ++ } ++ true ++} ++ ++fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option { ++ madt.iter().find_map(|entry| match entry { ++ MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid), ++ _ => None, ++ }) ++} ++ ++fn apply_lapic_address_override(local_apic: &mut crate::arch::device::local_apic::LocalApic, addr: u64) { ++ if local_apic.x2 || addr == 0 { ++ return; ++ } ++ ++ let Ok(physaddr) = usize::try_from(addr) else { ++ warn!("Ignoring LAPIC address override {:#x}: does not fit host usize", addr); ++ return; ++ }; ++ ++ let mapped = unsafe { map_device_memory(PhysicalAddress::new(physaddr), 4096) }.data(); ++ local_apic.address = mapped; ++ debug!("Applied LAPIC address override: {:#x}", addr); ++} + + pub(super) fn init(madt: Madt) { + let local_apic = unsafe { the_local_apic() }; +@@ -35,18 +85,19 @@ pub(super) fn init(madt: Madt) { + return; + } + +- // Map trampoline ++ // Map trampoline writable and executable (trampoline page holds both code ++ // and AP argument data — AP writes ap_ready on the same page, so W^X is ++ // not possible without splitting code/data across pages). + let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE)); + let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE)); + let (result, page_table_physaddr) = unsafe { +- //TODO: do not have writable and executable! + let mut mapper = KernelMapper::lock_rw(); + + let result = mapper + .map_phys( + trampoline_page.start_address(), + trampoline_frame.base(), +- PageFlags::new().execute(true).write(true), ++ PageFlags::new().write(true).execute(true), + ) + .expect("failed to map trampoline"); + +@@ -75,12 +126,11 @@ pub(super) fn init(madt: Madt) { + let cpu_id = LogicalCpuId::next(); + + // Allocate a stack +- let stack_start = RmmA::phys_to_virt( +- allocate_p2frame(4) +- .expect("no more frames in acpi stack_start") +- .base(), +- ) +- .data(); ++ let Some(stack_frame) = allocate_p2frame(4) else { ++ warn!("Unable to allocate AP bootstrap stack for local APIC {}", ap_local_apic.id); ++ continue; ++ }; ++ let stack_start = RmmA::phys_to_virt(stack_frame.base()).data(); + let stack_end = stack_start + (PAGE_SIZE << 4); + + let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end); +@@ -138,15 +188,168 @@ pub(super) fn init(madt: Madt) { + } + + // Wait for trampoline ready +- while unsafe { (*ap_ready.cast::()).load(Ordering::SeqCst) } == 0 { ++ let ready = wait_for_ap_flag(ap_ready, "trampoline startup", u32::from(ap_local_apic.id)); ++ let kernel_ready = ready && wait_for_kernel_ap_ready(u32::from(ap_local_apic.id)); ++ ++ if !kernel_ready { ++ warn!("Skipping local APIC {} after startup timeout", ap_local_apic.id); ++ } ++ ++ RmmA::invalidate_all(); ++ } ++ } else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry { ++ let x2id = ap_x2apic.x2apic_id; ++ let x2flags = ap_x2apic.flags; ++ if x2id == me.get() { ++ debug!(" This is my local x2APIC"); ++ } else if x2flags & 1 == 1 { ++ let cpu_id = LogicalCpuId::next(); ++ ++ let Some(stack_frame) = allocate_p2frame(4) else { ++ warn!( ++ "Unable to allocate AP bootstrap stack for x2APIC {}", ++ x2id ++ ); ++ continue; ++ }; ++ let stack_start = RmmA::phys_to_virt(stack_frame.base()).data(); ++ let stack_end = stack_start + (PAGE_SIZE << 4); ++ ++ let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end); ++ let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id); ++ ++ let args = KernelArgsAp { ++ stack_end: stack_end as *mut u8, ++ cpu_id, ++ pcr_ptr, ++ idt_ptr, ++ }; ++ ++ let ap_ready = (TRAMPOLINE + 8) as *mut u64; ++ let ap_args_ptr = unsafe { ap_ready.add(1) }; ++ let ap_page_table = unsafe { ap_ready.add(2) }; ++ let ap_code = unsafe { ap_ready.add(3) }; ++ ++ unsafe { ++ ap_ready.write(0); ++ ap_args_ptr.write(&args as *const _ as u64); ++ ap_page_table.write(page_table_physaddr as u64); ++ #[expect(clippy::fn_to_numeric_cast)] ++ ap_code.write(kstart_ap as u64); ++ core::arch::asm!(""); ++ }; ++ AP_READY.store(false, Ordering::SeqCst); ++ ++ // Same ICR delivery-mode bits are used by xAPIC and x2APIC; only the ++ // destination field encoding changes between the MMIO and MSR forms. ++ const ICR_INIT_ASSERT: u64 = 0x4500; ++ const ICR_STARTUP: u64 = 0x4600; ++ ++ // ICR bits 10:8 = 0b101 (INIT), bit 14 = level assert. ++ // Send INIT IPI (x2APIC always uses 32-bit APIC ID in bits 32-63) ++ { ++ let mut icr = ICR_INIT_ASSERT; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Wait for INIT delivery (~10 μs de-assert window per Intel SDM) ++ for _ in 0..100_000 { + hint::spin_loop(); + } +- while !AP_READY.load(Ordering::SeqCst) { ++ ++ // ICR bits 10:8 = 0b110 (STARTUP), bit 14 = level assert. ++ // Send STARTUP IPI ++ { ++ let ap_segment = (TRAMPOLINE >> 12) & 0xFF; ++ let mut icr = ICR_STARTUP | ap_segment as u64; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Wait ~200 μs, then send second STARTUP IPI per the universal ++ // startup algorithm. ++ for _ in 0..2_000_000 { + hint::spin_loop(); + } ++ { ++ let ap_segment = (TRAMPOLINE >> 12) & 0xFF; ++ let mut icr = ICR_STARTUP | ap_segment as u64; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Known limitation: cpu_id and per-CPU bootstrap state are allocated ++ // before the timeout checks, so a timed-out AP still consumes a ++ // logical CPU slot until startup rollback/teardown is implemented. ++ let ready = wait_for_ap_flag(ap_ready, "trampoline startup", x2id); ++ let kernel_ready = ready && wait_for_kernel_ap_ready(x2id); ++ ++ if !kernel_ready { ++ warn!("Skipping x2APIC {} after startup timeout", x2id); ++ } + + RmmA::invalidate_all(); + } ++ } else if let MadtEntry::LocalApicNmi(nmi) = madt_entry { ++ let target_id = nmi.processor; ++ let nmi_pin = nmi.nmi_pin; ++ let nmi_flags = nmi.flags; ++ if target_id == 0xFF { ++ debug!( ++ " NMI: all processors, pin={}, flags={:#x}", ++ nmi_pin, nmi_flags ++ ); ++ unsafe { ++ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ } ++ } else { ++ let my_apic_id = local_apic.id().get() as u8; ++ if target_id == my_apic_id { ++ debug!( ++ " NMI: processor {}, pin={}, flags={:#x}", ++ target_id, nmi_pin, nmi_flags ++ ); ++ unsafe { ++ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ } ++ } ++ } ++ } else if let MadtEntry::LocalX2ApicNmi(nmi) = madt_entry { ++ let target_uid = nmi.processor_uid; ++ let nmi_pin = nmi.nmi_pin; ++ let nmi_flags = nmi.flags; ++ if target_uid == 0xFFFFFFFF { ++ debug!( ++ " x2APIC NMI: all processors, pin={}, flags={:#x}", ++ nmi_pin, nmi_flags ++ ); ++ unsafe { ++ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ } ++ } else { ++ let current_uid = current_x2apic_processor_uid(&madt, me.get()); ++ if current_uid == Some(target_uid) { ++ debug!( ++ " x2APIC NMI: uid {}, pin={}, flags={:#x}", ++ target_uid, nmi_pin, nmi_flags ++ ); ++ unsafe { ++ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ } ++ } else { ++ debug!( ++ " x2APIC NMI: skipping uid {} on current uid {:?}", ++ target_uid, current_uid ++ ); ++ } ++ } ++ } else if let MadtEntry::LapicAddressOverride(addr) = madt_entry { ++ let lapic_addr = addr.local_apic_address; ++ if lapic_addr != 0 { ++ debug!(" LAPIC address override: {:#x}", lapic_addr); ++ apply_lapic_address_override(local_apic, lapic_addr); ++ } + } + } + +diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs +index 3159b9c4..23551c64 100644 +--- a/src/acpi/madt/mod.rs ++++ b/src/acpi/madt/mod.rs +@@ -146,6 +146,52 @@ pub struct MadtGicd { + _reserved2: [u8; 3], + } + ++/// MADT Local x2APIC (entry type 0x9) ++/// Used by modern AMD and Intel platforms with APIC IDs >= 255. ++#[derive(Clone, Copy, Debug)] ++#[repr(C, packed)] ++pub struct MadtLocalX2Apic { ++ _reserved: u16, ++ pub x2apic_id: u32, ++ pub flags: u32, ++ pub processor_uid: u32, ++} ++ ++/// MADT Local APIC NMI (entry type 0x4) ++/// Configures NMI routing to a processor's LINT0/LINT1 pin. ++#[derive(Clone, Copy, Debug)] ++#[repr(C, packed)] ++pub struct MadtLocalApicNmi { ++ pub processor: u8, // 0xFF = all processors ++ pub flags: u16, // bits 0-1: polarity, bits 2-3: trigger mode ++ pub nmi_pin: u8, // 0 = LINT0, 1 = LINT1 ++} ++ ++/// MADT Local APIC Address Override (entry type 0x5) ++/// Provides 64-bit override for the 32-bit local APIC address. ++#[derive(Clone, Copy, Debug)] ++#[repr(C, packed)] ++pub struct MadtLapicAddressOverride { ++ _reserved: u16, ++ pub local_apic_address: u64, ++} ++ ++/// MADT Local x2APIC NMI (entry type 0xA) ++/// x2APIC equivalent of type 0x4 for APIC IDs >= 255. ++#[derive(Clone, Copy, Debug)] ++#[repr(C, packed)] ++pub struct MadtLocalX2ApicNmi { ++ _reserved: u16, ++ pub processor_uid: u32, // 0xFFFFFFFF = all processors ++ pub flags: u16, ++ pub nmi_pin: u8, // 0 = LINT0, 1 = LINT1 ++ _reserved2: u8, ++} ++ ++const _: () = assert!(size_of::() == 4); ++const _: () = assert!(size_of::() == 10); ++const _: () = assert!(size_of::() == 10); ++ + /// MADT Entries + #[derive(Debug)] + #[allow(dead_code)] +@@ -160,6 +206,14 @@ pub enum MadtEntry { + InvalidGicc(usize), + Gicd(&'static MadtGicd), + InvalidGicd(usize), ++ LocalX2Apic(&'static MadtLocalX2Apic), ++ InvalidLocalX2Apic(usize), ++ LocalApicNmi(&'static MadtLocalApicNmi), ++ InvalidLocalApicNmi(usize), ++ LapicAddressOverride(&'static MadtLapicAddressOverride), ++ InvalidLapicAddressOverride(usize), ++ LocalX2ApicNmi(&'static MadtLocalX2ApicNmi), ++ InvalidLocalX2ApicNmi(usize), + Unknown(u8), + } + +@@ -176,6 +230,10 @@ impl Iterator for MadtIter { + let entry_len = + unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize; + ++ if entry_len < 2 { ++ return None; ++ } ++ + if self.i + entry_len <= self.sdt.data_len() { + let item = match entry_type { + 0x0 => { +@@ -224,6 +282,44 @@ impl Iterator for MadtIter { + MadtEntry::InvalidGicd(entry_len) + } + } ++ 0x9 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalX2Apic(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalX2Apic) ++ }) ++ } else { ++ MadtEntry::InvalidLocalX2Apic(entry_len) ++ } ++ } ++ 0x4 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalApicNmi(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApicNmi) ++ }) ++ } else { ++ MadtEntry::InvalidLocalApicNmi(entry_len) ++ } ++ } ++ 0x5 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LapicAddressOverride(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) ++ as *const MadtLapicAddressOverride) ++ }) ++ } else { ++ MadtEntry::InvalidLapicAddressOverride(entry_len) ++ } ++ } ++ 0xA => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalX2ApicNmi(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) ++ as *const MadtLocalX2ApicNmi) ++ }) ++ } else { ++ MadtEntry::InvalidLocalX2ApicNmi(entry_len) ++ } ++ } + _ => MadtEntry::Unknown(entry_type), + }; + +diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs +index 59e35265..d4c81f11 100644 +--- a/src/acpi/mod.rs ++++ b/src/acpi/mod.rs +@@ -10,6 +10,8 @@ use crate::memory::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch}; + + use self::{hpet::Hpet, madt::Madt, rsdp::Rsdp, rsdt::Rsdt, rxsdt::Rxsdt, sdt::Sdt, xsdt::Xsdt}; + ++const MAX_SDT_SIZE: usize = 16 * 1024 * 1024; ++ + #[cfg(target_arch = "aarch64")] + mod gtdt; + pub mod hpet; +@@ -22,39 +24,79 @@ pub mod sdt; + mod spcr; + mod xsdt; + +-unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) { ++unsafe fn map_linearly( ++ addr: PhysicalAddress, ++ len: usize, ++ mapper: &mut crate::memory::PageMapper, ++) -> bool { + unsafe { + let base = PhysicalAddress::new(crate::memory::round_down_pages(addr.data())); +- let aligned_len = crate::memory::round_up_pages(len + (addr.data() - base.data())); ++ let Some(total_len) = len.checked_add(addr.data() - base.data()) else { ++ error!("ACPI table mapping length overflow at {:#x}", addr.data()); ++ return false; ++ }; ++ let aligned_len = crate::memory::round_up_pages(total_len); + + for page_idx in 0..aligned_len / crate::memory::PAGE_SIZE { +- let (_, flush) = mapper ++ let Some((_virt, flush)) = mapper + .map_linearly( + base.add(page_idx * crate::memory::PAGE_SIZE), + PageFlags::new(), + ) +- .expect("failed to linearly map SDT"); ++ else { ++ error!( ++ "failed to linearly map ACPI table page at {:#x}", ++ base.add(page_idx * crate::memory::PAGE_SIZE).data() ++ ); ++ return false; ++ }; + flush.flush(); + } ++ ++ true + } + } + +-pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper) -> &'static Sdt { ++pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper) -> Option<&'static Sdt> { + let sdt; + + unsafe { + const SDT_SIZE: usize = size_of::(); +- map_linearly(sdt_address, SDT_SIZE, mapper); ++ if !map_linearly(sdt_address, SDT_SIZE, mapper) { ++ return None; ++ } + + sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt); + +- map_linearly( ++ let total_len = sdt.length as usize; ++ if total_len < SDT_SIZE { ++ warn!( ++ "ACPI table {:?} at {:#x} shorter than header ({})", ++ sdt.signature, ++ sdt_address.data(), ++ total_len ++ ); ++ return None; ++ } ++ if total_len > MAX_SDT_SIZE { ++ warn!( ++ "ACPI table {:?} at {:#x} exceeds max supported size ({})", ++ sdt.signature, ++ sdt_address.data(), ++ total_len ++ ); ++ return None; ++ } ++ ++ if !map_linearly( + sdt_address.add(SDT_SIZE), +- sdt.length as usize - SDT_SIZE, ++ total_len - SDT_SIZE, + mapper, +- ); ++ ) { ++ return None; ++ } + } +- sdt ++ Some(sdt) + } + + #[repr(C, packed)] +@@ -95,7 +137,19 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { + + if let Some(rsdp) = rsdp_opt { + debug!("SDT address: {:#x}", rsdp.sdt_address().data()); +- let rxsdt = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw()); ++ let Some(rxsdt) = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw()) else { ++ error!("Unable to map RSDT/XSDT header"); ++ return; ++ }; ++ ++ if !rxsdt.validate_checksum() { ++ warn!( ++ "Root ACPI table {:?} at {:#x} has invalid checksum; ignoring ACPI", ++ rxsdt.signature, ++ rsdp.sdt_address().data() ++ ); ++ return; ++ } + + let rxsdt = if let Some(rsdt) = Rsdt::new(rxsdt) { + let mut initialized = false; +@@ -132,12 +186,28 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { + // TODO: Don't touch ACPI tables in kernel? + + for sdt in rxsdt.iter() { +- get_sdt(sdt, &mut KernelMapper::lock_rw()); ++ if get_sdt(sdt, &mut KernelMapper::lock_rw()).is_none() { ++ warn!("Skipping unreadable ACPI table at {:#x}", sdt.data()); ++ } + } + + for sdt_address in rxsdt.iter() { ++ let Some(sdt) = get_sdt(sdt_address, &mut KernelMapper::lock_rw()) else { ++ warn!("Skipping ACPI table at {:#x}: unable to map safely", sdt_address.data()); ++ continue; ++ }; + let sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt); + ++ if !sdt.validate_checksum() { ++ let sig = &sdt.signature; ++ warn!( ++ "ACPI table {:?} at {:#x} has invalid checksum", ++ sig, ++ sdt_address.data() ++ ); ++ continue; ++ } ++ + let signature = get_sdt_signature(sdt); + if let Some(ref mut ptrs) = *(SDT_POINTERS.write()) { + ptrs.insert(signature, sdt); +@@ -198,8 +268,7 @@ macro_rules! find_one_sdt { + } + + pub fn get_sdt_signature(sdt: &'static Sdt) -> SdtSignature { +- let signature = +- String::from_utf8(sdt.signature.to_vec()).expect("Error converting signature to string"); ++ let signature = String::from_utf8_lossy(&sdt.signature).into_owned(); + (signature, sdt.oem_id, sdt.oem_table_id) + } + +diff --git a/src/acpi/rsdp.rs b/src/acpi/rsdp.rs +index f10c5ac9..571aeeec 100644 +--- a/src/acpi/rsdp.rs ++++ b/src/acpi/rsdp.rs +@@ -1,5 +1,8 @@ + use rmm::PhysicalAddress; + ++const RSDP_V1_SIZE: usize = 20; ++const RSDP_V2_MIN_SIZE: usize = size_of::(); ++ + /// RSDP + #[derive(Copy, Clone, Debug)] + #[repr(C, packed)] +@@ -17,10 +20,33 @@ pub struct Rsdp { + + impl Rsdp { + pub unsafe fn get_rsdp(already_supplied_rsdp: Option<*const u8>) -> Option { +- already_supplied_rsdp.map(|rsdp_ptr| { +- // TODO: Validate +- unsafe { *(rsdp_ptr as *const Rsdp) } +- }) ++ let rsdp_ptr = already_supplied_rsdp?; ++ let rsdp = unsafe { *(rsdp_ptr as *const Rsdp) }; ++ ++ if rsdp.signature != *b"RSD PTR " { ++ warn!("RSDP signature invalid"); ++ return None; ++ } ++ ++ if !checksum_ok(rsdp_ptr, RSDP_V1_SIZE) { ++ warn!("RSDP base checksum invalid"); ++ return None; ++ } ++ ++ if rsdp.revision >= 2 { ++ let length = rsdp._length as usize; ++ if length < RSDP_V2_MIN_SIZE { ++ warn!("RSDP revision {} length {} too small", rsdp.revision, length); ++ return None; ++ } ++ ++ if !checksum_ok(rsdp_ptr, length) { ++ warn!("RSDP extended checksum invalid"); ++ return None; ++ } ++ } ++ ++ Some(rsdp) + } + + /// Get the RSDT or XSDT address +@@ -32,3 +58,8 @@ impl Rsdp { + }) + } + } ++ ++fn checksum_ok(ptr: *const u8, len: usize) -> bool { ++ let bytes = unsafe { core::slice::from_raw_parts(ptr, len) }; ++ bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte)) == 0 ++} +diff --git a/src/acpi/sdt.rs b/src/acpi/sdt.rs +index 83ff67da..f49b6212 100644 +--- a/src/acpi/sdt.rs ++++ b/src/acpi/sdt.rs +@@ -24,4 +24,15 @@ impl Sdt { + let header_size = size_of::(); + total_size.saturating_sub(header_size) + } ++ ++ /// Validate that the sum of all bytes in this table is zero (ACPI spec requirement). ++ /// Returns false if the length is too small or the checksum doesn't match. ++ pub fn validate_checksum(&self) -> bool { ++ let len = self.length as usize; ++ if len < size_of::() { ++ return false; ++ } ++ let bytes = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, len) }; ++ bytes.iter().fold(0u8, |sum, &b| sum.wrapping_add(b)) == 0 ++ } + } +diff --git a/src/arch/aarch64/start.rs b/src/arch/aarch64/start.rs +index e1c8cfb4..65e3fe33 100644 +--- a/src/arch/aarch64/start.rs ++++ b/src/arch/aarch64/start.rs +@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { + dtb::serial::init_early(dtb); + } + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + // Initialize RMM +diff --git a/src/arch/riscv64/start.rs b/src/arch/riscv64/start.rs +index 2551968f..a825536a 100644 +--- a/src/arch/riscv64/start.rs ++++ b/src/arch/riscv64/start.rs +@@ -97,7 +97,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { + init_early(dtb); + } + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + if let Some(dtb) = &dtb { +diff --git a/src/arch/x86_shared/cpuid.rs b/src/arch/x86_shared/cpuid.rs +index b3683125..be7db1be 100644 +--- a/src/arch/x86_shared/cpuid.rs ++++ b/src/arch/x86_shared/cpuid.rs +@@ -1,11 +1,8 @@ + use raw_cpuid::{CpuId, CpuIdResult, ExtendedFeatures, FeatureInfo}; + ++#[cfg(target_arch = "x86_64")] + pub fn cpuid() -> CpuId { +- // FIXME check for cpuid availability during early boot and error out if it doesn't exist. + CpuId::with_cpuid_fn(|a, c| { +- #[cfg(target_arch = "x86")] +- let result = unsafe { core::arch::x86::__cpuid_count(a, c) }; +- #[cfg(target_arch = "x86_64")] + let result = unsafe { core::arch::x86_64::__cpuid_count(a, c) }; + CpuIdResult { + eax: result.eax, +@@ -16,6 +13,19 @@ pub fn cpuid() -> CpuId { + }) + } + ++#[cfg(target_arch = "x86")] ++pub fn cpuid() -> CpuId { ++ CpuId::with_cpuid_fn(|a, c| { ++ let result = unsafe { core::arch::x86::__cpuid_count(a, c) }; ++ CpuIdResult { ++ eax: result.eax, ++ ebx: result.ebx, ++ ecx: result.ecx, ++ edx: result.edx, ++ } ++ }) ++} ++ + #[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))] + pub fn feature_info() -> FeatureInfo { + cpuid() +diff --git a/src/arch/x86_shared/device/ioapic.rs b/src/arch/x86_shared/device/ioapic.rs +index fb66d3bf..5938540f 100644 +--- a/src/arch/x86_shared/device/ioapic.rs ++++ b/src/arch/x86_shared/device/ioapic.rs +@@ -14,6 +14,9 @@ pub struct IoApicRegs { + pointer: *const u32, + } + impl IoApicRegs { ++ fn redirection_index_valid(&mut self, idx: u8) -> bool { ++ idx <= self.max_redirection_table_entries() ++ } + fn ioregsel(&self) -> *const u32 { + self.pointer + } +@@ -44,21 +47,28 @@ impl IoApicRegs { + pub fn read_ioapicver(&mut self) -> u32 { + self.read_reg(0x01) + } +- pub fn read_ioredtbl(&mut self, idx: u8) -> u64 { +- assert!(idx < 24); ++ pub fn read_ioredtbl(&mut self, idx: u8) -> Option { ++ if !self.redirection_index_valid(idx) { ++ warn!("IOAPIC read_ioredtbl index {} out of range", idx); ++ return None; ++ } + let lo = self.read_reg(0x10 + idx * 2); + let hi = self.read_reg(0x10 + idx * 2 + 1); + +- u64::from(lo) | (u64::from(hi) << 32) ++ Some(u64::from(lo) | (u64::from(hi) << 32)) + } +- pub fn write_ioredtbl(&mut self, idx: u8, value: u64) { +- assert!(idx < 24); ++ pub fn write_ioredtbl(&mut self, idx: u8, value: u64) -> bool { ++ if !self.redirection_index_valid(idx) { ++ warn!("IOAPIC write_ioredtbl index {} out of range", idx); ++ return false; ++ } + + let lo = value as u32; + let hi = (value >> 32) as u32; + + self.write_reg(0x10 + idx * 2, lo); + self.write_reg(0x10 + idx * 2 + 1, hi); ++ true + } + + pub fn max_redirection_table_entries(&mut self) -> u8 { +@@ -92,17 +102,22 @@ impl IoApic { + } + /// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode). + #[allow(dead_code)] +- pub fn map(&self, idx: u8, info: MapInfo) { +- self.regs.lock().write_ioredtbl(idx, info.as_raw()) ++ pub fn map(&self, idx: u8, info: MapInfo) -> bool { ++ let Some(raw) = info.as_raw() else { ++ return false; ++ }; ++ self.regs.lock().write_ioredtbl(idx, raw) + } + pub fn set_mask(&self, gsi: u32, mask: bool) { + let idx = (gsi - self.gsi_start) as u8; + let mut guard = self.regs.lock(); + +- let mut reg = guard.read_ioredtbl(idx); ++ let Some(mut reg) = guard.read_ioredtbl(idx) else { ++ return; ++ }; + reg &= !(1 << 16); + reg |= u64::from(mask) << 16; +- guard.write_ioredtbl(idx, reg); ++ let _ = guard.write_ioredtbl(idx, reg); + } + } + +@@ -149,19 +164,21 @@ pub struct MapInfo { + } + + impl MapInfo { +- pub fn as_raw(&self) -> u64 { +- assert!(self.vector >= 0x20); +- assert!(self.vector <= 0xFE); ++ pub fn as_raw(&self) -> Option { ++ if !(0x20..=0xFE).contains(&self.vector) { ++ warn!("Refusing to map IOAPIC vector outside valid range: {:#x}", self.vector); ++ return None; ++ } + + // TODO: Check for reserved fields. + +- (u64::from(self.dest.get()) << 56) ++ Some((u64::from(self.dest.get()) << 56) + | (u64::from(self.mask) << 16) + | ((self.trigger_mode as u64) << 15) + | ((self.polarity as u64) << 13) + | ((self.dest_mode as u64) << 11) + | ((self.delivery_mode as u64) << 8) +- | u64::from(self.vector) ++ | u64::from(self.vector)) + } + } + +@@ -175,7 +192,7 @@ impl fmt::Debug for IoApic { + + let count = guard.max_redirection_table_entries(); + f.debug_list() +- .entries((0..count).map(|i| guard.read_ioredtbl(i))) ++ .entries((0..=count).filter_map(|i| guard.read_ioredtbl(i))) + .finish() + } + } +@@ -237,11 +254,14 @@ pub unsafe fn handle_ioapic(madt_ioapic: &'static MadtIoApic) { + let ioapic_registers = virt.data() as *const u32; + let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base); + +- assert_eq!( +- ioapic.regs.lock().id(), +- madt_ioapic.id, +- "mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC" +- ); ++ let detected_id = ioapic.regs.lock().id(); ++ if detected_id != madt_ioapic.id { ++ warn!( ++ "mismatched ACPI MADT I/O APIC ID: MADT={}, IOAPIC={}; continuing with detected hardware", ++ madt_ioapic.id, ++ detected_id ++ ); ++ } + + (*IOAPICS.get()).get_or_insert_with(Vec::new).push(ioapic); + } +@@ -310,11 +330,14 @@ pub unsafe fn init() { + } + } + } +- println!( +- "I/O APICs: {:?}, overrides: {:?}", +- ioapics(), +- src_overrides() +- ); ++ // Sanitize all IOAPIC redirection entries: mask everything first to clear ++ // stale firmware/emulator defaults. Entries are selectively unmasked below. ++ for ioapic in ioapics().iter() { ++ let max_idx = ioapic.count; ++ for idx in 0..=max_idx { ++ ioapic.set_mask(ioapic.gsi_start + u32::from(idx), true); ++ } ++ } + + // map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it + // wouldn't have been disabled due to this I/O APIC) +@@ -329,7 +352,6 @@ pub unsafe fn init() { + .iter() + .any(|over| over.bus_irq == legacy_irq) + { +- // there's an IRQ conflict, making this legacy IRQ inaccessible. + continue; + } + ( +@@ -349,7 +371,6 @@ pub unsafe fn init() { + let redir_tbl_index = (gsi - apic.gsi_start) as u8; + + let map_info = MapInfo { +- // only send to the BSP + dest: bsp_apic_id, + dest_mode: DestinationMode::Physical, + delivery_mode: DeliveryMode::Fixed, +@@ -366,7 +387,31 @@ pub unsafe fn init() { + }, + vector: 32 + legacy_irq, + }; +- apic.map(redir_tbl_index, map_info); ++ if !apic.map(redir_tbl_index, map_info) { ++ warn!( ++ "Unable to map legacy IRQ {} (GSI {}) through IOAPIC index {}", ++ legacy_irq, gsi, redir_tbl_index ++ ); ++ } ++ ++ // IRQ 0 (timer) is often overridden to GSI 2, but some platforms ++ // (including QEMU) route the HPET timer directly to GSI 0 regardless ++ // of the MADT override. Map GSI 0 as well so the timer works on both ++ // virtual and physical hardware. ++ if legacy_irq == 0 && gsi != u32::from(legacy_irq) { ++ if let Some(apic0) = find_ioapic(u32::from(legacy_irq)) { ++ let idx0 = (u32::from(legacy_irq) - apic0.gsi_start) as u8; ++ apic0.map(idx0, MapInfo { ++ dest: bsp_apic_id, ++ dest_mode: DestinationMode::Physical, ++ delivery_mode: DeliveryMode::Fixed, ++ mask: false, ++ polarity: ApicPolarity::ActiveHigh, ++ trigger_mode: ApicTriggerMode::Edge, ++ vector: 32, ++ }); ++ } ++ } + } + println!( + "I/O APICs: {:?}, overrides: {:?}", +@@ -406,7 +451,7 @@ fn resolve(irq: u8) -> u32 { + fn find_ioapic(gsi: u32) -> Option<&'static IoApic> { + ioapics() + .iter() +- .find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count)) ++ .find(|apic| gsi >= apic.gsi_start && gsi <= apic.gsi_start + u32::from(apic.count)) + } + + pub unsafe fn mask(irq: u8) { +diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs +index b6afe02a..e256d160 100644 +--- a/src/arch/x86_shared/device/local_apic.rs ++++ b/src/arch/x86_shared/device/local_apic.rs +@@ -103,7 +103,7 @@ impl LocalApic { + ApicId::new(if self.x2 { + unsafe { rdmsr(IA32_X2APIC_APICID) as u32 } + } else { +- unsafe { self.read(0x20) } ++ unsafe { self.read(0x20) >> 24 } + }) + } + +@@ -126,7 +126,14 @@ impl LocalApic { + pub fn set_icr(&mut self, value: u64) { + if self.x2 { + unsafe { ++ const PENDING: u32 = 1 << 12; ++ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING { ++ core::hint::spin_loop(); ++ } + wrmsr(IA32_X2APIC_ICR, value); ++ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING { ++ core::hint::spin_loop(); ++ } + } + } else { + unsafe { +@@ -256,6 +263,30 @@ impl LocalApic { + } + } + } ++ /// Configure LVT NMI entry. `pin` is 0 for LINT0, 1 for LINT1. ++ /// `flags` encodes polarity and trigger mode per MADT NMI spec. ++ pub unsafe fn set_lvt_nmi(&mut self, pin: u8, flags: u16) { ++ let lvt_value = (flags as u32) | 0x400; /* bit 10 = NMI delivery mode, masked off if flags don't set it */ ++ unsafe { ++ match pin { ++ 0 => { ++ if self.x2 { ++ wrmsr(IA32_X2APIC_LVT_LINT0, u64::from(lvt_value)); ++ } else { ++ self.write(0x350, lvt_value); ++ } ++ } ++ 1 => { ++ if self.x2 { ++ wrmsr(IA32_X2APIC_LVT_LINT1, u64::from(lvt_value)); ++ } else { ++ self.write(0x360, lvt_value); ++ } ++ } ++ _ => {} ++ } ++ } ++ } + unsafe fn setup_error_int(&mut self) { + unsafe { + let vector = 49u32; +diff --git a/src/arch/x86_shared/device/mod.rs b/src/arch/x86_shared/device/mod.rs +index 6f417706..acb14d72 100644 +--- a/src/arch/x86_shared/device/mod.rs ++++ b/src/arch/x86_shared/device/mod.rs +@@ -23,8 +23,7 @@ pub unsafe fn init() { + } + } + pub unsafe fn init_after_acpi() { +- // this will disable the IOAPIC if needed. +- //ioapic::init(mapper); ++ unsafe { ioapic::init() }; + } + + unsafe fn init_hpet() -> bool { +diff --git a/src/arch/x86_shared/interrupt/exception.rs b/src/arch/x86_shared/interrupt/exception.rs +index 7725a45d..fbba75c7 100644 +--- a/src/arch/x86_shared/interrupt/exception.rs ++++ b/src/arch/x86_shared/interrupt/exception.rs +@@ -1,3 +1,5 @@ ++use core::sync::atomic::{AtomicBool, Ordering}; ++ + use syscall::Exception; + use x86::irq::PageFaultError; + +@@ -10,6 +12,20 @@ use crate::{ + syscall::flag::*, + }; + ++static NMI_IN_PROGRESS: AtomicBool = AtomicBool::new(false); ++ ++unsafe fn nmi_raw_serial_write(s: &[u8]) { ++ use crate::syscall::io::{Io, Pio}; ++ let mut com1 = Pio::::new(0x3F8); ++ let lsr = Pio::::new(0x3F8 + 5); ++ for &b in s { ++ while lsr.read() & (1 << 5) == 0 { ++ core::hint::spin_loop(); ++ } ++ com1.write(b); ++ } ++} ++ + interrupt_stack!(divide_by_zero, |stack| { + println!("Divide by zero"); + stack.trace(); +@@ -55,9 +71,24 @@ interrupt_stack!(non_maskable, @paranoid, |stack| { + + #[cfg(not(all(target_arch = "x86_64", feature = "profiling")))] + { +- // TODO: This will likely deadlock +- println!("Non-maskable interrupt"); +- stack.dump(); ++ if NMI_IN_PROGRESS.swap(true, Ordering::SeqCst) { ++ return; ++ } ++ unsafe { ++ nmi_raw_serial_write(b"Non-maskable interrupt\n"); ++ nmi_raw_serial_write(b" RIP: "); ++ // Print RIP as hex manually to avoid formatting locks ++ let rip = stack.iret.rip; ++ let mut buf = [0u8; 19]; ++ buf[0] = b'0'; buf[1] = b'x'; ++ for i in 0..16 { ++ let nibble = ((rip >> (60 - i * 4)) & 0xf) as u8; ++ buf[2 + i] = if nibble < 10 { b'0' + nibble } else { b'a' + nibble - 10 }; ++ } ++ buf[18] = b'\n'; ++ nmi_raw_serial_write(&buf); ++ } ++ NMI_IN_PROGRESS.store(false, Ordering::SeqCst); + } + }); + +diff --git a/src/arch/x86_shared/start.rs b/src/arch/x86_shared/start.rs +index 7a7c0ae8..62f9523c 100644 +--- a/src/arch/x86_shared/start.rs ++++ b/src/arch/x86_shared/start.rs +@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { + // Set up graphical debug + graphical_debug::init(args.env()); + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + // Set up GDT +@@ -127,16 +127,21 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { + + // Initialize devices + device::init(); ++ info!("kernel: device init complete (PIC + LAPIC)"); + + // Read ACPI tables, starts APs + if cfg!(feature = "acpi") { + crate::acpi::init(args.acpi_rsdp()); ++ info!("kernel: ACPI tables parsed"); ++ + device::init_after_acpi(); ++ info!("kernel: IOAPIC init complete"); + } + crate::profiling::init(); + + // Initialize all of the non-core devices not otherwise needed to complete initialization + device::init_noncore(); ++ info!("kernel: timer init complete, entering userspace"); + + args.bootstrap() + }; +diff --git a/src/context/memory.rs b/src/context/memory.rs +index 94519448..0db1de53 100644 +--- a/src/context/memory.rs ++++ b/src/context/memory.rs +@@ -927,8 +927,8 @@ impl UserGrants { + .take_while(move |(base, info)| PageSpan::new(**base, info.page_count).intersects(span)) + .map(|(base, info)| (*base, info)) + } +- /// Return a free region with the specified size +- // TODO: Alignment (x86_64: 4 KiB, 2 MiB, or 1 GiB). ++ /// Return a free region with the specified size, optionally aligned to a power-of-two ++ /// boundary (x86_64 supports 4 KiB, 2 MiB, or 1 GiB pages). + // TODO: Support finding grant close to a requested address? + pub fn find_free_near( + &self, +@@ -936,29 +936,42 @@ impl UserGrants { + page_count: usize, + _near: Option, + ) -> Option { +- // Get first available hole, but do reserve the page starting from zero as most compiled +- // languages cannot handle null pointers safely even if they point to valid memory. If an +- // application absolutely needs to map the 0th page, they will have to do so explicitly via +- // MAP_FIXED/MAP_FIXED_NOREPLACE. +- // TODO: Allow explicitly allocating guard pages? Perhaps using mprotect or mmap with +- // PROT_NONE? ++ self.find_free_near_aligned(min, page_count, _near, 0) ++ } ++ pub fn find_free_near_aligned( ++ &self, ++ min: usize, ++ page_count: usize, ++ _near: Option, ++ page_alignment: usize, ++ ) -> Option { ++ let alignment = if page_alignment == 0 { ++ PAGE_SIZE ++ } else { ++ assert!(page_alignment.is_power_of_two(), "page_alignment must be a power of two"); ++ page_alignment * PAGE_SIZE ++ }; + + let (hole_start, _hole_size) = self + .holes + .iter() + .skip_while(|(hole_offset, hole_size)| hole_offset.data() + **hole_size <= min) + .find(|(hole_offset, hole_size)| { +- let avail_size = +- if hole_offset.data() <= min && min <= hole_offset.data() + **hole_size { +- **hole_size - (min - hole_offset.data()) +- } else { +- **hole_size +- }; ++ let base = cmp::max(hole_offset.data(), min); ++ let aligned_base = (base + alignment - 1) & !(alignment - 1); ++ let avail_size = if aligned_base <= hole_offset.data() + **hole_size { ++ hole_offset.data() + **hole_size - aligned_base ++ } else { ++ 0 ++ }; + page_count * PAGE_SIZE <= avail_size + })?; +- // Create new region ++ ++ let base = cmp::max(hole_start.data(), min); ++ let aligned_base = (base + alignment - 1) & !(alignment - 1); ++ + Some(PageSpan::new( +- Page::containing_address(VirtualAddress::new(cmp::max(hole_start.data(), min))), ++ Page::containing_address(VirtualAddress::new(aligned_base)), + page_count, + )) + } +diff --git a/src/event.rs b/src/event.rs +index 7398145a..92e5793c 100644 +--- a/src/event.rs ++++ b/src/event.rs +@@ -8,13 +8,14 @@ use crate::{ + context, + scheme::{self, SchemeExt, SchemeId}, + sync::{ +- CleanLockToken, LockToken, RwLock, RwLockReadGuard, RwLockWriteGuard, WaitQueue, L0, L1, L2, ++ CleanLockToken, LockToken, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, ++ WaitCondition, WaitQueue, L0, L1, L2, + }, + syscall::{ + data::Event, +- error::{Error, Result, EBADF}, +- flag::EventFlags, +- usercopy::UserSliceWo, ++ error::{Error, Result, EAGAIN, EBADF, EINVAL, EINTR}, ++ flag::{EVENT_READ, EVENT_WRITE, EventFlags}, ++ usercopy::{UserSliceRo, UserSliceWo}, + }, + }; + +@@ -25,6 +26,17 @@ pub struct EventQueue { + queue: WaitQueue, + } + ++const EVENTFD_COUNTER_MAX: u64 = u64::MAX - 1; ++const EVENTFD_TAG_BIT: usize = 1usize << (usize::BITS - 1); ++ ++pub struct EventCounter { ++ id: usize, ++ counter: Mutex, ++ read_condition: WaitCondition, ++ write_condition: WaitCondition, ++ semaphore: bool, ++} ++ + impl EventQueue { + pub fn new(id: EventQueueId) -> EventQueue { + EventQueue { +@@ -91,19 +103,146 @@ impl EventQueue { + } + } + ++impl EventCounter { ++ pub fn new(id: usize, init: u64, semaphore: bool) -> EventCounter { ++ EventCounter { ++ id, ++ counter: Mutex::new(init), ++ read_condition: WaitCondition::new(), ++ write_condition: WaitCondition::new(), ++ semaphore, ++ } ++ } ++ ++ pub fn is_readable(&self, token: &mut CleanLockToken) -> bool { ++ *self.counter.lock(token.token()) > 0 ++ } ++ ++ pub fn is_writable(&self, token: &mut CleanLockToken) -> bool { ++ *self.counter.lock(token.token()) < EVENTFD_COUNTER_MAX ++ } ++ ++ pub fn read(&self, buf: UserSliceWo, block: bool, token: &mut CleanLockToken) -> Result { ++ if buf.len() < core::mem::size_of::() { ++ return Err(Error::new(EINVAL)); ++ } ++ ++ loop { ++ let counter = self.counter.lock(token.token()); ++ let (mut counter, mut token) = counter.into_split(); ++ ++ if *counter > 0 { ++ let value = if self.semaphore { ++ *counter -= 1; ++ 1 ++ } else { ++ let value = *counter; ++ *counter = 0; ++ value ++ }; ++ ++ buf.limit(core::mem::size_of::()) ++ .ok_or(Error::new(EINVAL))? ++ .copy_from_slice(&value.to_ne_bytes())?; ++ ++ trigger_locked( ++ GlobalSchemes::Event.scheme_id(), ++ self.id, ++ EVENT_WRITE, ++ token.token(), ++ ); ++ self.write_condition.notify_locked(token.token()); ++ ++ return Ok(core::mem::size_of::()); ++ } ++ ++ if !block { ++ return Err(Error::new(EAGAIN)); ++ } ++ ++ if !self ++ .read_condition ++ .wait(counter, "EventCounter::read", &mut token) ++ { ++ return Err(Error::new(EINTR)); ++ } ++ } ++ } ++ ++ pub fn write(&self, buf: UserSliceRo, block: bool, token: &mut CleanLockToken) -> Result { ++ if buf.len() != core::mem::size_of::() { ++ return Err(Error::new(EINVAL)); ++ } ++ ++ let value = unsafe { buf.read_exact::()? }; ++ if value == u64::MAX { ++ return Err(Error::new(EINVAL)); ++ } ++ ++ loop { ++ let counter = self.counter.lock(token.token()); ++ let (mut counter, mut token) = counter.into_split(); ++ ++ if EVENTFD_COUNTER_MAX - *counter >= value { ++ let was_zero = *counter == 0; ++ *counter += value; ++ ++ if was_zero && value != 0 { ++ trigger_locked( ++ GlobalSchemes::Event.scheme_id(), ++ self.id, ++ EVENT_READ, ++ token.token(), ++ ); ++ self.read_condition.notify_locked(token.token()); ++ } ++ ++ return Ok(core::mem::size_of::()); ++ } ++ ++ if !block { ++ return Err(Error::new(EAGAIN)); ++ } ++ ++ if !self ++ .write_condition ++ .wait(counter, "EventCounter::write", &mut token) ++ { ++ return Err(Error::new(EINTR)); ++ } ++ } ++ } ++ ++ pub fn into_drop(self, _token: LockToken<'_, L1>) { ++ drop(self); ++ } ++} ++ + pub type EventQueueList = HashMap>; ++pub type EventCounterList = HashMap>; + + // Next queue id + static NEXT_QUEUE_ID: AtomicUsize = AtomicUsize::new(0); ++static NEXT_COUNTER_ID: AtomicUsize = AtomicUsize::new(0); + + /// Get next queue id + pub fn next_queue_id() -> EventQueueId { + EventQueueId::from(NEXT_QUEUE_ID.fetch_add(1, Ordering::SeqCst)) + } + ++pub fn next_counter_id() -> usize { ++ EVENTFD_TAG_BIT | NEXT_COUNTER_ID.fetch_add(1, Ordering::SeqCst) ++} ++ ++pub fn is_counter_id(id: usize) -> bool { ++ id & EVENTFD_TAG_BIT != 0 ++} ++ + // Current event queues + static QUEUES: RwLock = + RwLock::new(EventQueueList::with_hasher(DefaultHashBuilder::new())); ++static COUNTERS: RwLock = ++ RwLock::new(EventCounterList::with_hasher(DefaultHashBuilder::new())); + + /// Get the event queues list, const + pub fn queues(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L2, EventQueueList> { +@@ -115,6 +254,14 @@ pub fn queues_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L2, EventQue + QUEUES.write(token) + } + ++pub fn counters(token: LockToken<'_, L0>) -> RwLockReadGuard<'_, L2, EventCounterList> { ++ COUNTERS.read(token) ++} ++ ++pub fn counters_mut(token: LockToken<'_, L0>) -> RwLockWriteGuard<'_, L2, EventCounterList> { ++ COUNTERS.write(token) ++} ++ + #[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + pub struct RegKey { + pub scheme: SchemeId, +diff --git a/src/scheme/event.rs b/src/scheme/event.rs +index 36efe5b2..c64b6bd0 100644 +--- a/src/scheme/event.rs ++++ b/src/scheme/event.rs +@@ -1,9 +1,12 @@ +-use alloc::sync::Arc; ++use alloc::{sync::Arc, vec::Vec}; + use syscall::{EventFlags, O_NONBLOCK}; + + use crate::{ + context::file::InternalFlags, +- event::{next_queue_id, queues, queues_mut, EventQueue, EventQueueId}, ++ event::{ ++ EventCounter, EventQueue, EventQueueId, counters, counters_mut, is_counter_id, ++ next_counter_id, next_queue_id, queues, queues_mut, ++ }, + sync::CleanLockToken, + syscall::{ + data::Event, +@@ -25,7 +28,7 @@ impl KernelScheme for EventScheme { + fn kopenat( + &self, + id: usize, +- _user_buf: StrOrBytes, ++ user_buf: StrOrBytes, + _flags: usize, + _fcntl_flags: u32, + _ctx: CallerCtx, +@@ -34,13 +37,53 @@ impl KernelScheme for EventScheme { + if id != SCHEME_ROOT_ID { + return Err(Error::new(EACCES)); + } +- let id = next_queue_id(); +- queues_mut(token.token()).insert(id, Arc::new(EventQueue::new(id))); + +- Ok(OpenResult::SchemeLocal(id.get(), InternalFlags::empty())) ++ let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?; ++ let path = path.trim_matches('/'); ++ ++ if path.is_empty() { ++ let id = next_queue_id(); ++ queues_mut(token.token()).insert(id, Arc::new(EventQueue::new(id))); ++ return Ok(OpenResult::SchemeLocal(id.get(), InternalFlags::empty())); ++ } ++ ++ let parts: Vec<&str> = path.split('/').collect(); ++ if matches!(parts.first(), Some(&"eventfd")) { ++ let init = match parts.get(1) { ++ Some(value) => value.parse::().map_err(|_| Error::new(EINVAL))?, ++ None => 0_u64, ++ }; ++ if init > u32::MAX as u64 { ++ return Err(Error::new(EINVAL)); ++ } ++ let semaphore = match parts.get(2) { ++ Some(value) => match *value { ++ "0" => Ok(false), ++ "1" => Ok(true), ++ _ => Err(Error::new(EINVAL)), ++ }?, ++ None => false, ++ }; ++ ++ let id = next_counter_id(); ++ counters_mut(token.token()).insert(id, Arc::new(EventCounter::new(id, init, semaphore))); ++ return Ok(OpenResult::SchemeLocal(id, InternalFlags::empty())); ++ } ++ ++ Err(Error::new(ENOENT)) + } + + fn close(&self, id: usize, token: &mut CleanLockToken) -> Result<()> { ++ if is_counter_id(id) { ++ let counter = counters_mut(token.token()) ++ .remove(&id) ++ .ok_or(Error::new(EBADF))?; ++ if let Some(counter) = Arc::into_inner(counter) { ++ counter.into_drop(token.downgrade()); ++ } ++ return Ok(()); ++ } ++ + let id = EventQueueId::from(id); + let queue = queues_mut(token.token()) + .remove(&id) +@@ -59,6 +102,15 @@ impl KernelScheme for EventScheme { + _stored_flags: u32, + token: &mut CleanLockToken, + ) -> Result { ++ if is_counter_id(id) { ++ let counter = { ++ let handles = counters(token.token()); ++ let handle = handles.get(&id).ok_or(Error::new(EBADF))?; ++ handle.clone() ++ }; ++ return counter.read(buf, flags & O_NONBLOCK as u32 == 0, token); ++ } ++ + let id = EventQueueId::from(id); + + let queue = { +@@ -74,10 +126,19 @@ impl KernelScheme for EventScheme { + &self, + id: usize, + buf: UserSliceRo, +- _flags: u32, ++ flags: u32, + _stored_flags: u32, + token: &mut CleanLockToken, + ) -> Result { ++ if is_counter_id(id) { ++ let counter = { ++ let handles = counters(token.token()); ++ let handle = handles.get(&id).ok_or(Error::new(EBADF))?; ++ handle.clone() ++ }; ++ return counter.write(buf, flags & O_NONBLOCK as u32 == 0, token); ++ } ++ + let id = EventQueueId::from(id); + + let queue = { +@@ -98,8 +159,12 @@ impl KernelScheme for EventScheme { + Ok(events_written * size_of::()) + } + +- fn kfpath(&self, _id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result { +- buf.copy_common_bytes_from_slice(b"/scheme/event/") ++ fn kfpath(&self, id: usize, buf: UserSliceWo, _token: &mut CleanLockToken) -> Result { ++ if is_counter_id(id) { ++ buf.copy_common_bytes_from_slice(b"/scheme/event/eventfd") ++ } else { ++ buf.copy_common_bytes_from_slice(b"/scheme/event/") ++ } + } + + fn fevent( +@@ -108,6 +173,23 @@ impl KernelScheme for EventScheme { + flags: EventFlags, + token: &mut CleanLockToken, + ) -> Result { ++ if is_counter_id(id) { ++ let counter = { ++ let handles = counters(token.token()); ++ let handle = handles.get(&id).ok_or(Error::new(EBADF))?; ++ handle.clone() ++ }; ++ ++ let mut ready = EventFlags::empty(); ++ if flags.contains(EventFlags::EVENT_READ) && counter.is_readable(token) { ++ ready |= EventFlags::EVENT_READ; ++ } ++ if flags.contains(EventFlags::EVENT_WRITE) && counter.is_writable(token) { ++ ready |= EventFlags::EVENT_WRITE; ++ } ++ return Ok(ready); ++ } ++ + let id = EventQueueId::from(id); + + let queue = { diff --git a/local/patches/kernel/redox.patch b/local/patches/kernel/redox.patch index a92b883f..fa226ebc 100644 --- a/local/patches/kernel/redox.patch +++ b/local/patches/kernel/redox.patch @@ -1,8 +1,40 @@ diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs -index 4dc23883..a8e6fe8f 100644 --- a/src/acpi/madt/arch/x86.rs +++ b/src/acpi/madt/arch/x86.rs -@@ -35,18 +35,19 @@ pub(super) fn init(madt: Madt) { +@@ -1,154 +1,247 @@ + use core::{ + hint, + sync::atomic::{AtomicU8, Ordering}, + }; + + use crate::{ + arch::start::KernelArgsAp, + cpu_set::LogicalCpuId, + device::local_apic::the_local_apic, + memory::{ + allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch, + VirtualAddress, PAGE_SIZE, + }, + start::kstart_ap, + AP_READY, + }; + + use super::{Madt, MadtEntry}; + + const TRAMPOLINE: usize = 0x8000; + static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline")); + + pub(super) fn init(madt: Madt) { + let local_apic = unsafe { the_local_apic() }; + let me = local_apic.id(); + + if local_apic.x2 { + debug!(" X2APIC {}", me.get()); + } else { + debug!(" XAPIC {}: {:>08X}", me.get(), local_apic.address); + } + + if cfg!(not(feature = "multi_core")) { return; } @@ -25,7 +57,95 @@ index 4dc23883..a8e6fe8f 100644 ) .expect("failed to map trampoline"); -@@ -147,6 +148,160 @@ pub(super) fn init(madt: Madt) { + (result, mapper.table().phys().data()) + }; + result.flush(); + + // Write trampoline, make sure TRAMPOLINE page is free for use + for (i, val) in TRAMPOLINE_DATA.iter().enumerate() { + unsafe { + (*((TRAMPOLINE as *mut u8).add(i) as *const AtomicU8)).store(*val, Ordering::SeqCst); + } + } + + for madt_entry in madt.iter() { + debug!(" {:x?}", madt_entry); + if let MadtEntry::LocalApic(ap_local_apic) = madt_entry { + if u32::from(ap_local_apic.id) == me.get() { + debug!(" This is my local APIC"); + } else if ap_local_apic.flags & 1 == 1 { + let cpu_id = LogicalCpuId::next(); + + // Allocate a stack + let stack_start = RmmA::phys_to_virt( + allocate_p2frame(4) + .expect("no more frames in acpi stack_start") + .base(), + ) + .data(); + let stack_end = stack_start + (PAGE_SIZE << 4); + + let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end); + + let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id); + + let args = KernelArgsAp { + stack_end: stack_end as *mut u8, + cpu_id, + pcr_ptr, + idt_ptr, + }; + + let ap_ready = (TRAMPOLINE + 8) as *mut u64; + let ap_args_ptr = unsafe { ap_ready.add(1) }; + let ap_page_table = unsafe { ap_ready.add(2) }; + let ap_code = unsafe { ap_ready.add(3) }; + + // Set the ap_ready to 0, volatile + unsafe { + ap_ready.write(0); + ap_args_ptr.write(&args as *const _ as u64); + ap_page_table.write(page_table_physaddr as u64); + #[expect(clippy::fn_to_numeric_cast)] + ap_code.write(kstart_ap as u64); + + // TODO: Is this necessary (this fence)? + core::arch::asm!(""); + }; + AP_READY.store(false, Ordering::SeqCst); + + // Send INIT IPI + { + let mut icr = 0x4500; + if local_apic.x2 { + icr |= u64::from(ap_local_apic.id) << 32; + } else { + icr |= u64::from(ap_local_apic.id) << 56; + } + local_apic.set_icr(icr); + } + + // Send START IPI + { + let ap_segment = (TRAMPOLINE >> 12) & 0xFF; + let mut icr = 0x4600 | ap_segment as u64; + + if local_apic.x2 { + icr |= u64::from(ap_local_apic.id) << 32; + } else { + icr |= u64::from(ap_local_apic.id) << 56; + } + + local_apic.set_icr(icr); + } + + // Wait for trampoline ready + while unsafe { (*ap_ready.cast::()).load(Ordering::SeqCst) } == 0 { + hint::spin_loop(); + } + while !AP_READY.load(Ordering::SeqCst) { + hint::spin_loop(); + } RmmA::invalidate_all(); } @@ -68,15 +188,9 @@ index 4dc23883..a8e6fe8f 100644 + }; + AP_READY.store(false, Ordering::SeqCst); + -+ // Same ICR delivery-mode bits are used by xAPIC and x2APIC; only the -+ // destination field encoding changes between the MMIO and MSR forms. -+ const ICR_INIT_ASSERT: u64 = 0x4500; -+ const ICR_STARTUP: u64 = 0x4600; -+ -+ // ICR bits 10:8 = 0b101 (INIT), bit 14 = level assert. + // Send INIT IPI (x2APIC always uses 32-bit APIC ID in bits 32-63) + { -+ let mut icr = ICR_INIT_ASSERT; ++ let mut icr = 0x4500u64; + icr |= u64::from(ap_x2apic.x2apic_id) << 32; + local_apic.set_icr(icr); + } @@ -86,11 +200,10 @@ index 4dc23883..a8e6fe8f 100644 + hint::spin_loop(); + } + -+ // ICR bits 10:8 = 0b110 (STARTUP), bit 14 = level assert. + // Send STARTUP IPI + { + let ap_segment = (TRAMPOLINE >> 12) & 0xFF; -+ let mut icr = ICR_STARTUP | ap_segment as u64; ++ let mut icr = 0x4600u64 | ap_segment as u64; + icr |= u64::from(ap_x2apic.x2apic_id) << 32; + local_apic.set_icr(icr); + } @@ -102,21 +215,17 @@ index 4dc23883..a8e6fe8f 100644 + } + { + let ap_segment = (TRAMPOLINE >> 12) & 0xFF; -+ let mut icr = ICR_STARTUP | ap_segment as u64; ++ let mut icr = 0x4600u64 | ap_segment as u64; + icr |= u64::from(ap_x2apic.x2apic_id) << 32; + local_apic.set_icr(icr); + } + -+ // Known limitation: cpu_id and per-CPU bootstrap state are allocated -+ // before the timeout checks, so a timed-out AP still consumes a -+ // logical CPU slot until startup rollback/teardown is implemented. + let mut timeout = 100_000_000u32; + while unsafe { (*ap_ready.cast::()).load(Ordering::SeqCst) } == 0 { + hint::spin_loop(); + timeout -= 1; + if timeout == 0 { -+ let x2apic_id = ap_x2apic.x2apic_id; -+ debug!("x2APIC AP {} trampoline startup timed out", x2apic_id); ++ debug!("x2APIC AP {} trampoline startup timed out", ap_x2apic.x2apic_id); + break; + } + } @@ -125,14 +234,796 @@ index 4dc23883..a8e6fe8f 100644 + hint::spin_loop(); + timeout -= 1; + if timeout == 0 { -+ let x2apic_id = ap_x2apic.x2apic_id; -+ debug!("x2APIC AP {} kernel startup timed out", x2apic_id); ++ debug!("x2APIC AP {} kernel startup timed out", ap_x2apic.x2apic_id); + break; + } + } + + RmmA::invalidate_all(); + } + } + } + + // Unmap trampoline + let (_frame, _, flush) = unsafe { + KernelMapper::lock_rw() + .unmap_phys(trampoline_page.start_address()) + .expect("failed to unmap trampoline page") + }; + flush.flush(); + } +diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs +--- a/src/acpi/madt/mod.rs ++++ b/src/acpi/madt/mod.rs +@@ -27,214 +27,240 @@ + pub fn madt() -> Option<&'static Madt> { + unsafe { &*MADT.get() }.as_ref() + } + pub const FLAG_PCAT: u32 = 1; + + impl Madt { + pub fn init() { + let madt = Madt::new(find_one_sdt!("APIC")); + + if let Some(madt) = madt { + // safe because no APs have been started yet. + unsafe { MADT.get().write(Some(madt)) }; + + debug!(" APIC: {:>08X}: {}", madt.local_address, madt.flags); + + arch::init(madt); + } + } + + pub fn new(sdt: &'static Sdt) -> Option { + if &sdt.signature == b"APIC" && sdt.data_len() >= 8 { + //Not valid if no local address and flags + let local_address = unsafe { (sdt.data_address() as *const u32).read_unaligned() }; + let flags = unsafe { + (sdt.data_address() as *const u32) + .offset(1) + .read_unaligned() + }; + + Some(Madt { + sdt, + local_address, + flags, + }) + } else { + None + } + } + + pub fn iter(&self) -> MadtIter { + MadtIter { + sdt: self.sdt, + i: 8, // Skip local controller address and flags + } + } + } + + /// MADT Local APIC + #[derive(Clone, Copy, Debug)] + #[repr(C, packed)] + pub struct MadtLocalApic { + /// Processor ID + pub processor: u8, + /// Local APIC ID + pub id: u8, + /// Flags. 1 means that the processor is enabled + pub flags: u32, + } + + /// MADT I/O APIC + #[derive(Clone, Copy, Debug)] + #[repr(C, packed)] + pub struct MadtIoApic { + /// I/O APIC ID + pub id: u8, + /// reserved + _reserved: u8, + /// I/O APIC address + pub address: u32, + /// Global system interrupt base + pub gsi_base: u32, + } + + /// MADT Interrupt Source Override + #[derive(Clone, Copy, Debug)] + #[repr(C, packed)] + pub struct MadtIntSrcOverride { + /// Bus Source + pub bus_source: u8, + /// IRQ Source + pub irq_source: u8, + /// Global system interrupt base + pub gsi_base: u32, + /// Flags + pub flags: u16, + } + + /// MADT GICC + #[derive(Clone, Copy, Debug)] + #[repr(C, packed)] + pub struct MadtGicc { + _reserved: u16, + pub cpu_interface_number: u32, + pub acpi_processor_uid: u32, + pub flags: u32, + pub parking_protocol_version: u32, + pub performance_interrupt_gsiv: u32, + pub parked_address: u64, + pub physical_base_address: u64, + pub gicv: u64, + pub gich: u64, + pub vgic_maintenance_interrupt: u32, + pub gicr_base_address: u64, + pub mpidr: u64, + pub processor_power_efficiency_class: u8, + _reserved2: u8, + pub spe_overflow_interrupt: u16, + //TODO: optional field introduced in ACPI 6.5: pub trbe_interrupt: u16, + } + + /// MADT GICD + #[derive(Clone, Copy, Debug)] + #[repr(C, packed)] + pub struct MadtGicd { + _reserved: u16, + pub gic_id: u32, + pub physical_base_address: u64, + pub system_vector_base: u32, + pub gic_version: u8, + _reserved2: [u8; 3], ++} ++ ++/// MADT Local x2APIC (entry type 0x9) ++/// Used by modern AMD and Intel platforms with APIC IDs >= 255. ++#[derive(Clone, Copy, Debug)] ++#[repr(C, packed)] ++pub struct MadtLocalX2Apic { ++ _reserved: u16, ++ pub x2apic_id: u32, ++ pub flags: u32, ++ pub processor_uid: u32, + } + + /// MADT Entries + #[derive(Debug)] + #[allow(dead_code)] + pub enum MadtEntry { + LocalApic(&'static MadtLocalApic), + InvalidLocalApic(usize), + IoApic(&'static MadtIoApic), + InvalidIoApic(usize), + IntSrcOverride(&'static MadtIntSrcOverride), + InvalidIntSrcOverride(usize), + Gicc(&'static MadtGicc), + InvalidGicc(usize), + Gicd(&'static MadtGicd), + InvalidGicd(usize), ++ LocalX2Apic(&'static MadtLocalX2Apic), ++ InvalidLocalX2Apic(usize), + Unknown(u8), + } + + pub struct MadtIter { + sdt: &'static Sdt, + i: usize, + } + + impl Iterator for MadtIter { + type Item = MadtEntry; + fn next(&mut self) -> Option { + if self.i + 1 < self.sdt.data_len() { + let entry_type = unsafe { *(self.sdt.data_address() as *const u8).add(self.i) }; + let entry_len = + unsafe { *(self.sdt.data_address() as *const u8).add(self.i + 1) } as usize; + ++ if entry_len < 2 { ++ return None; ++ } ++ + if self.i + entry_len <= self.sdt.data_len() { + let item = match entry_type { + 0x0 => { + if entry_len == size_of::() + 2 { + MadtEntry::LocalApic(unsafe { + &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApic) + }) + } else { + MadtEntry::InvalidLocalApic(entry_len) + } + } + 0x1 => { + if entry_len == size_of::() + 2 { + MadtEntry::IoApic(unsafe { + &*((self.sdt.data_address() + self.i + 2) as *const MadtIoApic) + }) + } else { + MadtEntry::InvalidIoApic(entry_len) + } + } + 0x2 => { + if entry_len == size_of::() + 2 { + MadtEntry::IntSrcOverride(unsafe { + &*((self.sdt.data_address() + self.i + 2) + as *const MadtIntSrcOverride) + }) + } else { + MadtEntry::InvalidIntSrcOverride(entry_len) + } + } + 0xB => { + if entry_len >= size_of::() + 2 { + MadtEntry::Gicc(unsafe { + &*((self.sdt.data_address() + self.i + 2) as *const MadtGicc) + }) + } else { + MadtEntry::InvalidGicc(entry_len) + } + } + 0xC => { + if entry_len >= size_of::() + 2 { + MadtEntry::Gicd(unsafe { + &*((self.sdt.data_address() + self.i + 2) as *const MadtGicd) + }) + } else { + MadtEntry::InvalidGicd(entry_len) + } + } ++ 0x9 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalX2Apic(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalX2Apic) ++ }) ++ } else { ++ MadtEntry::InvalidLocalX2Apic(entry_len) ++ } ++ } + _ => MadtEntry::Unknown(entry_type), + }; + + self.i += entry_len; + + Some(item) + } else { + None + } + } else { + None + } + } + } +diff --git a/src/arch/x86_shared/cpuid.rs b/src/arch/x86_shared/cpuid.rs +--- a/src/arch/x86_shared/cpuid.rs ++++ b/src/arch/x86_shared/cpuid.rs +@@ -1,29 +1,39 @@ + use raw_cpuid::{CpuId, CpuIdResult, ExtendedFeatures, FeatureInfo}; + ++#[cfg(target_arch = "x86_64")] + pub fn cpuid() -> CpuId { +- // FIXME check for cpuid availability during early boot and error out if it doesn't exist. + CpuId::with_cpuid_fn(|a, c| { +- #[cfg(target_arch = "x86")] ++ let result = unsafe { core::arch::x86_64::__cpuid_count(a, c) }; ++ CpuIdResult { ++ eax: result.eax, ++ ebx: result.ebx, ++ ecx: result.ecx, ++ edx: result.edx, ++ } ++ }) ++} ++ ++#[cfg(target_arch = "x86")] ++pub fn cpuid() -> CpuId { ++ CpuId::with_cpuid_fn(|a, c| { + let result = unsafe { core::arch::x86::__cpuid_count(a, c) }; +- #[cfg(target_arch = "x86_64")] +- let result = unsafe { core::arch::x86_64::__cpuid_count(a, c) }; + CpuIdResult { + eax: result.eax, + ebx: result.ebx, + ecx: result.ecx, + edx: result.edx, + } + }) + } + + #[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))] + pub fn feature_info() -> FeatureInfo { + cpuid() + .get_feature_info() + .expect("x86_64 requires CPUID leaf=0x01 to be present") + } + + #[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))] + pub fn has_ext_feat(feat: impl FnOnce(ExtendedFeatures) -> bool) -> bool { + cpuid().get_extended_feature_info().is_some_and(feat) + } +diff --git a/src/context/memory.rs b/src/context/memory.rs +--- a/src/context/memory.rs ++++ b/src/context/memory.rs +@@ -890,112 +890,128 @@ + .range(..=page) + .next_back() + .filter(|(base, info)| (**base..base.next_by(info.page_count)).contains(&page)) + .map(|(base, info)| (*base, info)) + } + + /// Returns an iterator over all grants that occupy some part of the + /// requested region + pub fn conflicts(&self, span: PageSpan) -> impl Iterator + '_ { + let start = self.contains(span.base); + + // If there is a grant that contains the base page, start searching at the base of that + // grant, rather than the requested base here. + let start_span = start + .map(|(base, info)| PageSpan::new(base, info.page_count)) + .unwrap_or(span); + + self.inner + .range(start_span.base..) + .take_while(move |(base, info)| PageSpan::new(**base, info.page_count).intersects(span)) + .map(|(base, info)| (*base, info)) + } + // TODO: DEDUPLICATE CODE! + pub fn conflicts_mut( + &mut self, + span: PageSpan, + ) -> impl Iterator + '_ { + let start = self.contains(span.base); + + // If there is a grant that contains the base page, start searching at the base of that + // grant, rather than the requested base here. + let start_span = start + .map(|(base, info)| PageSpan::new(base, info.page_count)) + .unwrap_or(span); + + self.inner + .range_mut(start_span.base..) + .take_while(move |(base, info)| PageSpan::new(**base, info.page_count).intersects(span)) + .map(|(base, info)| (*base, info)) + } +- /// Return a free region with the specified size +- // TODO: Alignment (x86_64: 4 KiB, 2 MiB, or 1 GiB). ++ /// Return a free region with the specified size, optionally aligned to a power-of-two ++ /// boundary (x86_64 supports 4 KiB, 2 MiB, or 1 GiB pages). + // TODO: Support finding grant close to a requested address? + pub fn find_free_near( + &self, + min: usize, + page_count: usize, + _near: Option, + ) -> Option { +- // Get first available hole, but do reserve the page starting from zero as most compiled +- // languages cannot handle null pointers safely even if they point to valid memory. If an +- // application absolutely needs to map the 0th page, they will have to do so explicitly via +- // MAP_FIXED/MAP_FIXED_NOREPLACE. +- // TODO: Allow explicitly allocating guard pages? Perhaps using mprotect or mmap with +- // PROT_NONE? ++ self.find_free_near_aligned(min, page_count, _near, 0) ++ } ++ pub fn find_free_near_aligned( ++ &self, ++ min: usize, ++ page_count: usize, ++ _near: Option, ++ page_alignment: usize, ++ ) -> Option { ++ let alignment = if page_alignment == 0 { ++ PAGE_SIZE ++ } else { ++ assert!( ++ page_alignment.is_power_of_two(), ++ "page_alignment must be a power of two" ++ ); ++ page_alignment * PAGE_SIZE ++ }; + + let (hole_start, _hole_size) = self + .holes + .iter() + .skip_while(|(hole_offset, hole_size)| hole_offset.data() + **hole_size <= min) + .find(|(hole_offset, hole_size)| { +- let avail_size = +- if hole_offset.data() <= min && min <= hole_offset.data() + **hole_size { +- **hole_size - (min - hole_offset.data()) +- } else { +- **hole_size +- }; ++ let base = cmp::max(hole_offset.data(), min); ++ let aligned_base = (base + alignment - 1) & !(alignment - 1); ++ let avail_size = if aligned_base <= hole_offset.data() + **hole_size { ++ hole_offset.data() + **hole_size - aligned_base ++ } else { ++ 0 ++ }; + page_count * PAGE_SIZE <= avail_size + })?; +- // Create new region ++ ++ let base = cmp::max(hole_start.data(), min); ++ let aligned_base = (base + alignment - 1) & !(alignment - 1); ++ + Some(PageSpan::new( +- Page::containing_address(VirtualAddress::new(cmp::max(hole_start.data(), min))), ++ Page::containing_address(VirtualAddress::new(aligned_base)), + page_count, + )) + } + pub fn find_free(&self, min: usize, page_count: usize) -> Option { + self.find_free_near(min, page_count, None) + } + fn reserve(&mut self, base: Page, page_count: usize) { + let start_address = base.start_address(); + let size = page_count * PAGE_SIZE; + let end_address = base.start_address().add(size); + + let previous_hole = self.holes.range_mut(..start_address).next_back(); + + if let Some((hole_offset, hole_size)) = previous_hole { + let prev_hole_end = hole_offset.data() + *hole_size; + + // Note that prev_hole_end cannot exactly equal start_address, since that would imply + // there is another grant at that position already, as it would otherwise have been + // larger. + + if prev_hole_end > start_address.data() { + // hole_offset must be below (but never equal to) the start address due to the + // `..start_address()` limit; hence, all we have to do is to shrink the + // previous offset. + *hole_size = start_address.data() - hole_offset.data(); + } + if prev_hole_end > end_address.data() { + // The grant is splitting this hole in two, so insert the new one at the end. + self.holes + .insert(end_address, prev_hole_end - end_address.data()); + } + } + + // Next hole + if let Some(hole_size) = self.holes.remove(&start_address) { + let remainder = hole_size - size; + if remainder > 0 { + self.holes.insert(end_address, remainder); + } + } +diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs +--- a/src/arch/x86_shared/device/local_apic.rs ++++ b/src/arch/x86_shared/device/local_apic.rs +@@ -100,61 +100,68 @@ + } + } + + pub fn id(&self) -> ApicId { + ApicId::new(if self.x2 { + unsafe { rdmsr(IA32_X2APIC_APICID) as u32 } + } else { + unsafe { self.read(0x20) } + }) + } + + pub fn version(&self) -> u32 { + if self.x2 { + unsafe { rdmsr(IA32_X2APIC_VERSION) as u32 } + } else { + unsafe { self.read(0x30) } + } + } + + pub fn icr(&self) -> u64 { + if self.x2 { + unsafe { rdmsr(IA32_X2APIC_ICR) } + } else { + unsafe { ((self.read(0x310) as u64) << 32) | self.read(0x300) as u64 } + } + } + + pub fn set_icr(&mut self, value: u64) { + if self.x2 { + unsafe { ++ const PENDING: u32 = 1 << 12; ++ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING { ++ core::hint::spin_loop(); ++ } + wrmsr(IA32_X2APIC_ICR, value); ++ while (rdmsr(IA32_X2APIC_ICR) as u32) & PENDING == PENDING { ++ core::hint::spin_loop(); ++ } + } + } else { + unsafe { + const PENDING: u32 = 1 << 12; + while self.read(0x300) & PENDING == PENDING { + core::hint::spin_loop(); + } + self.write(0x310, (value >> 32) as u32); + self.write(0x300, value as u32); + while self.read(0x300) & PENDING == PENDING { + core::hint::spin_loop(); + } + } + } + } + + pub fn ipi(&mut self, apic_id: ApicId, kind: IpiKind) { + let shift = if self.x2 { 32 } else { 56 }; + self.set_icr((u64::from(apic_id.get()) << shift) | 0x40 | kind as u64); + } + pub fn ipi_nmi(&mut self, apic_id: ApicId) { + let shift = if self.x2 { 32 } else { 56 }; + self.set_icr((u64::from(apic_id.get()) << shift) | (1 << 14) | (0b100 << 8)); + } + + pub unsafe fn eoi(&mut self) { + unsafe { + if self.x2 { + wrmsr(IA32_X2APIC_EOI, 0); + } else { +diff --git a/src/acpi/rsdp.rs b/src/acpi/rsdp.rs +index f10c5ac9..f3cf3175 100644 +--- a/src/acpi/rsdp.rs ++++ b/src/acpi/rsdp.rs +@@ -17,9 +17,33 @@ pub struct Rsdp { + + impl Rsdp { + pub unsafe fn get_rsdp(already_supplied_rsdp: Option<*const u8>) -> Option { +- already_supplied_rsdp.map(|rsdp_ptr| { +- // TODO: Validate +- unsafe { *(rsdp_ptr as *const Rsdp) } ++ already_supplied_rsdp.and_then(|rsdp_ptr| { ++ let rsdp = unsafe { *(rsdp_ptr as *const Rsdp) }; ++ ++ // Validate signature "RSD PTR " ++ if &rsdp.signature != b"RSD PTR " { ++ return None; ++ } ++ ++ // ACPI 1.0 checksum: sum of first 20 bytes must be zero ++ let bytes_v1 = unsafe { core::slice::from_raw_parts(rsdp_ptr, 20) }; ++ if bytes_v1.iter().fold(0u8, |sum, &b| sum.wrapping_add(b)) != 0 { ++ return None; ++ } ++ ++ // ACPI 2.0+ extended checksum: sum of entire table (length bytes) must be zero ++ if rsdp.revision >= 2 { ++ let full_len = rsdp._length as usize; ++ if full_len < 36 || full_len > 256 { ++ return None; ++ } ++ let bytes_full = unsafe { core::slice::from_raw_parts(rsdp_ptr, full_len) }; ++ if bytes_full.iter().fold(0u8, |sum, &b| sum.wrapping_add(b)) != 0 { ++ return None; ++ } ++ } ++ ++ Some(rsdp) + }) + } + +diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs +index 4dc23883..c52e0ab4 100644 +--- a/src/acpi/madt/arch/x86.rs ++++ b/src/acpi/madt/arch/x86.rs +@@ -10,7 +10,8 @@ use crate::{ + }, + cpu_set::LogicalCpuId, + memory::{ +- allocate_p2frame, Frame, KernelMapper, Page, PageFlags, PhysicalAddress, RmmA, RmmArch, ++ allocate_p2frame, map_device_memory, Frame, KernelMapper, Page, PageFlags, ++ PhysicalAddress, RmmA, RmmArch, + VirtualAddress, PAGE_SIZE, + }, + startup::AP_READY, +@@ -20,6 +21,55 @@ use super::{Madt, MadtEntry}; + + const TRAMPOLINE: usize = 0x8000; + static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline")); ++const AP_STARTUP_TIMEOUT: u32 = 100_000_000; ++ ++fn wait_for_ap_flag(flag: *mut u64, description: &str, apic_id: u32) -> bool { ++ let mut timeout = AP_STARTUP_TIMEOUT; ++ while unsafe { (*flag.cast::()).load(Ordering::SeqCst) } == 0 { ++ hint::spin_loop(); ++ timeout -= 1; ++ if timeout == 0 { ++ debug!("AP {} {} timed out", apic_id, description); ++ return false; ++ } ++ } ++ true ++} ++ ++fn wait_for_kernel_ap_ready(apic_id: u32) -> bool { ++ let mut timeout = AP_STARTUP_TIMEOUT; ++ while !AP_READY.load(Ordering::SeqCst) { ++ hint::spin_loop(); ++ timeout -= 1; ++ if timeout == 0 { ++ debug!("AP {} kernel startup timed out", apic_id); ++ return false; ++ } ++ } ++ true ++} ++ ++fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option { ++ madt.iter().find_map(|entry| match entry { ++ MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid), ++ _ => None, ++ }) ++} ++ ++fn apply_lapic_address_override(local_apic: &mut crate::arch::device::local_apic::LocalApic, addr: u64) { ++ if local_apic.x2 || addr == 0 { ++ return; ++ } ++ ++ let Ok(physaddr) = usize::try_from(addr) else { ++ warn!("Ignoring LAPIC address override {:#x}: does not fit host usize", addr); ++ return; ++ }; ++ ++ let mapped = unsafe { map_device_memory(PhysicalAddress::new(physaddr), 4096) }.data(); ++ local_apic.address = mapped; ++ debug!("Applied LAPIC address override: {:#x}", addr); ++} + + pub(super) fn init(madt: Madt) { + let local_apic = unsafe { the_local_apic() }; +@@ -35,18 +85,19 @@ pub(super) fn init(madt: Madt) { + return; + } + +- // Map trampoline ++ // Map trampoline writable and executable (trampoline page holds both code ++ // and AP argument data — AP writes ap_ready on the same page, so W^X is ++ // not possible without splitting code/data across pages). + let trampoline_frame = Frame::containing(PhysicalAddress::new(TRAMPOLINE)); + let trampoline_page = Page::containing_address(VirtualAddress::new(TRAMPOLINE)); + let (result, page_table_physaddr) = unsafe { +- //TODO: do not have writable and executable! + let mut mapper = KernelMapper::lock_rw(); + + let result = mapper + .map_phys( + trampoline_page.start_address(), + trampoline_frame.base(), +- PageFlags::new().execute(true).write(true), ++ PageFlags::new().write(true).execute(true), + ) + .expect("failed to map trampoline"); + +@@ -75,12 +126,11 @@ pub(super) fn init(madt: Madt) { + let cpu_id = LogicalCpuId::next(); + + // Allocate a stack +- let stack_start = RmmA::phys_to_virt( +- allocate_p2frame(4) +- .expect("no more frames in acpi stack_start") +- .base(), +- ) +- .data(); ++ let Some(stack_frame) = allocate_p2frame(4) else { ++ warn!("Unable to allocate AP bootstrap stack for local APIC {}", ap_local_apic.id); ++ continue; ++ }; ++ let stack_start = RmmA::phys_to_virt(stack_frame.base()).data(); + let stack_end = stack_start + (PAGE_SIZE << 4); + + let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end); +@@ -138,15 +188,168 @@ pub(super) fn init(madt: Madt) { + } + + // Wait for trampoline ready +- while unsafe { (*ap_ready.cast::()).load(Ordering::SeqCst) } == 0 { ++ let ready = wait_for_ap_flag(ap_ready, "trampoline startup", u32::from(ap_local_apic.id)); ++ let kernel_ready = ready && wait_for_kernel_ap_ready(u32::from(ap_local_apic.id)); ++ ++ if !kernel_ready { ++ warn!("Skipping local APIC {} after startup timeout", ap_local_apic.id); ++ } ++ ++ RmmA::invalidate_all(); ++ } ++ } else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry { ++ let x2id = ap_x2apic.x2apic_id; ++ let x2flags = ap_x2apic.flags; ++ if x2id == me.get() { ++ debug!(" This is my local x2APIC"); ++ } else if x2flags & 1 == 1 { ++ let cpu_id = LogicalCpuId::next(); ++ ++ let Some(stack_frame) = allocate_p2frame(4) else { ++ warn!( ++ "Unable to allocate AP bootstrap stack for x2APIC {}", ++ x2id ++ ); ++ continue; ++ }; ++ let stack_start = RmmA::phys_to_virt(stack_frame.base()).data(); ++ let stack_end = stack_start + (PAGE_SIZE << 4); ++ ++ let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end); ++ let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id); ++ ++ let args = KernelArgsAp { ++ stack_end: stack_end as *mut u8, ++ cpu_id, ++ pcr_ptr, ++ idt_ptr, ++ }; ++ ++ let ap_ready = (TRAMPOLINE + 8) as *mut u64; ++ let ap_args_ptr = unsafe { ap_ready.add(1) }; ++ let ap_page_table = unsafe { ap_ready.add(2) }; ++ let ap_code = unsafe { ap_ready.add(3) }; ++ ++ unsafe { ++ ap_ready.write(0); ++ ap_args_ptr.write(&args as *const _ as u64); ++ ap_page_table.write(page_table_physaddr as u64); ++ #[expect(clippy::fn_to_numeric_cast)] ++ ap_code.write(kstart_ap as u64); ++ core::arch::asm!(""); ++ }; ++ AP_READY.store(false, Ordering::SeqCst); ++ ++ // Same ICR delivery-mode bits are used by xAPIC and x2APIC; only the ++ // destination field encoding changes between the MMIO and MSR forms. ++ const ICR_INIT_ASSERT: u64 = 0x4500; ++ const ICR_STARTUP: u64 = 0x4600; ++ ++ // ICR bits 10:8 = 0b101 (INIT), bit 14 = level assert. ++ // Send INIT IPI (x2APIC always uses 32-bit APIC ID in bits 32-63) ++ { ++ let mut icr = ICR_INIT_ASSERT; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Wait for INIT delivery (~10 μs de-assert window per Intel SDM) ++ for _ in 0..100_000 { + hint::spin_loop(); + } +- while !AP_READY.load(Ordering::SeqCst) { ++ ++ // ICR bits 10:8 = 0b110 (STARTUP), bit 14 = level assert. ++ // Send STARTUP IPI ++ { ++ let ap_segment = (TRAMPOLINE >> 12) & 0xFF; ++ let mut icr = ICR_STARTUP | ap_segment as u64; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Wait ~200 μs, then send second STARTUP IPI per the universal ++ // startup algorithm. ++ for _ in 0..2_000_000 { + hint::spin_loop(); + } ++ { ++ let ap_segment = (TRAMPOLINE >> 12) & 0xFF; ++ let mut icr = ICR_STARTUP | ap_segment as u64; ++ icr |= u64::from(x2id) << 32; ++ local_apic.set_icr(icr); ++ } ++ ++ // Known limitation: cpu_id and per-CPU bootstrap state are allocated ++ // before the timeout checks, so a timed-out AP still consumes a ++ // logical CPU slot until startup rollback/teardown is implemented. ++ let ready = wait_for_ap_flag(ap_ready, "trampoline startup", x2id); ++ let kernel_ready = ready && wait_for_kernel_ap_ready(x2id); ++ ++ if !kernel_ready { ++ warn!("Skipping x2APIC {} after startup timeout", x2id); ++ } + + RmmA::invalidate_all(); + } + } else if let MadtEntry::LocalApicNmi(nmi) = madt_entry { + let target_id = nmi.processor; + let nmi_pin = nmi.nmi_pin; @@ -170,24 +1061,33 @@ index 4dc23883..a8e6fe8f 100644 + local_apic.set_lvt_nmi(nmi_pin, nmi_flags); + } + } else { -+ debug!( -+ " x2APIC NMI: uid {}, pin={}, flags={:#x}", -+ target_uid, nmi_pin, nmi_flags -+ ); -+ unsafe { -+ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ let current_uid = current_x2apic_processor_uid(&madt, me.get()); ++ if current_uid == Some(target_uid) { ++ debug!( ++ " x2APIC NMI: uid {}, pin={}, flags={:#x}", ++ target_uid, nmi_pin, nmi_flags ++ ); ++ unsafe { ++ local_apic.set_lvt_nmi(nmi_pin, nmi_flags); ++ } ++ } else { ++ debug!( ++ " x2APIC NMI: skipping uid {} on current uid {:?}", ++ target_uid, current_uid ++ ); + } + } + } else if let MadtEntry::LapicAddressOverride(addr) = madt_entry { + let lapic_addr = addr.local_apic_address; + if lapic_addr != 0 { + debug!(" LAPIC address override: {:#x}", lapic_addr); ++ apply_lapic_address_override(local_apic, lapic_addr); + } } } diff --git a/src/acpi/madt/mod.rs b/src/acpi/madt/mod.rs -index 3159b9c4..da6c12af 100644 +index 3159b9c4..23551c64 100644 --- a/src/acpi/madt/mod.rs +++ b/src/acpi/madt/mod.rs @@ -146,6 +146,52 @@ pub struct MadtGicd { @@ -269,7 +1169,7 @@ index 3159b9c4..da6c12af 100644 if self.i + entry_len <= self.sdt.data_len() { let item = match entry_type { 0x0 => { -@@ -224,6 +282,15 @@ impl Iterator for MadtIter { +@@ -224,6 +282,44 @@ impl Iterator for MadtIter { MadtEntry::InvalidGicd(entry_len) } } @@ -281,16 +1181,178 @@ index 3159b9c4..da6c12af 100644 + } else { + MadtEntry::InvalidLocalX2Apic(entry_len) + } ++ } ++ 0x4 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalApicNmi(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) as *const MadtLocalApicNmi) ++ }) ++ } else { ++ MadtEntry::InvalidLocalApicNmi(entry_len) ++ } ++ } ++ 0x5 => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LapicAddressOverride(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) ++ as *const MadtLapicAddressOverride) ++ }) ++ } else { ++ MadtEntry::InvalidLapicAddressOverride(entry_len) ++ } ++ } ++ 0xA => { ++ if entry_len == size_of::() + 2 { ++ MadtEntry::LocalX2ApicNmi(unsafe { ++ &*((self.sdt.data_address() + self.i + 2) ++ as *const MadtLocalX2ApicNmi) ++ }) ++ } else { ++ MadtEntry::InvalidLocalX2ApicNmi(entry_len) ++ } + } _ => MadtEntry::Unknown(entry_type), }; diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs -index 59e35265..80a40a01 100644 +index 59e35265..d4c81f11 100644 --- a/src/acpi/mod.rs +++ b/src/acpi/mod.rs -@@ -138,6 +138,15 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { +@@ -10,6 +10,8 @@ use crate::memory::{KernelMapper, PageFlags, PhysicalAddress, RmmA, RmmArch}; + + use self::{hpet::Hpet, madt::Madt, rsdp::Rsdp, rsdt::Rsdt, rxsdt::Rxsdt, sdt::Sdt, xsdt::Xsdt}; + ++const MAX_SDT_SIZE: usize = 16 * 1024 * 1024; ++ + #[cfg(target_arch = "aarch64")] + mod gtdt; + pub mod hpet; +@@ -22,39 +24,79 @@ pub mod sdt; + mod spcr; + mod xsdt; + +-unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) { ++unsafe fn map_linearly( ++ addr: PhysicalAddress, ++ len: usize, ++ mapper: &mut crate::memory::PageMapper, ++) -> bool { + unsafe { + let base = PhysicalAddress::new(crate::memory::round_down_pages(addr.data())); +- let aligned_len = crate::memory::round_up_pages(len + (addr.data() - base.data())); ++ let Some(total_len) = len.checked_add(addr.data() - base.data()) else { ++ error!("ACPI table mapping length overflow at {:#x}", addr.data()); ++ return false; ++ }; ++ let aligned_len = crate::memory::round_up_pages(total_len); + + for page_idx in 0..aligned_len / crate::memory::PAGE_SIZE { +- let (_, flush) = mapper ++ let Some((_virt, flush)) = mapper + .map_linearly( + base.add(page_idx * crate::memory::PAGE_SIZE), + PageFlags::new(), + ) +- .expect("failed to linearly map SDT"); ++ else { ++ error!( ++ "failed to linearly map ACPI table page at {:#x}", ++ base.add(page_idx * crate::memory::PAGE_SIZE).data() ++ ); ++ return false; ++ }; + flush.flush(); + } ++ ++ true + } + } + +-pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper) -> &'static Sdt { ++pub fn get_sdt(sdt_address: PhysicalAddress, mapper: &mut KernelMapper) -> Option<&'static Sdt> { + let sdt; + + unsafe { + const SDT_SIZE: usize = size_of::(); +- map_linearly(sdt_address, SDT_SIZE, mapper); ++ if !map_linearly(sdt_address, SDT_SIZE, mapper) { ++ return None; ++ } + + sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt); + +- map_linearly( ++ let total_len = sdt.length as usize; ++ if total_len < SDT_SIZE { ++ warn!( ++ "ACPI table {:?} at {:#x} shorter than header ({})", ++ sdt.signature, ++ sdt_address.data(), ++ total_len ++ ); ++ return None; ++ } ++ if total_len > MAX_SDT_SIZE { ++ warn!( ++ "ACPI table {:?} at {:#x} exceeds max supported size ({})", ++ sdt.signature, ++ sdt_address.data(), ++ total_len ++ ); ++ return None; ++ } ++ ++ if !map_linearly( + sdt_address.add(SDT_SIZE), +- sdt.length as usize - SDT_SIZE, ++ total_len - SDT_SIZE, + mapper, +- ); ++ ) { ++ return None; ++ } + } +- sdt ++ Some(sdt) + } + + #[repr(C, packed)] +@@ -95,7 +137,19 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { + + if let Some(rsdp) = rsdp_opt { + debug!("SDT address: {:#x}", rsdp.sdt_address().data()); +- let rxsdt = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw()); ++ let Some(rxsdt) = get_sdt(rsdp.sdt_address(), &mut KernelMapper::lock_rw()) else { ++ error!("Unable to map RSDT/XSDT header"); ++ return; ++ }; ++ ++ if !rxsdt.validate_checksum() { ++ warn!( ++ "Root ACPI table {:?} at {:#x} has invalid checksum; ignoring ACPI", ++ rxsdt.signature, ++ rsdp.sdt_address().data() ++ ); ++ return; ++ } + + let rxsdt = if let Some(rsdt) = Rsdt::new(rxsdt) { + let mut initialized = false; +@@ -132,12 +186,28 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) { + // TODO: Don't touch ACPI tables in kernel? + + for sdt in rxsdt.iter() { +- get_sdt(sdt, &mut KernelMapper::lock_rw()); ++ if get_sdt(sdt, &mut KernelMapper::lock_rw()).is_none() { ++ warn!("Skipping unreadable ACPI table at {:#x}", sdt.data()); ++ } + } + for sdt_address in rxsdt.iter() { ++ let Some(sdt) = get_sdt(sdt_address, &mut KernelMapper::lock_rw()) else { ++ warn!("Skipping ACPI table at {:#x}: unable to map safely", sdt_address.data()); ++ continue; ++ }; let sdt = &*(RmmA::phys_to_virt(sdt_address).data() as *const Sdt); + if !sdt.validate_checksum() { @@ -300,11 +1362,82 @@ index 59e35265..80a40a01 100644 + sig, + sdt_address.data() + ); ++ continue; + } + let signature = get_sdt_signature(sdt); if let Some(ref mut ptrs) = *(SDT_POINTERS.write()) { ptrs.insert(signature, sdt); +@@ -198,8 +268,7 @@ macro_rules! find_one_sdt { + } + + pub fn get_sdt_signature(sdt: &'static Sdt) -> SdtSignature { +- let signature = +- String::from_utf8(sdt.signature.to_vec()).expect("Error converting signature to string"); ++ let signature = String::from_utf8_lossy(&sdt.signature).into_owned(); + (signature, sdt.oem_id, sdt.oem_table_id) + } + +diff --git a/src/acpi/rsdp.rs b/src/acpi/rsdp.rs +index f10c5ac9..571aeeec 100644 +--- a/src/acpi/rsdp.rs ++++ b/src/acpi/rsdp.rs +@@ -1,5 +1,8 @@ + use rmm::PhysicalAddress; + ++const RSDP_V1_SIZE: usize = 20; ++const RSDP_V2_MIN_SIZE: usize = size_of::(); ++ + /// RSDP + #[derive(Copy, Clone, Debug)] + #[repr(C, packed)] +@@ -17,10 +20,33 @@ pub struct Rsdp { + + impl Rsdp { + pub unsafe fn get_rsdp(already_supplied_rsdp: Option<*const u8>) -> Option { +- already_supplied_rsdp.map(|rsdp_ptr| { +- // TODO: Validate +- unsafe { *(rsdp_ptr as *const Rsdp) } +- }) ++ let rsdp_ptr = already_supplied_rsdp?; ++ let rsdp = unsafe { *(rsdp_ptr as *const Rsdp) }; ++ ++ if rsdp.signature != *b"RSD PTR " { ++ warn!("RSDP signature invalid"); ++ return None; ++ } ++ ++ if !checksum_ok(rsdp_ptr, RSDP_V1_SIZE) { ++ warn!("RSDP base checksum invalid"); ++ return None; ++ } ++ ++ if rsdp.revision >= 2 { ++ let length = rsdp._length as usize; ++ if length < RSDP_V2_MIN_SIZE { ++ warn!("RSDP revision {} length {} too small", rsdp.revision, length); ++ return None; ++ } ++ ++ if !checksum_ok(rsdp_ptr, length) { ++ warn!("RSDP extended checksum invalid"); ++ return None; ++ } ++ } ++ ++ Some(rsdp) + } + + /// Get the RSDT or XSDT address +@@ -32,3 +58,8 @@ impl Rsdp { + }) + } + } ++ ++fn checksum_ok(ptr: *const u8, len: usize) -> bool { ++ let bytes = unsafe { core::slice::from_raw_parts(ptr, len) }; ++ bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte)) == 0 ++} diff --git a/src/acpi/sdt.rs b/src/acpi/sdt.rs index 83ff67da..f49b6212 100644 --- a/src/acpi/sdt.rs @@ -325,6 +1458,32 @@ index 83ff67da..f49b6212 100644 + bytes.iter().fold(0u8, |sum, &b| sum.wrapping_add(b)) == 0 + } } +diff --git a/src/arch/aarch64/start.rs b/src/arch/aarch64/start.rs +index e1c8cfb4..65e3fe33 100644 +--- a/src/arch/aarch64/start.rs ++++ b/src/arch/aarch64/start.rs +@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { + dtb::serial::init_early(dtb); + } + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + // Initialize RMM +diff --git a/src/arch/riscv64/start.rs b/src/arch/riscv64/start.rs +index 2551968f..a825536a 100644 +--- a/src/arch/riscv64/start.rs ++++ b/src/arch/riscv64/start.rs +@@ -97,7 +97,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs) -> ! { + init_early(dtb); + } + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + if let Some(dtb) = &dtb { diff --git a/src/arch/x86_shared/cpuid.rs b/src/arch/x86_shared/cpuid.rs index b3683125..be7db1be 100644 --- a/src/arch/x86_shared/cpuid.rs @@ -362,6 +1521,215 @@ index b3683125..be7db1be 100644 #[cfg_attr(not(target_arch = "x86_64"), expect(dead_code))] pub fn feature_info() -> FeatureInfo { cpuid() +diff --git a/src/arch/x86_shared/device/ioapic.rs b/src/arch/x86_shared/device/ioapic.rs +index fb66d3bf..5938540f 100644 +--- a/src/arch/x86_shared/device/ioapic.rs ++++ b/src/arch/x86_shared/device/ioapic.rs +@@ -14,6 +14,9 @@ pub struct IoApicRegs { + pointer: *const u32, + } + impl IoApicRegs { ++ fn redirection_index_valid(&mut self, idx: u8) -> bool { ++ idx <= self.max_redirection_table_entries() ++ } + fn ioregsel(&self) -> *const u32 { + self.pointer + } +@@ -44,21 +47,28 @@ impl IoApicRegs { + pub fn read_ioapicver(&mut self) -> u32 { + self.read_reg(0x01) + } +- pub fn read_ioredtbl(&mut self, idx: u8) -> u64 { +- assert!(idx < 24); ++ pub fn read_ioredtbl(&mut self, idx: u8) -> Option { ++ if !self.redirection_index_valid(idx) { ++ warn!("IOAPIC read_ioredtbl index {} out of range", idx); ++ return None; ++ } + let lo = self.read_reg(0x10 + idx * 2); + let hi = self.read_reg(0x10 + idx * 2 + 1); + +- u64::from(lo) | (u64::from(hi) << 32) ++ Some(u64::from(lo) | (u64::from(hi) << 32)) + } +- pub fn write_ioredtbl(&mut self, idx: u8, value: u64) { +- assert!(idx < 24); ++ pub fn write_ioredtbl(&mut self, idx: u8, value: u64) -> bool { ++ if !self.redirection_index_valid(idx) { ++ warn!("IOAPIC write_ioredtbl index {} out of range", idx); ++ return false; ++ } + + let lo = value as u32; + let hi = (value >> 32) as u32; + + self.write_reg(0x10 + idx * 2, lo); + self.write_reg(0x10 + idx * 2 + 1, hi); ++ true + } + + pub fn max_redirection_table_entries(&mut self) -> u8 { +@@ -92,17 +102,22 @@ impl IoApic { + } + /// Map an interrupt vector to a physical local APIC ID of a processor (thus physical mode). + #[allow(dead_code)] +- pub fn map(&self, idx: u8, info: MapInfo) { +- self.regs.lock().write_ioredtbl(idx, info.as_raw()) ++ pub fn map(&self, idx: u8, info: MapInfo) -> bool { ++ let Some(raw) = info.as_raw() else { ++ return false; ++ }; ++ self.regs.lock().write_ioredtbl(idx, raw) + } + pub fn set_mask(&self, gsi: u32, mask: bool) { + let idx = (gsi - self.gsi_start) as u8; + let mut guard = self.regs.lock(); + +- let mut reg = guard.read_ioredtbl(idx); ++ let Some(mut reg) = guard.read_ioredtbl(idx) else { ++ return; ++ }; + reg &= !(1 << 16); + reg |= u64::from(mask) << 16; +- guard.write_ioredtbl(idx, reg); ++ let _ = guard.write_ioredtbl(idx, reg); + } + } + +@@ -149,19 +164,21 @@ pub struct MapInfo { + } + + impl MapInfo { +- pub fn as_raw(&self) -> u64 { +- assert!(self.vector >= 0x20); +- assert!(self.vector <= 0xFE); ++ pub fn as_raw(&self) -> Option { ++ if !(0x20..=0xFE).contains(&self.vector) { ++ warn!("Refusing to map IOAPIC vector outside valid range: {:#x}", self.vector); ++ return None; ++ } + + // TODO: Check for reserved fields. + +- (u64::from(self.dest.get()) << 56) ++ Some((u64::from(self.dest.get()) << 56) + | (u64::from(self.mask) << 16) + | ((self.trigger_mode as u64) << 15) + | ((self.polarity as u64) << 13) + | ((self.dest_mode as u64) << 11) + | ((self.delivery_mode as u64) << 8) +- | u64::from(self.vector) ++ | u64::from(self.vector)) + } + } + +@@ -175,7 +192,7 @@ impl fmt::Debug for IoApic { + + let count = guard.max_redirection_table_entries(); + f.debug_list() +- .entries((0..count).map(|i| guard.read_ioredtbl(i))) ++ .entries((0..=count).filter_map(|i| guard.read_ioredtbl(i))) + .finish() + } + } +@@ -237,11 +254,14 @@ pub unsafe fn handle_ioapic(madt_ioapic: &'static MadtIoApic) { + let ioapic_registers = virt.data() as *const u32; + let ioapic = IoApic::new(ioapic_registers, madt_ioapic.gsi_base); + +- assert_eq!( +- ioapic.regs.lock().id(), +- madt_ioapic.id, +- "mismatched ACPI MADT I/O APIC ID, and the ID reported by the I/O APIC" +- ); ++ let detected_id = ioapic.regs.lock().id(); ++ if detected_id != madt_ioapic.id { ++ warn!( ++ "mismatched ACPI MADT I/O APIC ID: MADT={}, IOAPIC={}; continuing with detected hardware", ++ madt_ioapic.id, ++ detected_id ++ ); ++ } + + (*IOAPICS.get()).get_or_insert_with(Vec::new).push(ioapic); + } +@@ -310,11 +330,14 @@ pub unsafe fn init() { + } + } + } +- println!( +- "I/O APICs: {:?}, overrides: {:?}", +- ioapics(), +- src_overrides() +- ); ++ // Sanitize all IOAPIC redirection entries: mask everything first to clear ++ // stale firmware/emulator defaults. Entries are selectively unmasked below. ++ for ioapic in ioapics().iter() { ++ let max_idx = ioapic.count; ++ for idx in 0..=max_idx { ++ ioapic.set_mask(ioapic.gsi_start + u32::from(idx), true); ++ } ++ } + + // map the legacy PC-compatible IRQs (0-15) to 32-47, just like we did with 8259 PIC (if it + // wouldn't have been disabled due to this I/O APIC) +@@ -329,7 +352,6 @@ pub unsafe fn init() { + .iter() + .any(|over| over.bus_irq == legacy_irq) + { +- // there's an IRQ conflict, making this legacy IRQ inaccessible. + continue; + } + ( +@@ -349,7 +371,6 @@ pub unsafe fn init() { + let redir_tbl_index = (gsi - apic.gsi_start) as u8; + + let map_info = MapInfo { +- // only send to the BSP + dest: bsp_apic_id, + dest_mode: DestinationMode::Physical, + delivery_mode: DeliveryMode::Fixed, +@@ -366,7 +387,31 @@ pub unsafe fn init() { + }, + vector: 32 + legacy_irq, + }; +- apic.map(redir_tbl_index, map_info); ++ if !apic.map(redir_tbl_index, map_info) { ++ warn!( ++ "Unable to map legacy IRQ {} (GSI {}) through IOAPIC index {}", ++ legacy_irq, gsi, redir_tbl_index ++ ); ++ } ++ ++ // IRQ 0 (timer) is often overridden to GSI 2, but some platforms ++ // (including QEMU) route the HPET timer directly to GSI 0 regardless ++ // of the MADT override. Map GSI 0 as well so the timer works on both ++ // virtual and physical hardware. ++ if legacy_irq == 0 && gsi != u32::from(legacy_irq) { ++ if let Some(apic0) = find_ioapic(u32::from(legacy_irq)) { ++ let idx0 = (u32::from(legacy_irq) - apic0.gsi_start) as u8; ++ apic0.map(idx0, MapInfo { ++ dest: bsp_apic_id, ++ dest_mode: DestinationMode::Physical, ++ delivery_mode: DeliveryMode::Fixed, ++ mask: false, ++ polarity: ApicPolarity::ActiveHigh, ++ trigger_mode: ApicTriggerMode::Edge, ++ vector: 32, ++ }); ++ } ++ } + } + println!( + "I/O APICs: {:?}, overrides: {:?}", +@@ -406,7 +451,7 @@ fn resolve(irq: u8) -> u32 { + fn find_ioapic(gsi: u32) -> Option<&'static IoApic> { + ioapics() + .iter() +- .find(|apic| gsi >= apic.gsi_start && gsi < apic.gsi_start + u32::from(apic.count)) ++ .find(|apic| gsi >= apic.gsi_start && gsi <= apic.gsi_start + u32::from(apic.count)) + } + + pub unsafe fn mask(irq: u8) { diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs index b6afe02a..e256d160 100644 --- a/src/arch/x86_shared/device/local_apic.rs @@ -421,6 +1789,114 @@ index b6afe02a..e256d160 100644 unsafe fn setup_error_int(&mut self) { unsafe { let vector = 49u32; +diff --git a/src/arch/x86_shared/device/mod.rs b/src/arch/x86_shared/device/mod.rs +index 6f417706..acb14d72 100644 +--- a/src/arch/x86_shared/device/mod.rs ++++ b/src/arch/x86_shared/device/mod.rs +@@ -23,8 +23,7 @@ pub unsafe fn init() { + } + } + pub unsafe fn init_after_acpi() { +- // this will disable the IOAPIC if needed. +- //ioapic::init(mapper); ++ unsafe { ioapic::init() }; + } + + unsafe fn init_hpet() -> bool { +diff --git a/src/arch/x86_shared/interrupt/exception.rs b/src/arch/x86_shared/interrupt/exception.rs +index 7725a45d..fbba75c7 100644 +--- a/src/arch/x86_shared/interrupt/exception.rs ++++ b/src/arch/x86_shared/interrupt/exception.rs +@@ -1,3 +1,5 @@ ++use core::sync::atomic::{AtomicBool, Ordering}; ++ + use syscall::Exception; + use x86::irq::PageFaultError; + +@@ -10,6 +12,20 @@ use crate::{ + syscall::flag::*, + }; + ++static NMI_IN_PROGRESS: AtomicBool = AtomicBool::new(false); ++ ++unsafe fn nmi_raw_serial_write(s: &[u8]) { ++ use crate::syscall::io::{Io, Pio}; ++ let mut com1 = Pio::::new(0x3F8); ++ let lsr = Pio::::new(0x3F8 + 5); ++ for &b in s { ++ while lsr.read() & (1 << 5) == 0 { ++ core::hint::spin_loop(); ++ } ++ com1.write(b); ++ } ++} ++ + interrupt_stack!(divide_by_zero, |stack| { + println!("Divide by zero"); + stack.trace(); +@@ -55,9 +71,24 @@ interrupt_stack!(non_maskable, @paranoid, |stack| { + + #[cfg(not(all(target_arch = "x86_64", feature = "profiling")))] + { +- // TODO: This will likely deadlock +- println!("Non-maskable interrupt"); +- stack.dump(); ++ if NMI_IN_PROGRESS.swap(true, Ordering::SeqCst) { ++ return; ++ } ++ unsafe { ++ nmi_raw_serial_write(b"Non-maskable interrupt\n"); ++ nmi_raw_serial_write(b" RIP: "); ++ // Print RIP as hex manually to avoid formatting locks ++ let rip = stack.iret.rip; ++ let mut buf = [0u8; 19]; ++ buf[0] = b'0'; buf[1] = b'x'; ++ for i in 0..16 { ++ let nibble = ((rip >> (60 - i * 4)) & 0xf) as u8; ++ buf[2 + i] = if nibble < 10 { b'0' + nibble } else { b'a' + nibble - 10 }; ++ } ++ buf[18] = b'\n'; ++ nmi_raw_serial_write(&buf); ++ } ++ NMI_IN_PROGRESS.store(false, Ordering::SeqCst); + } + }); + +diff --git a/src/arch/x86_shared/start.rs b/src/arch/x86_shared/start.rs +index 7a7c0ae8..62f9523c 100644 +--- a/src/arch/x86_shared/start.rs ++++ b/src/arch/x86_shared/start.rs +@@ -91,7 +91,7 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { + // Set up graphical debug + graphical_debug::init(args.env()); + +- info!("Redox OS starting..."); ++ info!("RedBear OS starting..."); + args.print(); + + // Set up GDT +@@ -127,16 +127,21 @@ unsafe extern "C" fn start(args_ptr: *const KernelArgs, stack_end: usize) -> ! { + + // Initialize devices + device::init(); ++ info!("kernel: device init complete (PIC + LAPIC)"); + + // Read ACPI tables, starts APs + if cfg!(feature = "acpi") { + crate::acpi::init(args.acpi_rsdp()); ++ info!("kernel: ACPI tables parsed"); ++ + device::init_after_acpi(); ++ info!("kernel: IOAPIC init complete"); + } + crate::profiling::init(); + + // Initialize all of the non-core devices not otherwise needed to complete initialization + device::init_noncore(); ++ info!("kernel: timer init complete, entering userspace"); + + args.bootstrap() + }; diff --git a/src/context/memory.rs b/src/context/memory.rs index 94519448..0db1de53 100644 --- a/src/context/memory.rs diff --git a/recipes/core/base/P2-acpi-defer-aml.patch b/recipes/core/base/P2-acpi-defer-aml.patch new file mode 120000 index 00000000..bfb92644 --- /dev/null +++ b/recipes/core/base/P2-acpi-defer-aml.patch @@ -0,0 +1 @@ +../../../local/patches/base/P2-acpi-defer-aml.patch \ No newline at end of file diff --git a/recipes/core/base/recipe.toml b/recipes/core/base/recipe.toml index 9ea2d889..ac011867 100644 --- a/recipes/core/base/recipe.toml +++ b/recipes/core/base/recipe.toml @@ -1,7 +1,7 @@ [source] git = "https://gitlab.redox-os.org/redox-os/base.git" rev = "463f76b9608a896e6f6c9f63457f57f6409873c7" -patches = ["redox.patch", "P2-boot-runtime-fixes.patch", "P2-acpi-i2c-resources.patch", "P2-daemon-ready-graceful.patch", "P2-daemon-hardening.patch"] +patches = ["redox.patch", "P2-boot-runtime-fixes.patch", "P2-acpi-i2c-resources.patch", "P2-daemon-ready-graceful.patch", "P2-daemon-hardening.patch", "P2-acpi-defer-aml.patch"] [build] template = "custom"