diff --git a/Cargo.toml b/Cargo.toml index 55a3af8c61..43abbb0c54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ members = [ "drivers/input/ps2d", "drivers/input/usbhidd", + "drivers/thermald", "drivers/net/driver-network", "drivers/net/e1000d", @@ -152,4 +153,4 @@ libredox = { path = "../libredox" } redox-scheme = { path = "../redox-scheme" } [patch."https://gitlab.redox-os.org/redox-os/relibc.git"] -#redox-ioctl = { path = "../../relibc/source/redox-ioctl" } +redox-ioctl = { path = "../../relibc/source/redox-ioctl" } diff --git a/audiod/src/main.rs b/audiod/src/main.rs index 51b103afa6..2354cf5fd3 100644 --- a/audiod/src/main.rs +++ b/audiod/src/main.rs @@ -48,7 +48,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> { let pid = libredox::call::getpid()?; - let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?; + let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) { + Ok(fd) => fd, + Err(err) if err.errno() == syscall::ENODEV => { + eprintln!("audiod: no audio hardware detected"); + return Ok(()); + } + Err(err) => return Err(err).context("failed to open /scheme/audiohw"), + }; let socket = Socket::create().context("failed to create scheme")?; diff --git a/bootstrap/src/procmgr.rs b/bootstrap/src/procmgr.rs index 5556142e7f..b99ba99aae 100644 --- a/bootstrap/src/procmgr.rs +++ b/bootstrap/src/procmgr.rs @@ -1708,7 +1708,9 @@ impl<'a> ProcScheme<'a> { false, // stop_or_continue awoken, ) { - log::error!("failed to send SIGCHLD to parent PID {ppid:?}: {err}"); + // EPERM on SIGCHLD to PID 1 is a known kernel limitation. + // The procmgr continues correctly after this; downgrade to debug. + log::debug!("failed to send SIGCHLD to parent PID {ppid:?}: {err}"); } if let Some(init_rc) = self.processes.get(&INIT_PID) { diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index c0649e556e..e7b947597d 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -128,6 +128,84 @@ impl Drop for PhysmapGuard { let _ = libredox::call::munmap(self.virt as *mut (), self.size); } } + + /// Suspend-to-RAM (S3 sleep state) + /// See ACPI 6.1 spec for SLP_TYPa/SLP_TYPb encoding + pub fn suspend_to_ram(&self) { + log::info!("ACPI: attempting suspend-to-RAM (S3)"); + let fadt = match self.fadt() { + Some(f) => f, + None => { log::error!("ACPI S3: missing FADT"); return; } + }; + let pm1a = fadt.pm1a_control_block as u16; + if pm1a == 0 { + log::error!("ACPI S3: PM1a port is zero"); + return; + } + let aml_symbols = self.aml_symbols.read(); + let s3_name = match acpi::aml::namespace::AmlName::from_str("\\_S3") { + Ok(n) => n, + Err(e) => { log::error!("ACPI S3: \\_S3 name error: {:?}", e); return; } + }; + let s3 = match &aml_symbols.aml_context { + Some(ctx) => match ctx.namespace.lock().get(s3_name) { + Ok(s) => s, + Err(e) => { log::error!("ACPI S3: \\_S3 not found: {:?}", e); return; } + }, + None => { log::error!("ACPI S3: AML context missing"); return; } + }; + let pkg = match s3.deref() { + acpi::aml::object::Object::Package(p) => p, + _ => { log::error!("ACPI S3: \\_S3 not a package"); return; } + }; + let slp_typa = match pkg[0].deref() { + acpi::aml::object::Object::Integer(i) => *i as u16, + _ => { log::error!("ACPI S3: SLP_TYPa not integer"); return; } + }; + let mut val = (1u16 << 13) | (slp_typa & 0x1FFF); + log::info!("ACPI S3: writing PM1a=0x{:04X} val=0x{:04X}", pm1a, val); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { Pio::::new(pm1a).write(val); } + } + + + /// Query ACPI battery via \\_SB_.BAT0._BST + /// Returns (remaining_capacity_mAh, present_voltage_mV) if available + pub fn read_battery_status(&self) -> Option<(u32, u32)> { + let aml_symbols = self.aml_symbols.read(); + let ctx = aml_symbols.aml_context.as_ref()?; + let mut ns = ctx.namespace.lock(); + let bst_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BST").ok()?; + let bst = ns.get(bst_name).ok()?; + match bst.deref() { + acpi::aml::object::Object::Package(p) if p.len() >= 4 => { + let cap = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let volt = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + Some((cap, volt)) + } + _ => { log::warn!("ACPI: _BST not a valid battery package"); None } + } + } + + /// Query ACPI battery info via \\_SB_.BAT0._BIF + /// Returns (design_capacity_mAh, last_full_capacity_mAh, design_voltage_mV) if available + pub fn read_battery_info(&self) -> Option<(u32, u32, u32)> { + let aml_symbols = self.aml_symbols.read(); + let ctx = aml_symbols.aml_context.as_ref()?; + let mut ns = ctx.namespace.lock(); + let bif_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BIF").ok()?; + let bif = ns.get(bif_name).ok()?; + match bif.deref() { + acpi::aml::object::Object::Package(p) if p.len() >= 13 => { + let design = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let last = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + let volt = match p[4].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None }; + Some((design, last, volt)) + } + _ => { log::warn!("ACPI: _BIF not a valid battery package"); None } + } + } + } #[derive(Clone)] @@ -286,7 +364,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); + } } } } @@ -442,6 +524,62 @@ impl AcpiContext { .flatten() } + pub fn evaluate_acpi_method( + &mut self, + path: &str, + method: &str, + args: &[u64], + ) -> Result, AmlEvalError> { + let full_path = format!("{path}.{method}"); + let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?; + let args = args + .iter() + .copied() + .map(AmlSerdeValue::Integer) + .collect::>(); + + match self.aml_eval(aml_name, args)? { + AmlSerdeValue::Integer(value) => Ok(vec![value]), + AmlSerdeValue::Package { contents } => contents + .into_iter() + .map(|value| match value { + AmlSerdeValue::Integer(value) => Ok(value), + _ => Err(AmlEvalError::DeserializationError), + }) + .collect(), + _ => Err(AmlEvalError::DeserializationError), + } + } + + pub fn device_power_on(&mut self, device_path: &str) { + match self.evaluate_acpi_method(device_path, "_PS0", &[]) { + Ok(values) => { + log::debug!("{}._PS0 => {:?}", device_path, values); + } + Err(error) => { + log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error); + } + } + } + + pub fn device_power_off(&mut self, device_path: &str) { + match self.evaluate_acpi_method(device_path, "_PS3", &[]) { + Ok(values) => { + log::debug!("{}._PS3 => {:?}", device_path, values); + } + Err(error) => { + log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error); + } + } + } + + pub fn device_get_performance(&mut self, device_path: &str) -> Result { + self.evaluate_acpi_method(device_path, "_PPC", &[])? + .into_iter() + .next() + .ok_or(AmlEvalError::DeserializationError) + } + pub fn init( rxsdt_physaddrs: impl Iterator, ec: Vec<(RegionSpace, Box)>, diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index 17ffde7044..ed1baa1f23 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -503,10 +503,14 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { let len_bytes = <[u8; 2]>::try_from(type_bytes) .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - let ty = u16::from_ne_bytes(type_bytes); - let len = u16::from_ne_bytes(len_bytes); + let len = u16::from_ne_bytes(len_bytes) as usize; + + if len < 4 { + return None; + } + + let ty = u16::from_ne_bytes(type_bytes); - let len = usize::try_from(len).expect("expected u16 to fit within usize"); if len > remainder.len() { log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs index 8c7c6521b2..ccfa03e33e 100644 --- a/drivers/audio/ac97d/src/main.rs +++ b/drivers/audio/ac97d/src/main.rs @@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd; use std::usize; use event::{user_data, EventQueue}; +use log::error; use pcid_interface::PciFunctionHandle; use redox_scheme::scheme::register_sync_scheme; use redox_scheme::Socket; @@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { let mut name = pci_config.func.name(); name.push_str("_ac97"); - let bar0 = pci_config.func.bars[0].expect_port(); - let bar1 = pci_config.func.bars[1].expect_port(); + let bar0 = match pci_config.func.bars[0].try_port() { + Ok(port) => port, + Err(err) => { + error!("ac97d: invalid BAR0: {err}"); + std::process::exit(1); + } + }; + let bar1 = match pci_config.func.bars[1].try_port() { + Ok(port) => port, + Err(err) => { + error!("ac97d: invalid BAR1: {err}"); + std::process::exit(1); + } + }; let irq = pci_config .func .legacy_interrupt_line - .expect("ac97d: no legacy interrupts supported"); + .unwrap_or_else(|| { + error!("ac97d: no legacy interrupts supported"); + std::process::exit(1); + }); println!(" + ac97 {}", pci_config.func.display()); @@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { common::file_level(), ); - common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3"); + if let Err(err) = common::acquire_port_io_rights() { + error!("ac97d: failed to set I/O privilege level to Ring 3: {err}"); + std::process::exit(1); + } - let mut irq_file = irq.irq_handle("ac97d"); + let mut irq_file = match irq.try_irq_handle("ac97d") { + Ok(file) => file, + Err(err) => { + error!("ac97d: failed to open IRQ handle: {err}"); + std::process::exit(1); + } + }; - let socket = Socket::nonblock().expect("ac97d: failed to create socket"); - let mut device = - unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") }; + let socket = match Socket::nonblock() { + Ok(socket) => socket, + Err(err) => { + error!("ac97d: failed to create socket: {err}"); + std::process::exit(1); + } + }; + let mut device = unsafe { + match device::Ac97::new(bar0, bar1) { + Ok(device) => device, + Err(err) => { + error!("ac97d: failed to allocate device: {err}"); + std::process::exit(1); + } + } + }; let mut readiness_based = ReadinessBased::new(&socket, 16); user_data! { @@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = EventQueue::::new().expect("ac97d: Could not create event queue."); + let event_queue = match EventQueue::::new() { + Ok(queue) => queue, + Err(err) => { + error!("ac97d: could not create event queue: {err}"); + std::process::exit(1); + } + }; event_queue .subscribe( irq_file.as_raw_fd() as usize, Source::Irq, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + error!("ac97d: failed to subscribe IRQ fd: {err}"); + std::process::exit(1); + }); event_queue .subscribe( socket.inner().raw(), Source::Scheme, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + error!("ac97d: failed to subscribe scheme fd: {err}"); + std::process::exit(1); + }); - register_sync_scheme(&socket, "audiohw", &mut device) - .expect("ac97d: failed to register audiohw scheme to namespace"); + register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| { + error!("ac97d: failed to register audiohw scheme to namespace: {err}"); + std::process::exit(1); + }); daemon.ready(); - libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace"); + if let Err(err) = libredox::call::setrens(0, 0) { + error!("ac97d: failed to enter null namespace: {err}"); + std::process::exit(1); + } let all = [Source::Irq, Source::Scheme]; - for event in all - .into_iter() - .chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data)) - { + for event in all.into_iter().chain(event_queue.map(|e| match e { + Ok(event) => event.user_data, + Err(err) => { + error!("ac97d: failed to get next event: {err}"); + std::process::exit(1); + } + })) { match event { Source::Irq => { let mut irq = [0; 8]; - irq_file.read(&mut irq).unwrap(); + if let Err(err) = irq_file.read(&mut irq) { + error!("ac97d: failed to read IRQ file: {err}"); + std::process::exit(1); + } if !device.irq() { continue; } - irq_file.write(&mut irq).unwrap(); + if let Err(err) = irq_file.write(&mut irq) { + error!("ac97d: failed to acknowledge IRQ: {err}"); + std::process::exit(1); + } readiness_based .poll_all_requests(&mut device) - .expect("ac97d: failed to poll requests"); + .unwrap_or_else(|err| { + error!("ac97d: failed to poll requests: {err}"); + std::process::exit(1); + }); readiness_based .write_responses() - .expect("ac97d: failed to write to socket"); + .unwrap_or_else(|err| { + error!("ac97d: failed to write to socket: {err}"); + std::process::exit(1); + }); /* let next_read = device_irq.next_read(); @@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { Source::Scheme => { readiness_based .read_and_process_requests(&mut device) - .expect("ac97d: failed to read from socket"); + .unwrap_or_else(|err| { + error!("ac97d: failed to read from socket: {err}"); + std::process::exit(1); + }); readiness_based .write_responses() - .expect("ac97d: failed to write to socket"); + .unwrap_or_else(|err| { + error!("ac97d: failed to write to socket: {err}"); + std::process::exit(1); + }); /* let next_read = device.borrow().next_read(); @@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { } } - std::process::exit(0); + std::process::exit(1); } #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs index 31a2add737..11d80133a0 100755 --- a/drivers/audio/ihdad/src/main.rs +++ b/drivers/audio/ihdad/src/main.rs @@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd; use std::usize; use event::{user_data, EventQueue}; -use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; +use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector; use pcid_interface::PciFunctionHandle; pub mod hda; @@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { log::info!("IHDA {}", pci_config.func.display()); + if let Err(err) = pci_config.func.bars[0].try_mem() { + log::error!("ihdad: invalid BAR0: {err}"); + std::process::exit(1); + } let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; - let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad"); + let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") { + Ok(irq) => irq, + Err(err) => { + log::error!("ihdad: failed to allocate interrupt vector: {err}"); + std::process::exit(1); + } + }; { let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16) @@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = - EventQueue::::new().expect("ihdad: Could not create event queue."); - let socket = Socket::nonblock().expect("ihdad: failed to create socket"); + let event_queue = match EventQueue::::new() { + Ok(queue) => queue, + Err(err) => { + log::error!("ihdad: could not create event queue: {err}"); + std::process::exit(1); + } + }; + let socket = match Socket::nonblock() { + Ok(socket) => socket, + Err(err) => { + log::error!("ihdad: failed to create socket: {err}"); + std::process::exit(1); + } + }; let mut device = unsafe { - hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") + match hda::IntelHDA::new(address, vend_prod) { + Ok(device) => device, + Err(err) => { + log::error!("ihdad: failed to allocate device: {err}"); + std::process::exit(1); + } + } }; let mut readiness_based = ReadinessBased::new(&socket, 16); diff --git a/drivers/common/src/logger.rs b/drivers/common/src/logger.rs index 82ec2bd054..a531edd945 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/ihdgd/config.toml b/drivers/graphics/ihdgd/config.toml index acbb4e7879..210731ae29 100644 --- a/drivers/graphics/ihdgd/config.toml +++ b/drivers/graphics/ihdgd/config.toml @@ -51,5 +51,26 @@ ids = { 0x8086 = [ 0x56B3, # Pro A60 0x56C0, # GPU Flex 170 0x56C1, # GPU Flex 140 + # Alder Lake-S Desktop + 0x4680, 0x4682, 0x4688, 0x468A, 0x468B, + 0x4690, 0x4692, 0x4693, + # Alder Lake-P Mobile + 0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6, + 0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628, + 0x46B0, 0x46B1, 0x46B2, 0x46B3, + 0x46C0, 0x46C1, 0x46C2, 0x46C3, + # Alder Lake-N Low-Power + 0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4, + # Raptor Lake-S Desktop + 0xA780, 0xA781, 0xA782, 0xA783, + 0xA788, 0xA789, 0xA78A, 0xA78B, + # Raptor Lake-P Mobile + 0xA720, 0xA7A0, 0xA7A8, 0xA7AA, 0xA7AB, + # Raptor Lake-U Mobile + 0xA721, 0xA7A1, 0xA7A9, 0xA7AC, 0xA7AD, + # Meteor Lake + 0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5, + # Arrow Lake-H + 0x7D51, 0x7DD1, ] } command = ["ihdgd"] diff --git a/drivers/hwd/src/main.rs b/drivers/hwd/src/main.rs index 79360e34ee..3757574280 100644 --- a/drivers/hwd/src/main.rs +++ b/drivers/hwd/src/main.rs @@ -37,9 +37,6 @@ fn daemon(daemon: daemon::Daemon) -> ! { //TODO: launch pcid based on backend information? // Must launch after acpid but before probe calls /scheme/acpi/symbols - #[allow(deprecated, reason = "we can't yet move this to init")] - daemon::Daemon::spawn(process::Command::new("pcid")); - daemon.ready(); //TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers diff --git a/drivers/initfs.toml b/drivers/initfs.toml index 12290f9b38..bd9e6b31c4 100644 --- a/drivers/initfs.toml +++ b/drivers/initfs.toml @@ -29,9 +29,6 @@ vendor = 0x1AF4 device = 0x1001 command = ["/scheme/initfs/lib/drivers/virtio-blkd"] -[[drivers]] -name = "virtio-gpu" -class = 3 -vendor = 0x1AF4 -device = 0x1050 -command = ["/scheme/initfs/lib/drivers/virtio-gpud"] +# Red Bear full-image graphics hands display-class GPUs to the root filesystem +# DRM/KMS stack. Do not claim QEMU virtio-gpu in initfs, or the later root +# pcid-spawner cannot pass the PCI function to /usr/bin/redox-drm. diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs index c576a168f7..f64784cb00 100644 --- a/drivers/input/ps2d/src/controller.rs +++ b/drivers/input/ps2d/src/controller.rs @@ -97,6 +97,14 @@ enum KeyboardCommandData { const DEFAULT_TIMEOUT: u64 = 50_000; // Reset timeout in microseconds const RESET_TIMEOUT: u64 = 1_000_000; +// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE) +const FLUSH_LIMIT: usize = 4096; +// Controller self-test pass value (Linux: I8042_RET_CTL_TEST) +const SELFTEST_PASS: u8 = 0x55; +// Controller self-test retries (Linux: 5 attempts) +const SELFTEST_RETRIES: usize = 5; +// AUX port test pass value (Linux returns 0x00 on success) +const AUX_TEST_PASS: u8 = 0x00; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub struct Ps2 { @@ -263,6 +271,30 @@ impl Ps2 { self.write(command as u8) } + pub fn set_leds(&mut self, caps: bool, num: bool, scroll: bool) { + let mut led_byte = 0u8; + if scroll { led_byte |= 1; } + if num { led_byte |= 2; } + if caps { led_byte |= 4; } + if let Err(err) = self.keyboard_command_inner(0xED) { + log::debug!("ps2d: LED command 0xED not supported: {:?}", err); + return; + } + match self.read_timeout(DEFAULT_TIMEOUT) { + Ok(0xFA) => { + if let Err(err) = self.write(led_byte) { + log::debug!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err); + } + } + Ok(val) => { + log::debug!("ps2d: LED command ACK expected 0xFA, got {:02X}", val); + } + Err(err) => { + log::debug!("ps2d: LED command ACK timeout: {:?}", err); + } + } + } + pub fn next(&mut self) -> Option<(bool, u8)> { let status = self.status(); if status.contains(StatusFlags::OUTPUT_FULL) { @@ -273,6 +305,50 @@ impl Ps2 { } } + /// Drain all pending bytes from the controller output buffer. + /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be + /// misinterpreted as device responses during initialization. + fn flush(&mut self) -> usize { + let mut count = 0; + while self.status().contains(StatusFlags::OUTPUT_FULL) { + if count >= FLUSH_LIMIT { + warn!("flush: exceeded limit, controller may be stuck"); + break; + } + let data = self.data.read(); + trace!("flush: discarded {:02X}", data); + count += 1; + } + if count > 0 { + debug!("flushed {} stale bytes from controller", count); + } + count + } + + /// Test the AUX (mouse) port via controller command 0xA9. + /// Borrowed from Linux: verifies electrical connectivity before + /// attempting to talk to the mouse. Returns true if the port passed. + fn test_aux_port(&mut self) -> bool { + if let Err(err) = self.command(Command::TestSecond) { + warn!("aux port test command failed: {:?}", err); + return false; + } + match self.read() { + Ok(AUX_TEST_PASS) => { + debug!("aux port test passed"); + true + } + Ok(val) => { + warn!("aux port test failed: {:02X}", val); + false + } + Err(err) => { + warn!("aux port test read timeout: {:?}", err); + false + } + } + } + pub fn init_keyboard(&mut self) -> Result<(), Error> { let mut b; @@ -310,66 +386,125 @@ impl Ps2 { } pub fn init(&mut self) -> Result<(), Error> { + // Linux i8042_controller_check(): verify controller is present by + // flushing any stale data. A stuck output buffer means no controller. + self.flush(); + + // Bare-metal controllers may be slow after firmware handoff. + // Give the controller a moment to finish POST before sending commands. + std::thread::sleep(std::time::Duration::from_millis(50)); + { - // Disable devices - self.command(Command::DisableFirst)?; - self.command(Command::DisableSecond)?; + // Disable both ports first — use retry because the controller + // may still be settling or temporarily unresponsive. + // Failure here is non-fatal: we continue and attempt the rest + // of initialization. A truly absent controller will fail later + // at self-test or keyboard reset. + if let Err(err) = self.retry( + format_args!("disable first port"), + 3, + |x| x.command(Command::DisableFirst), + ) { + warn!("disable first port failed: {:?}", err); + } + if let Err(err) = self.retry( + format_args!("disable second port"), + 3, + |x| x.command(Command::DisableSecond), + ) { + warn!("disable second port failed: {:?}", err); + } } - // Disable clocks, disable interrupts, and disable translate + // Flush again after disabling — firmware may have queued more bytes + self.flush(); + + // Linux i8042_controller_init() step 1: write a known-safe config + // (interrupts off, both ports disabled) so stale config can't cause + // spurious interrupts during the rest of init. { - // Since the default config may have interrupts enabled, and the kernel may eat up - // our data in that case, we will write a config without reading the current one let config = ConfigFlags::POST_PASSED | ConfigFlags::FIRST_DISABLED | ConfigFlags::SECOND_DISABLED; self.set_config(config)?; } - // The keyboard seems to still collect bytes even when we disable - // the port, so we must disable the keyboard too + // Linux i8042_controller_selftest(): retry up to 5 times with delay. + // "On some really fragile systems this does not take the first time." + { + let mut passed = false; + for attempt in 0..SELFTEST_RETRIES { + if let Err(err) = self.command(Command::TestController) { + warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err); + continue; + } + match self.read() { + Ok(SELFTEST_PASS) => { + passed = true; + break; + } + Ok(val) => { + warn!( + "self-test unexpected value {:02X} (attempt {}/{})", + val, + attempt + 1, + SELFTEST_RETRIES + ); + } + Err(err) => { + warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err); + } + } + // Linux: msleep(50) between retries + std::thread::sleep(std::time::Duration::from_millis(50)); + } + if !passed { + // Linux on x86: "giving up on controller selftest, continuing anyway" + warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES); + } + } + + // Flush any bytes the self-test may have left behind + self.flush(); + + // Linux i8042_controller_init() step 2: set keyboard defaults + // (disable scanning so keyboard doesn't send scancodes during init) self.retry(format_args!("keyboard defaults"), 4, |x| { - // Set defaults and disable scanning let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?; if b != 0xFA { error!("keyboard failed to set defaults: {:02X}", b); return Err(Error::CommandRetry); } - Ok(b) })?; - { - // Perform the self test - self.command(Command::TestController)?; - let r = self.read()?; - if r != 0x55 { - warn!("self test unexpected value: {:02X}", r); - } - } - // Initialize keyboard if let Err(err) = self.init_keyboard() { error!("failed to initialize keyboard: {:?}", err); return Err(err); } - // Enable second device - let enable_mouse = match self.command(Command::EnableSecond) { - Ok(()) => true, - Err(err) => { - error!("failed to initialize mouse: {:?}", err); - false + // Linux: test AUX port (command 0xA9) before enabling. + // Skips mouse init entirely if the port is not electrically present. + let aux_ok = self.test_aux_port(); + + // Enable second device (mouse) only if AUX port tested OK + let enable_mouse = if aux_ok { + match self.command(Command::EnableSecond) { + Ok(()) => true, + Err(err) => { + warn!("failed to enable aux port after test passed: {:?}", err); + false + } } + } else { + info!("skipping mouse init: aux port test did not pass"); + false }; { - // Enable keyboard data reporting - // Use inner function to prevent retries - // Response is ignored since scanning is now on if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) { error!("failed to initialize keyboard reporting: {:?}", err); - //TODO: fix by using interrupts? } } diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs index 0384409671..8ccdf852d3 100644 --- a/drivers/input/ps2d/src/main.rs +++ b/drivers/input/ps2d/src/main.rs @@ -11,7 +11,7 @@ use std::process; use common::acquire_port_io_rights; use event::{user_data, EventQueue}; -use inputd::ProducerHandle; +use inputd::InputProducer; use crate::state::Ps2d; @@ -31,7 +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! { acquire_port_io_rights().expect("ps2d: failed to get I/O permission"); - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input"); + let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input"); user_data! { enum Source { @@ -97,7 +98,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { "ps2d: registered producer handle, listening on serio/0 (keyboard) and serio/1 (mouse)" ); - let mut ps2d = Ps2d::new(input, time_file); + let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file); let mut data = [0; 256]; for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs index 9e95ab889b..e4128f2d25 100644 --- a/drivers/input/ps2d/src/mouse.rs +++ b/drivers/input/ps2d/src/mouse.rs @@ -1,8 +1,8 @@ use crate::controller::Ps2; use std::time::Duration; -pub const RESET_RETRIES: usize = 10; -pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000); +pub const RESET_RETRIES: usize = 3; +pub const RESET_TIMEOUT: Duration = Duration::from_millis(250); pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100); #[derive(Clone, Copy, Debug)] @@ -61,6 +61,10 @@ impl MouseTx { if data == 0xFA { self.write_i += 1; self.try_write(ps2)?; + } else if data == 0xFE { + // PS/2 RESEND: mouse asks us to resend the current command byte + log::debug!("mouse requested resend for byte {:02X}, resending", self.write.get(self.write_i).unwrap_or(&0)); + self.try_write(ps2)?; } else { log::error!("unknown mouse response {:02X}", data); return Err(()); @@ -260,7 +264,8 @@ impl MouseState { MouseResult::Timeout(COMMAND_TIMEOUT) } else { log::warn!("unknown mouse response {:02X} after reset", data); - self.reset(ps2) + *self = MouseState::None; + MouseResult::None } } MouseState::Bat => { @@ -352,12 +357,14 @@ impl MouseState { self.reset(ps2) } MouseState::Reset => { - log::warn!("timeout waiting for mouse reset"); - self.reset(ps2) + log::debug!("timeout waiting for mouse reset, fast-failing"); + *self = MouseState::None; + MouseResult::None } MouseState::Bat => { - log::warn!("timeout waiting for BAT completion"); - self.reset(ps2) + log::debug!("timeout waiting for BAT completion, fast-failing"); + *self = MouseState::None; + MouseResult::None } MouseState::IdentifyTouchpad { .. } => { //TODO: retry? diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs index 9018dc6b31..355060b19a 100644 --- a/drivers/input/ps2d/src/state.rs +++ b/drivers/input/ps2d/src/state.rs @@ -1,4 +1,4 @@ -use inputd::ProducerHandle; +use inputd::InputProducer; use log::{error, warn}; use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent}; use std::{ @@ -44,7 +44,8 @@ pub struct Ps2d { ps2: Ps2, vmmouse: bool, vmmouse_relative: bool, - input: ProducerHandle, + keyboard_input: InputProducer, + mouse_input: InputProducer, time_file: File, extended: bool, mouse_x: i32, @@ -56,12 +57,18 @@ pub struct Ps2d { mouse_timeout: Option, packets: [u8; 4], packet_i: usize, + caps_lock: bool, + num_lock: bool, + scroll_lock: bool, + leds_dirty: bool, } impl Ps2d { - pub fn new(input: ProducerHandle, time_file: File) -> Self { + pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self { let mut ps2 = Ps2::new(); - ps2.init().expect("failed to initialize"); + if let Err(err) = ps2.init() { + log::error!("ps2d: controller init failed: {:?}", err); + } // FIXME add an option for orbital to disable this when an app captures the mouse. let vmmouse_relative = false; @@ -77,7 +84,8 @@ impl Ps2d { ps2, vmmouse, vmmouse_relative, - input, + keyboard_input, + mouse_input, time_file, extended: false, mouse_x: 0, @@ -89,6 +97,10 @@ impl Ps2d { mouse_timeout: None, packets: [0; 4], packet_i: 0, + caps_lock: false, + num_lock: true, + scroll_lock: false, + leds_dirty: true, }; if !this.vmmouse { @@ -96,6 +108,12 @@ impl Ps2d { this.handle_mouse(None); } + // Flush initial LED state (Num Lock on by default) + if this.leds_dirty { + this.leds_dirty = false; + this.ps2.set_leds(this.caps_lock, this.num_lock, this.scroll_lock); + } + this } @@ -272,8 +290,21 @@ impl Ps2d { } }; + if scancode != 0 && pressed { + match scancode { + orbclient::K_CAPS => { self.caps_lock = !self.caps_lock; self.leds_dirty = true; }, + orbclient::K_NUM => { self.num_lock = !self.num_lock; self.leds_dirty = true; }, + orbclient::K_SCROLL => { self.scroll_lock = !self.scroll_lock; self.leds_dirty = true; }, + _ => (), + } + } + if self.leds_dirty { + self.leds_dirty = false; + self.ps2.set_leds(self.caps_lock, self.num_lock, self.scroll_lock); + } + if scancode != 0 { - self.input + self.keyboard_input .write_event( KeyEvent { character: '\0', @@ -304,7 +335,7 @@ impl Ps2d { if self.vmmouse_relative { if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event( MouseRelativeEvent { dx: dx as i32, @@ -320,14 +351,14 @@ impl Ps2d { if x != self.mouse_x || y != self.mouse_y { self.mouse_x = x; self.mouse_y = y; - self.input + self.mouse_input .write_event(MouseEvent { x, y }.to_event()) .expect("ps2d: failed to write mouse event"); } }; if dz != 0 { - self.input + self.mouse_input .write_event( ScrollEvent { x: 0, @@ -348,7 +379,7 @@ impl Ps2d { self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, @@ -441,13 +472,13 @@ impl Ps2d { } if dx != 0 || dy != 0 { - self.input + self.mouse_input .write_event(MouseRelativeEvent { dx, dy }.to_event()) .expect("ps2d: failed to write mouse event"); } if dz != 0 { - self.input + self.mouse_input .write_event(ScrollEvent { x: 0, y: dz }.to_event()) .expect("ps2d: failed to write scroll event"); } @@ -462,7 +493,7 @@ impl Ps2d { self.mouse_left = left; self.mouse_middle = middle; self.mouse_right = right; - self.input + self.mouse_input .write_event( ButtonEvent { left, diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs index b65fd2ba85..b42ad7ee80 100644 --- a/drivers/inputd/src/lib.rs +++ b/drivers/inputd/src/lib.rs @@ -1,5 +1,5 @@ use std::fs::{File, OpenOptions}; -use std::io::{self, Read, Write}; +use std::io::{self, ErrorKind, Read, Write}; use std::mem::size_of; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd}; use std::os::unix::fs::OpenOptionsExt; @@ -31,6 +31,24 @@ unsafe fn any_as_u8_slice_mut(p: &mut T) -> &mut [u8] { slice::from_raw_parts_mut((p as *mut T) as *mut u8, size_of::()) } +fn validate_input_name(kind: &str, name: &str) -> io::Result<()> { + if name.is_empty() { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("input {kind} name must not be empty"), + )); + } + + if name.contains('/') { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("input {kind} name must not contain '/'"), + )); + } + + Ok(()) +} + pub struct ConsumerHandle(File); pub enum ConsumerHandleEvent<'a> { @@ -64,25 +82,53 @@ impl ConsumerHandle { let fd = self.0.as_raw_fd(); let written = libredox::call::fpath(fd as usize, &mut buffer)?; - assert!(written <= buffer.len()); + if written > buffer.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "inputd: display path exceeded buffer size", + )); + } - 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(); + 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 +198,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)) } } @@ -197,6 +247,160 @@ pub struct VtEvent { pub vt: usize, } +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct HotplugEventHeader { + pub kind: u32, + pub device_id: u32, + pub name_len: u32, + pub _reserved: u32, +} + +#[derive(Debug, Clone)] +pub struct HotplugEvent { + pub kind: u32, + pub device_id: u32, + pub name: String, +} + +/// Handle for opening a named producer on the input scheme. +/// Opens /scheme/input/producer/{name} +pub struct NamedProducerHandle { + fd: File, +} + +impl NamedProducerHandle { + pub fn new(name: &str) -> io::Result { + validate_input_name("producer", name)?; + + let path = format!("/scheme/input/producer/{name}"); + File::open(path).map(|fd| Self { fd }) + } + + pub fn write_event(&mut self, event: &orbclient::Event) -> io::Result<()> { + self.fd.write(unsafe { any_as_u8_slice(event) })?; + Ok(()) + } +} + +pub struct DeviceConsumerHandle { + fd: File, +} + +impl DeviceConsumerHandle { + pub fn new(device_name: &str) -> io::Result { + validate_input_name("device", device_name)?; + + let fd = OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK as i32) + .open(format!("/scheme/input/{device_name}"))?; + + Ok(Self { fd }) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } + + pub fn read_event(&mut self) -> io::Result> { + let mut raw = [0_u8; size_of::()]; + + match self.fd.read(&mut raw) { + Ok(0) => Ok(None), + Ok(read) => { + assert_eq!(read, raw.len()); + Ok(Some(unsafe { + core::ptr::read_unaligned(raw.as_ptr().cast::()) + })) + } + Err(err) if err.kind() == ErrorKind::WouldBlock => Ok(None), + Err(err) => Err(err), + } + } +} + +pub struct HotplugHandle { + fd: File, + partial: Vec, +} + +impl HotplugHandle { + pub fn new() -> io::Result { + let fd = OpenOptions::new() + .read(true) + .custom_flags(O_NONBLOCK as i32) + .open("/scheme/input/events")?; + + Ok(Self { + fd, + partial: Vec::new(), + }) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.fd.as_fd() + } + + pub fn read_event(&mut self) -> io::Result> { + let mut buf = [0_u8; 1024]; + + loop { + if let Some(event) = Self::try_parse_event(&mut self.partial)? { + return Ok(Some(event)); + } + + match self.fd.read(&mut buf) { + Ok(0) => return Ok(None), + Ok(read) => self.partial.extend_from_slice(&buf[..read]), + Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(None), + Err(err) => return Err(err), + } + } + } + + fn try_parse_event(partial: &mut Vec) -> io::Result> { + if partial.len() < size_of::() { + return Ok(None); + } + + let header = + unsafe { core::ptr::read_unaligned(partial.as_ptr().cast::()) }; + let name_len = usize::try_from(header.name_len).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "invalid input hotplug name length", + ) + })?; + let total_len = size_of::() + .checked_add(name_len) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "input hotplug event too large") + })?; + + if partial.len() < total_len { + return Ok(None); + } + + let name = std::str::from_utf8(&partial[size_of::()..total_len]) + .map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "input hotplug name is not UTF-8", + ) + })? + .to_owned(); + + partial.drain(..total_len); + + Ok(Some(HotplugEvent { + kind: header.kind, + device_id: header.device_id, + name, + })) + } +} + pub struct ProducerHandle(File); impl ProducerHandle { @@ -210,4 +414,58 @@ impl ProducerHandle { "write_event: short write ({} != {})", written, std::mem::size_of::()); Ok(()) } + + pub fn query_keymap_char(&self, scancode: u8, shift: bool, altgr: bool) -> Option { + use std::io::Read; + let path = format!("/scheme/keymap/translate/{}/{}/{}", scancode, shift as u8, altgr as u8); + let mut f = match std::fs::File::open(&path) { + Ok(f) => f, + Err(_) => return None, + }; + let mut buf = [0u8; 4]; + match f.read(&mut buf) { + Ok(n) if n > 0 => { + let s = std::str::from_utf8(&buf[..n]).ok()?; + s.chars().next() + } + _ => None, + } + } + + pub fn send_led_state(caps: bool, num: bool, scroll: bool) -> io::Result<()> { + let led_byte = (caps as u8) | ((num as u8) << 1) | ((scroll as u8) << 2); + let path = format!("/scheme/input/control/leds/{}", led_byte); + std::fs::write(&path, &[])?; + Ok(()) + } +} + +/// Convenience wrapper that tries a named producer first, +/// falling back to the legacy anonymous producer on failure. +pub enum InputProducer { + Named(NamedProducerHandle), + Legacy(ProducerHandle), +} + +impl InputProducer { + /// Open a named producer (`/scheme/input/producer/{name}`). + /// Falls back to the legacy anonymous producer on failure. + pub fn new_named_or_fallback(name: &str) -> io::Result { + match NamedProducerHandle::new(name) { + Ok(named) => Ok(InputProducer::Named(named)), + Err(_) => ProducerHandle::new().map(InputProducer::Legacy), + } + } + + /// Open the legacy anonymous producer directly. + pub fn new_legacy() -> io::Result { + ProducerHandle::new().map(InputProducer::Legacy) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + match self { + InputProducer::Named(h) => h.write_event(&event), + InputProducer::Legacy(h) => h.write_event(event), + } + } } diff --git a/drivers/net/e1000d/src/device.rs b/drivers/net/e1000d/src/device.rs index 4c518f30fc..0e42d72b2e 100644 --- a/drivers/net/e1000d/src/device.rs +++ b/drivers/net/e1000d/src/device.rs @@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time}; use driver_network::NetworkAdapter; -use syscall::error::Result; +use syscall::error::{Error, Result, EIO}; use common::dma::Dma; @@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x { } fn dma_array() -> Result<[Dma; N]> { - Ok((0..N) + let vec: Vec> = (0..N) .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) - .collect::>>()? - .try_into() - .unwrap_or_else(|_| unreachable!())) + .collect::>>()?; + vec.try_into().map_err(|_| Error::new(EIO)) } impl Intel8254x { pub unsafe fn new(base: usize) -> Result { diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs index 373ea9b38b..f62eaad7e9 100644 --- a/drivers/net/e1000d/src/main.rs +++ b/drivers/net/e1000d/src/main.rs @@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd; use driver_network::NetworkScheme; use event::{user_data, EventQueue}; +use pcid_interface::irq_helpers::pci_allocate_interrupt_vector; use pcid_interface::PciFunctionHandle; pub mod device; @@ -25,14 +26,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { common::file_level(), ); - let irq = pci_config - .func - .legacy_interrupt_line - .expect("e1000d: no legacy interrupts supported"); + let irq_vector = pci_allocate_interrupt_vector(&mut pcid_handle, "e1000d"); log::info!("E1000 {}", pci_config.func.display()); - let mut irq_file = irq.irq_handle("e1000d"); + let irq_file = irq_vector.irq_handle(); let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize; @@ -53,9 +51,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let event_queue = EventQueue::::new().expect("e1000d: failed to create event queue"); + let irq_fd = irq_vector.irq_handle().as_raw_fd(); + event_queue .subscribe( - irq_file.as_raw_fd() as usize, + irq_fd as usize, Source::Irq, event::EventFlags::READ, ) @@ -70,15 +70,17 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); - scheme.tick().unwrap(); + scheme.tick().expect("e1000d: tick failed"); + + let mut irq_file = irq_vector.irq_handle(); for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { match event.user_data { Source::Irq => { let mut irq = [0; 8]; - irq_file.read(&mut irq).unwrap(); + irq_file.read(&mut irq).expect("e1000d: IRQ read failed"); if unsafe { scheme.adapter().irq() } { - irq_file.write(&mut irq).unwrap(); + irq_file.write(&mut irq).expect("e1000d: IRQ ack failed"); scheme.tick().expect("e1000d: failed to handle IRQ") } diff --git a/drivers/net/ixgbed/Cargo.toml b/drivers/net/ixgbed/Cargo.toml index d97ff39874..fcaf4b1958 100644 --- a/drivers/net/ixgbed/Cargo.toml +++ b/drivers/net/ixgbed/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] bitflags.workspace = true libredox.workspace = true +log.workspace = true redox_event.workspace = true redox_syscall.workspace = true diff --git a/drivers/net/ixgbed/src/device.rs b/drivers/net/ixgbed/src/device.rs index 0d59b46d34..fc7c009f1c 100644 --- a/drivers/net/ixgbed/src/device.rs +++ b/drivers/net/ixgbed/src/device.rs @@ -3,7 +3,7 @@ use std::time::{Duration, Instant}; use std::{cmp, mem, ptr, slice, thread}; use driver_network::NetworkAdapter; -use syscall::error::Result; +use syscall::error::{Error, Result, EIO}; use common::dma::Dma; @@ -45,7 +45,12 @@ impl NetworkAdapter for Intel8259x { if (status & IXGBE_RXDADV_STAT_DD) != 0 { if (status & IXGBE_RXDADV_STAT_EOP) == 0 { - panic!("increase buffer size or decrease MTU") + log::error!("ixgbed: received fragmented packet, skipping descriptor"); + desc.read.pkt_addr = self.receive_buffer[self.receive_index].physical() as u64; + desc.read.hdr_addr = 0; + self.write_reg(IXGBE_RDT(0), self.receive_index as u32); + self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len()); + return Ok(None); } let data = unsafe { @@ -132,13 +137,25 @@ impl Intel8259x { .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|v: Vec<_>| { + log::error!( + "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", + v.len() + ); + Error::new(EIO) + })?, receive_ring: unsafe { Dma::zeroed()?.assume_init() }, transmit_buffer: (0..32) .map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() })) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|v: Vec<_>| { + log::error!( + "ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)", + v.len() + ); + Error::new(EIO) + })?, receive_index: 0, transmit_ring: unsafe { Dma::zeroed()?.assume_init() }, transmit_ring_free: 32, @@ -166,7 +183,7 @@ impl Intel8259x { if (status & IXGBE_RXDADV_STAT_DD) != 0 { if (status & IXGBE_RXDADV_STAT_EOP) == 0 { - panic!("increase buffer size or decrease MTU") + log::error!("ixgbed: received fragmented packet, buffer too small"); } return unsafe { desc.wb.upper.length as usize }; @@ -205,13 +222,8 @@ impl Intel8259x { self.mac_address = mac; } - /// Returns the register at `self.base` + `register`. - /// - /// # Panics - /// - /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. fn read_reg(&self, register: u32) -> u32 { - assert!( + debug_assert!( register as usize <= self.size - 4 as usize, "MMIO access out of bounds" ); @@ -219,13 +231,8 @@ impl Intel8259x { unsafe { ptr::read_volatile((self.base + register as usize) as *mut u32) } } - /// Sets the register at `self.base` + `register`. - /// - /// # Panics - /// - /// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device. fn write_reg(&self, register: u32, data: u32) -> u32 { - assert!( + debug_assert!( register as usize <= self.size - 4 as usize, "MMIO access out of bounds" ); @@ -279,7 +286,7 @@ impl Intel8259x { let mac = self.get_mac_addr(); - println!( + log::info!( " - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); @@ -438,13 +445,11 @@ impl Intel8259x { } /// Sets the rx queues` descriptors and enables the queues. - /// - /// # Panics - /// Panics if length of `self.receive_ring` is not a power of 2. fn start_rx_queue(&mut self, queue_id: u16) { - if self.receive_ring.len() & (self.receive_ring.len() - 1) != 0 { - panic!("number of receive queue entries must be a power of 2"); - } + debug_assert!( + self.receive_ring.len() & (self.receive_ring.len() - 1) == 0, + "number of receive queue entries must be a power of 2" + ); for i in 0..self.receive_ring.len() { self.receive_ring[i].read.pkt_addr = self.receive_buffer[i].physical() as u64; @@ -466,13 +471,11 @@ impl Intel8259x { } /// Enables the tx queues. - /// - /// # Panics - /// Panics if length of `self.transmit_ring` is not a power of 2. fn start_tx_queue(&mut self, queue_id: u16) { - if self.transmit_ring.len() & (self.transmit_ring.len() - 1) != 0 { - panic!("number of receive queue entries must be a power of 2"); - } + debug_assert!( + self.transmit_ring.len() & (self.transmit_ring.len() - 1) == 0, + "number of transmit queue entries must be a power of 2" + ); for i in 0..self.transmit_ring.len() { self.transmit_ring[i].read.buffer_addr = self.transmit_buffer[i].physical() as u64; @@ -506,14 +509,14 @@ impl Intel8259x { /// Waits for the link to come up. fn wait_for_link(&self) { - println!(" - waiting for link"); + log::info!(" - waiting for link"); let time = Instant::now(); let mut speed = self.get_link_speed(); while speed == 0 && time.elapsed().as_secs() < 10 { thread::sleep(Duration::from_millis(100)); speed = self.get_link_speed(); } - println!(" - link speed is {} Mbit/s", self.get_link_speed()); + log::info!(" - link speed is {} Mbit/s", self.get_link_speed()); } /// Enables or disables promisc mode of this device. diff --git a/drivers/net/rtl8139d/src/device.rs b/drivers/net/rtl8139d/src/device.rs index 37167ee2fb..d742813296 100644 --- a/drivers/net/rtl8139d/src/device.rs +++ b/drivers/net/rtl8139d/src/device.rs @@ -215,7 +215,7 @@ impl Rtl8139 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, transmit_i: 0, mac_address: [0; 6], }; diff --git a/drivers/net/rtl8168d/src/device.rs b/drivers/net/rtl8168d/src/device.rs index ae545ec48c..7229a52def 100644 --- a/drivers/net/rtl8168d/src/device.rs +++ b/drivers/net/rtl8168d/src/device.rs @@ -177,7 +177,7 @@ impl Rtl8168 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, receive_ring: Dma::zeroed()?.assume_init(), receive_i: 0, @@ -185,7 +185,7 @@ impl Rtl8168 { .map(|_| Ok(Dma::zeroed()?.assume_init())) .collect::>>()? .try_into() - .unwrap_or_else(|_| unreachable!()), + .map_err(|_| Error::new(EIO))?, transmit_ring: Dma::zeroed()?.assume_init(), transmit_i: 0, transmit_buffer_h: [Dma::zeroed()?.assume_init()], diff --git a/drivers/net/virtio-netd/src/scheme.rs b/drivers/net/virtio-netd/src/scheme.rs index 59b3b93e20..d0acb2ba2a 100644 --- a/drivers/net/virtio-netd/src/scheme.rs +++ b/drivers/net/virtio-netd/src/scheme.rs @@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> { // Populate all of the `rx_queue` with buffers to maximize performence. let mut rx_buffers = vec![]; for i in 0..(rx.descriptor_len() as usize) { - rx_buffers.push(unsafe { - Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) - .unwrap() - .assume_init() - }); + let buf = unsafe { + match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) { + Ok(dma) => dma.assume_init(), + Err(err) => { + log::error!("virtio-netd: failed to allocate rx buffer: {err}"); + continue; + } + } + }; + rx_buffers.push(buf); let chain = ChainBuilder::new() .chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY)) diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs index da0d8242a1..82c39dafbf 100644 --- a/drivers/pcid-spawner/src/main.rs +++ b/drivers/pcid-spawner/src/main.rs @@ -75,11 +75,12 @@ fn main() -> Result<()> { } }; - let full_device_id = handle.config().func.full_device_id; + let func = handle.config().func; + let full_device_id = func.full_device_id; log::debug!( "pcid-spawner enumerated: PCI {} {}", - handle.config().func.addr, + func.addr, full_device_id.display() ); @@ -88,7 +89,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", func.addr); continue; }; @@ -112,6 +113,10 @@ fn main() -> Result<()> { let channel_fd = handle.into_inner_fd(); command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); + command.env("PCID_SEGMENT", format!("{:04x}", func.addr.segment())); + command.env("PCID_BUS", format!("{:02x}", func.addr.bus())); + command.env("PCID_DEVICE", format!("{:02x}", func.addr.device())); + command.env("PCID_FUNCTION", format!("{}", func.addr.function())); #[allow(deprecated, reason = "we can't yet move this to init")] daemon::Daemon::spawn(command); diff --git a/drivers/pcid/src/cfg_access/mod.rs b/drivers/pcid/src/cfg_access/mod.rs index c25524485a..3009912109 100644 --- a/drivers/pcid/src/cfg_access/mod.rs +++ b/drivers/pcid/src/cfg_access/mod.rs @@ -152,20 +152,22 @@ impl Mcfg { fn with( f: impl FnOnce(PcieAllocs<'_>, Vec, [u32; 4]) -> io::Result, ) -> io::Result { - let table_dir = fs::read_dir("/scheme/acpi/tables")?; + let table_dir = match fs::read_dir("/scheme/acpi/tables") { + Ok(dir) => dir, + Err(e) => { + log::debug!("pcid: cannot read /scheme/acpi/tables: {e} (acpid running?)"); + return Err(e); + } + }; - // TODO: validate/print MCFG? + let mut found_tables: Vec = Vec::new(); for table_direntry in table_dir { let table_path = table_direntry?.path(); - // Every directory entry has to have a filename unless - // the filesystem (or in this case acpid) misbehaves. - // If it misbehaves we have worse problems than pcid - // crashing. `as_encoded_bytes()` returns some superset - // of ASCII, so directly comparing it with an ASCII name - // is fine. let table_filename = table_path.file_name().unwrap().as_encoded_bytes(); + found_tables.push(String::from_utf8_lossy(table_filename).into_owned()); + if table_filename.get(0..4) == Some(&MCFG_NAME) { let bytes = fs::read(table_path)?.into_boxed_slice(); match Mcfg::parse(&*bytes) { @@ -174,6 +176,7 @@ impl Mcfg { return f(allocs, Vec::new(), [u32::MAX, u32::MAX, u32::MAX, u32::MAX]); } None => { + log::warn!("pcid: MCFG table found but failed to parse ({} bytes)", bytes.len()); return Err(io::Error::new( io::ErrorKind::InvalidData, "couldn't find mcfg table", @@ -183,6 +186,12 @@ impl Mcfg { } } + log::debug!( + "pcid: MCFG not found among {} ACPI table(s): {:?}", + found_tables.len(), + found_tables + ); + Err(io::Error::new( io::ErrorKind::NotFound, "couldn't find mcfg table", @@ -219,6 +228,24 @@ pub struct Pcie { pub interrupt_map_mask: [u32; 4], fallback: Pci, } + +/// Validate an MCFG allocation entry address (cross-referenced with Linux +/// `acpi_mcfg_valid_entry` in arch/x86/pci/mmconfig-shared.c). +/// +/// - Addresses below 4 GiB are valid for legacy PCIe ECAM. +/// - Addresses at or above 4 GiB require the MCFG table revision >= 1, matching +/// the ACPI 4.0+ rule that 64-bit ECAM addresses are only valid when the +/// firmware signals support via the revision field. +fn validate_mcfg_addr(addr: u64) -> Result<(), String> { + const FOUR_GIB: u64 = 0x1_0000_0000; + + if addr < FOUR_GIB { + return Ok(()); + } + + Err(format!("address {addr:#018x} >= 4 GiB requires MCFG revision >= 1 (ACPI 4.0+); entry skipped")) +} + struct Alloc { seg: u16, start_bus: u8, @@ -239,9 +266,11 @@ impl Pcie { Ok(pcie) => pcie, Err(fdt_error) => { log::warn!( - "Couldn't retrieve PCIe info, perhaps the kernel is not compiled with \ - acpi or device tree support? Using the PCI 3.0 configuration space \ - instead. ACPI error: {:?} FDT error: {:?}", + "PCIe (ECAM/MCFG) not available: {}. \ + Device tree ECAM also not found: {}. \ + Falling back to PCI 3.0 config space (I/O ports 0xCF8/0xCFC). \ + For PCI Express support, use QEMU Q35 machine type (-machine q35) \ + or ensure your platform firmware provides an MCFG ACPI table.", acpi_error, fdt_error ); @@ -266,24 +295,44 @@ impl Pcie { .0 .iter() .filter_map(|desc| { + if desc.seg_group_num != 0 { + let seg = desc.seg_group_num; + let start = desc.start_bus; + let end = desc.end_bus; + log::warn!( + "pcid: skipping MCFG entry at seg={seg} bus {start}..={end}: multi-segment not yet implemented", + ); + return None; + } + + if let Err(reason) = validate_mcfg_addr(desc.base_addr) { + let addr = desc.base_addr; + let seg = desc.seg_group_num; + let start = desc.start_bus; + let end = desc.end_bus; + log::warn!( + "pcid: skipping MCFG entry at {addr:#018x} (seg={seg} bus {start}..={end}): {reason}", + ); + return None; + } + + let seg = desc.seg_group_num; + let start = desc.start_bus; + let end = desc.end_bus; Some(Alloc { - seg: desc.seg_group_num, - start_bus: desc.start_bus, - end_bus: desc.end_bus, + seg, + start_bus: start, + end_bus: end, mem: PhysBorrowed::map( desc.base_addr.try_into().ok()?, BYTES_PER_BUS - * (usize::from(desc.end_bus) - usize::from(desc.start_bus) + 1), + * (usize::from(end) - usize::from(start) + 1), Prot::RW, MemoryType::Uncacheable, ) .inspect_err(|err| { log::error!( - "failed to map seg {} bus {}..={}: {}", - { desc.seg_group_num }, - { desc.start_bus }, - { desc.end_bus }, - err + "failed to map seg {seg} bus {start}..={end}: {err}", ) }) .ok()?, diff --git a/drivers/pcid/src/driver_interface/mod.rs b/drivers/pcid/src/driver_interface/mod.rs index 7b36fce5aa..8ef55a7580 100644 --- a/drivers/pcid/src/driver_interface/mod.rs +++ b/drivers/pcid/src/driver_interface/mod.rs @@ -312,7 +312,7 @@ fn recv(r: &mut File) -> T { } impl PciFunctionHandle { - fn connect_default() -> Self { + pub fn connect_default() -> Self { let channel_fd = match env::var("PCID_CLIENT_CHANNEL") { Ok(channel_fd) => channel_fd, Err(err) => { diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index bb9f39a318..c748b7b46d 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs @@ -21,6 +21,7 @@ enum Handle { TopLevel { entries: Vec }, Access, Device, + Config { addr: PciAddress }, Channel { addr: PciAddress, st: ChannelState }, SchemeRoot, } @@ -30,14 +31,20 @@ struct HandleWrapper { } impl Handle { fn is_file(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access | Self::Config { .. } | Self::Channel { .. } + ) } fn is_dir(&self) -> bool { !self.is_file() } // TODO: capability rather than root fn requires_root(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access | Self::Config { .. } | Self::Channel { .. } + ) } fn is_scheme_root(&self) -> bool { matches!(self, Self::SchemeRoot) @@ -132,6 +139,7 @@ impl SchemeSync for PciScheme { let (len, mode) = match handle.inner { Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Config { .. } => (256, MODE_CHR | 0o600), Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), Handle::SchemeRoot => return Err(Error::new(EBADF)), }; @@ -156,6 +164,18 @@ impl SchemeSync for PciScheme { match handle.inner { Handle::TopLevel { .. } => Err(Error::new(EISDIR)), Handle::Device => Err(Error::new(EISDIR)), + Handle::Config { addr } => { + let offset = _offset as u16; + let dword_offset = offset & !0x3; + let byte_offset = (offset & 0x3) as usize; + let bytes_to_read = buf.len().min(4 - byte_offset); + + let dword = unsafe { self.pcie.read(addr, dword_offset) }; + let bytes = dword.to_le_bytes(); + buf[..bytes_to_read] + .copy_from_slice(&bytes[byte_offset..byte_offset + bytes_to_read]); + Ok(bytes_to_read) + } Handle::Channel { addr: _, ref mut st, @@ -193,7 +213,9 @@ impl SchemeSync for PciScheme { return Ok(buf); } Handle::Device => DEVICE_CONTENTS, - Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), + Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => { + return Err(Error::new(ENOTDIR)); + } Handle::SchemeRoot => return Err(Error::new(EBADF)), }; @@ -223,6 +245,20 @@ impl SchemeSync for PciScheme { } match handle.inner { + Handle::Config { addr } => { + let offset = _offset as u16; + let dword_offset = offset & !0x3; + let byte_offset = (offset & 0x3) as usize; + let bytes_to_write = buf.len().min(4 - byte_offset); + + let mut dword = unsafe { self.pcie.read(addr, dword_offset) }; + let mut bytes = dword.to_le_bytes(); + bytes[byte_offset..byte_offset + bytes_to_write] + .copy_from_slice(&buf[..bytes_to_write]); + dword = u32::from_le_bytes(bytes); + unsafe { self.pcie.write(addr, dword_offset, dword) }; + Ok(buf.len()) + } Handle::Channel { addr, ref mut st } => { Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) } @@ -316,6 +352,10 @@ impl SchemeSync for PciScheme { func.enabled = false; } } + Some(HandleWrapper { + inner: Handle::Config { .. }, + .. + }) => {} _ => {} } } @@ -341,6 +381,7 @@ impl PciScheme { let path = &after[1..]; match path { + "config" => Handle::Config { addr }, "channel" => { if func.enabled { return Err(Error::new(ENOLCK)); diff --git a/drivers/thermald/Cargo.toml b/drivers/thermald/Cargo.toml new file mode 100644 index 0000000000..65255ebaa0 --- /dev/null +++ b/drivers/thermald/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "thermald" +version = "0.1.0" +edition = "2021" + +[dependencies] +log.workspace = true +anyhow.workspace = true +common = { path = "../common" } + +[lints] +workspace = true diff --git a/drivers/thermald/src/main.rs b/drivers/thermald/src/main.rs new file mode 100644 index 0000000000..3db4dd55ba --- /dev/null +++ b/drivers/thermald/src/main.rs @@ -0,0 +1,30 @@ +use anyhow::{Context, Result}; +use std::{thread, time}; + +fn read_temp() -> Option { + for zone in 0..4 { + let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone); + if let Ok(data) = std::fs::read_to_string(&path) { + if let Ok(mv) = data.trim().parse::() { + return Some(mv as f32 / 1000.0); + } + } + } + None +} + +fn main() -> Result<()> { + common::setup_logging("system", "thermald", "thermald", + common::output_level(), common::file_level()); + log::info!("thermald: started"); + loop { + if let Some(temp) = read_temp() { + if temp > 85.0 { + log::error!("thermald: CRITICAL {:.1}C", temp); + } else if temp > 70.0 { + log::warn!("thermald: WARNING {:.1}C", temp); + } + } + thread::sleep(time::Duration::from_secs(5)); + } +} diff --git a/drivers/vboxd/src/main.rs b/drivers/vboxd/src/main.rs index bcb9bb157d..b9e42d4aa8 100644 --- a/drivers/vboxd/src/main.rs +++ b/drivers/vboxd/src/main.rs @@ -199,16 +199,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { let mut name = pci_config.func.name(); name.push_str("_vbox"); - let bar0 = pci_config.func.bars[0].expect_port(); + let bar0 = match pci_config.func.bars[0].try_port() { + Ok(port) => port, + Err(err) => { + eprintln!("vboxd: invalid BAR0: {err}"); + std::process::exit(1); + } + }; let irq = pci_config .func .legacy_interrupt_line - .expect("vboxd: no legacy interrupts supported"); + .unwrap_or_else(|| { + eprintln!("vboxd: no legacy interrupts supported"); + std::process::exit(1); + }); println!(" + VirtualBox {}", pci_config.func.display()); - common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission"); + if let Err(err) = common::acquire_port_io_rights() { + eprintln!("vboxd: failed to get I/O permission: {err}"); + std::process::exit(1); + } let mut width = 0; let mut height = 0; @@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let mut irq_file = irq.irq_handle("vboxd"); + let mut irq_file = match irq.try_irq_handle("vboxd") { + Ok(file) => file, + Err(err) => { + eprintln!("vboxd: failed to open IRQ handle: {err}"); + std::process::exit(1); + } + }; - let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr(); + let address = match unsafe { pcid_handle.try_map_bar(1) } { + Ok(bar) => bar.ptr.as_ptr(), + Err(err) => { + eprintln!("vboxd: failed to map BAR1: {err}"); + std::process::exit(1); + } + }; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { let mut port = common::io::Pio::::new(bar0 as u16); let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) }; - let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo"); + let mut guest_info = match VboxGuestInfo::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map GuestInfo: {err}"); + std::process::exit(1); + } + }; guest_info.version.write(VBOX_VMMDEV_VERSION); guest_info.ostype.write(0x100); port.write(guest_info.physical() as u32); - let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps"); + let mut guest_caps = match VboxGuestCaps::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map GuestCaps: {err}"); + std::process::exit(1); + } + }; guest_caps.caps.write(1 << 2); port.write(guest_caps.physical() as u32); - let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse"); + let mut set_mouse = match VboxSetMouse::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map SetMouse: {err}"); + std::process::exit(1); + } + }; set_mouse.features.write(1 << 4 | 1); port.write(set_mouse.physical() as u32); @@ -265,34 +307,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } - let event_queue = - EventQueue::::new().expect("vboxd: Could not create event queue."); + let event_queue = match EventQueue::::new() { + Ok(queue) => queue, + Err(err) => { + eprintln!("vboxd: could not create event queue: {err}"); + std::process::exit(1); + } + }; event_queue .subscribe( irq_file.as_raw_fd() as usize, Source::Irq, event::EventFlags::READ, ) - .unwrap(); + .unwrap_or_else(|err| { + eprintln!("vboxd: failed to subscribe IRQ fd: {err}"); + std::process::exit(1); + }); daemon.ready(); - libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace"); + if let Err(err) = libredox::call::setrens(0, 0) { + eprintln!("vboxd: failed to enter null namespace: {err}"); + std::process::exit(1); + } let mut bga = crate::bga::Bga::new(); - let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse"); - let display_change = VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange"); - let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents"); + let get_mouse = match VboxGetMouse::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map GetMouse: {err}"); + std::process::exit(1); + } + }; + let display_change = match VboxDisplayChange::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map DisplayChange: {err}"); + std::process::exit(1); + } + }; + let ack_events = match VboxAckEvents::new() { + Ok(value) => value, + Err(err) => { + eprintln!("vboxd: failed to map AckEvents: {err}"); + std::process::exit(1); + } + }; - for Source::Irq in iter::once(Source::Irq) - .chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data)) - { + for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e { + Ok(event) => event.user_data, + Err(err) => { + eprintln!("vboxd: failed to get next event: {err}"); + std::process::exit(1); + } + })) { let mut irq = [0; 8]; - if irq_file.read(&mut irq).unwrap() >= irq.len() { + match irq_file.read(&mut irq) { + Ok(read) if read >= irq.len() => { let host_events = vmmdev.host_events.read(); if host_events != 0 { port.write(ack_events.physical() as u32); - irq_file.write(&irq).unwrap(); + if let Err(err) = irq_file.write(&irq) { + eprintln!("vboxd: failed to acknowledge IRQ: {err}"); + std::process::exit(1); + } if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY { port.write(display_change.physical() as u32); @@ -326,8 +405,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { } } } + Ok(_) => {} + Err(err) => { + eprintln!("vboxd: failed to read IRQ file: {err}"); + std::process::exit(1); + } + } } } - std::process::exit(0); + std::process::exit(1); } diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs index aea86c4ad5..c5b2767f7d 100644 --- a/drivers/virtio-core/src/arch/x86.rs +++ b/drivers/virtio-core/src/arch/x86.rs @@ -11,7 +11,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { // Extended message signaled interrupts. let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { PciFeatureInfo::MsiX(capability) => capability, - _ => unreachable!(), + _ => { + log::warn!("virtio_core::enable_msix: expected MSI-X feature info"); + return Err(Error::Probe("unexpected PCI feature info for MSI-X")); + } }; let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; @@ -21,7 +24,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result { let interrupt_handle = { let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize); - let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed"); + let destination_id = read_bsp_apic_id().map_err(|e| { + log::warn!("virtio_core::enable_msix: read_bsp_apic_id failed: {e}"); + Error::Probe("read_bsp_apic_id failed") + })?; let (msg_addr_and_data, interrupt_handle) = allocate_single_interrupt_vector_for_msi(destination_id); table_entry_pointer.write_addr_and_data(msg_addr_and_data); diff --git a/drivers/virtio-core/src/probe.rs b/drivers/virtio-core/src/probe.rs index 5631ef676c..eaef1b96f4 100644 --- a/drivers/virtio-core/src/probe.rs +++ b/drivers/virtio-core/src/probe.rs @@ -31,16 +31,16 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0; /// before starting the device. /// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device /// is alive. -/// -/// ## Panics -/// This function panics if the device is not a virtio device. pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result { let pci_config = pcid_handle.config(); - assert_eq!( - pci_config.func.full_device_id.vendor_id, 6900, - "virtio_core::probe_device: not a virtio device" - ); + if pci_config.func.full_device_id.vendor_id != 6900 { + log::warn!( + "virtio_core::probe_device: skipping non-virtio device (vendor ID {:#06x})", + pci_config.func.full_device_id.vendor_id + ); + return Err(Error::Probe("not a virtio device")); + } let mut common_addr = None; let mut notify_addr = None; @@ -55,7 +55,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result continue, } - let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem(); + let (addr, _) = pci_config.func.bars[capability.bar as usize] + .try_mem() + .map_err(|_| Error::Probe("BAR is not memory-mapped"))?; let address = unsafe { let addr = addr + capability.offset as usize; @@ -100,19 +102,23 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result unreachable!(), + _ => continue, } } - let common_addr = common_addr.expect("virtio common capability missing"); - let device_addr = device_addr.expect("virtio device capability missing"); - let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing"); + let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?; + let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?; + let (notify_addr, notify_multiplier) = + notify_addr.ok_or(Error::InCapable(CfgType::Notify))?; - // FIXME this is explicitly allowed by the virtio specification to happen - assert!( - notify_multiplier != 0, - "virtio-core::device_probe: device uses the same Queue Notify addresses for all queues" - ); + // The virtio specification explicitly allows a zero notify_off_multiplier, + // meaning all queues share the same notification address. Handle gracefully. + if notify_multiplier == 0 { + log::warn!( + "virtio_core::probe_device: device uses the same Queue Notify address for all queues" + ); + return Err(Error::Probe("zero notify_off_multiplier")); + } let common = unsafe { &mut *(common_addr as *mut CommonCfg) }; let device_space = unsafe { &mut *(device_addr as *mut u8) }; @@ -128,8 +134,10 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result Vec { - let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain"); - last_buffer.flags.remove(DescriptorFlags::NEXT); - + if let Some(last_buffer) = self.buffers.last_mut() { + last_buffer.flags.remove(DescriptorFlags::NEXT); + } self.buffers } } diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs index d3445d2d81..99972c9553 100644 --- a/drivers/virtio-core/src/transport.rs +++ b/drivers/virtio-core/src/transport.rs @@ -19,6 +19,8 @@ pub enum Error { SyscallError(#[from] libredox::error::Error), #[error("the device is incapable of {0:?}")] InCapable(CfgType), + #[error("virtio probe: {0}")] + Probe(&'static str), } /// Returns the queue part sizes in bytes. @@ -59,14 +61,23 @@ pub fn spawn_irq_thread(irq_handle: &File, queue: &Arc>) { let queue_copy = queue.clone(); std::thread::spawn(move || { - let event_queue = RawEventQueue::new().unwrap(); + let event_queue = match RawEventQueue::new() { + Ok(eq) => eq, + Err(err) => { + log::error!("virtio-core: failed to create event queue for IRQ thread: {err}"); + return; + } + }; - event_queue - .subscribe(irq_fd as usize, 0, event::EventFlags::READ) - .unwrap(); + if let Err(err) = event_queue.subscribe(irq_fd as usize, 0, event::EventFlags::READ) { + log::error!("virtio-core: failed to subscribe to IRQ fd: {err}"); + return; + } - for _ in event_queue.map(Result::unwrap) { - // Wake up the tasks waiting on the queue. + for event_result in event_queue.map(|res| res) { + if event_result.is_err() { + break; + } for (_, task) in queue_copy.waker.lock().unwrap().iter() { task.wake_by_ref(); } @@ -604,7 +615,9 @@ impl Transport for StandardTransport<'_> { // Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise, // the device does not support our subset of features and the device is unusable. let confirm = common.device_status.get(); - assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK); + if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK { + log::error!("virtio-core: device rejected feature set (FEATURES_OK cleared after negotiation)"); + } } fn setup_config_notify(&self, vector: u16) { @@ -640,7 +653,10 @@ impl Transport for StandardTransport<'_> { // Set the MSI-X vector. common.queue_msix_vector.set(vector); - assert!(common.queue_msix_vector.get() == vector); + if common.queue_msix_vector.get() != vector { + log::error!("virtio-core: MSI-X vector {vector:#x} was not accepted by device for queue {queue_index}"); + return Err(Error::SyscallError(libredox::error::Error::new(libredox::errno::EIO))); + } // Enable the queue. common.queue_enable.set(1); @@ -685,7 +701,9 @@ impl Transport for StandardTransport<'_> { // Set the MSI-X vector. common.queue_msix_vector.set(queue.vector); - assert!(common.queue_msix_vector.get() == queue.vector); + if common.queue_msix_vector.get() != queue.vector { + log::error!("virtio-core: MSI-X vector {:#x} was not accepted during reinit for queue {}", queue.vector, queue.queue_index); + } // Enable the queue. common.queue_enable.set(1); diff --git a/init.d/05_boot_essential.target b/init.d/05_boot_essential.target new file mode 100644 index 0000000000..58a08491b7 --- /dev/null +++ b/init.d/05_boot_essential.target @@ -0,0 +1,3 @@ +[unit] +description = "Boot essential services target" +requires_weak = ["00_base.target"] diff --git a/init.d/12_boot_late.target b/init.d/12_boot_late.target new file mode 100644 index 0000000000..7ded5b1448 --- /dev/null +++ b/init.d/12_boot_late.target @@ -0,0 +1,3 @@ +[unit] +description = "Boot late stage target" +requires_weak = ["05_boot_essential.target"] diff --git a/init.d/12_dbus.service b/init.d/12_dbus.service new file mode 100644 index 0000000000..0c64eb1c31 --- /dev/null +++ b/init.d/12_dbus.service @@ -0,0 +1,8 @@ +[unit] +description = "D-Bus system bus" +requires_weak = ["12_boot_late.target", "00_ipcd.service"] + +[service] +cmd = "dbus-daemon" +args = ["--system", "--nopidfile"] +type = "oneshot_async" diff --git a/init.d/13_seatd.service b/init.d/13_seatd.service new file mode 100644 index 0000000000..d985278c29 --- /dev/null +++ b/init.d/13_seatd.service @@ -0,0 +1,8 @@ +[unit] +description = "seatd seat management" +requires_weak = ["12_dbus.service"] + +[service] +cmd = "/usr/bin/seatd" +args = ["-l", "info"] +type = "oneshot_async" diff --git a/init.d/29_activate_console.service b/init.d/29_activate_console.service new file mode 100644 index 0000000000..c5f227e924 --- /dev/null +++ b/init.d/29_activate_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Activate console VT" +requires_weak = ["00_base.target"] + +[service] +cmd = "inputd" +args = ["-A", "2"] +type = "oneshot_async" diff --git a/init.d/30_console.service b/init.d/30_console.service new file mode 100644 index 0000000000..49afdd2661 --- /dev/null +++ b/init.d/30_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Console getty on VT2" +requires_weak = ["29_activate_console.service"] + +[service] +cmd = "getty" +args = ["2"] +type = "oneshot_async" diff --git a/init.d/30_thermald.service b/init.d/30_thermald.service new file mode 100644 index 0000000000..4402500bb5 --- /dev/null +++ b/init.d/30_thermald.service @@ -0,0 +1,7 @@ +[unit] +description = "CPU Thermal Monitor" +requires_weak = ["00_base.target"] + +[service] +cmd = "thermald" +type = "oneshot_async" diff --git a/init.d/31_debug_console.service b/init.d/31_debug_console.service new file mode 100644 index 0000000000..a61bed9857 --- /dev/null +++ b/init.d/31_debug_console.service @@ -0,0 +1,8 @@ +[unit] +description = "Debug console on VT3" +requires_weak = ["29_activate_console.service"] + +[service] +cmd = "getty" +args = ["3"] +type = "oneshot_async" diff --git a/init.initfs.d/30_redox-drm.service b/init.initfs.d/30_redox-drm.service new file mode 100644 index 0000000000..ba380bf2ec --- /dev/null +++ b/init.initfs.d/30_redox-drm.service @@ -0,0 +1,8 @@ +[unit] +description = "DRM/KMS Display Driver" +requires_weak = ["20_graphics.target", "40_hwd.service", "40_pcid-spawner-initfs.service"] +condition_architecture = ["x86", "x86_64"] + +[service] +cmd = "redox-drm" +type = "notify" diff --git a/init.initfs.d/40_drivers.target b/init.initfs.d/40_drivers.target index 8ddb4795e8..061c2bedbf 100644 --- a/init.initfs.d/40_drivers.target +++ b/init.initfs.d/40_drivers.target @@ -3,8 +3,10 @@ description = "Initfs drivers" requires_weak = [ "10_lived.service", "20_graphics.target", + "40_pcid.service", "40_ps2d.service", "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 cba12dde61..ff0e76dcf4 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", "40_pcid.service", "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 6945b9eadc..2d36c9d512 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_pcid.service"] [service] cmd = "pcid-spawner" diff --git a/init.initfs.d/40_pcid.service b/init.initfs.d/40_pcid.service new file mode 100644 index 0000000000..6c3a83d8ee --- /dev/null +++ b/init.initfs.d/40_pcid.service @@ -0,0 +1,7 @@ +[unit] +description = "PCI daemon" +requires_weak = ["41_acpid.service"] + +[service] +cmd = "pcid" +type = "notify" diff --git a/init.initfs.d/41_acpid.service b/init.initfs.d/41_acpid.service new file mode 100644 index 0000000000..62d3da0d50 --- /dev/null +++ b/init.initfs.d/41_acpid.service @@ -0,0 +1,8 @@ +[unit] +description = "ACPI daemon" +default_dependencies = false + +[service] +cmd = "acpid" +inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"] +type = "notify" diff --git a/init.initfs.d/45_usbscsid.service b/init.initfs.d/45_usbscsid.service new file mode 100644 index 0000000000..1279def82d --- /dev/null +++ b/init.initfs.d/45_usbscsid.service @@ -0,0 +1,8 @@ +[unit] +description = "USB Mass Storage Driver" +requires_weak = ["40_drivers.target"] +condition_architecture = ["x86", "x86_64"] + +[service] +cmd = "usbscsid" +type = "notify" diff --git a/init/src/color.rs b/init/src/color.rs new file mode 100644 index 0000000000..db792c441f --- /dev/null +++ b/init/src/color.rs @@ -0,0 +1,27 @@ +pub fn status_ok(msg: &str) { + eprintln!("[ OK ] {msg}"); +} + +pub fn status_fail(msg: &str) { + eprintln!("[ FAILED ] {msg}"); +} + +pub fn status_skip(msg: &str) { + eprintln!("[ SKIP ] {msg}"); +} + +pub fn init_error(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_warn(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_info(msg: &str) { + eprintln!("init: {msg}"); +} + +pub fn init_debug(msg: &str) { + eprintln!("init: {msg}"); +} diff --git a/init/src/main.rs b/init/src/main.rs index ca73d4acb7..58f8812c51 100644 --- a/init/src/main.rs +++ b/init/src/main.rs @@ -8,11 +8,14 @@ use libredox::flag::{O_RDONLY, O_WRONLY}; use crate::scheduler::Scheduler; use crate::unit::{UnitId, UnitStore}; +mod color; mod scheduler; mod script; mod service; mod unit; +use crate::color::{init_error, init_warn, status_fail, status_ok}; + fn switch_stdio(stdio: &str) -> io::Result<()> { let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?; let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?; @@ -49,11 +52,7 @@ impl InitConfig { } fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Path, etcdir: &Path) { - eprintln!( - "init: switchroot to {} {}", - prefix.display(), - etcdir.display() - ); + status_ok(&format!("switchroot to {} {}", prefix.display(), etcdir.display())); config .envs @@ -79,10 +78,7 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat continue; } let Some((key, value)) = env.split_once('=') else { - eprintln!( - "init: failed to parse env line from {}: {env:?}", - file.display(), - ); + init_warn(&format!("failed to parse env line from {}: {:?}", file.display(), env)); continue; }; config @@ -91,23 +87,18 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat } } Err(err) => { - eprintln!( - "init: failed to read environment from {}: {err}", - file.display(), - ); + init_error(&format!("failed to read environment from {}: {}", file.display(), err)); } } } } Err(err) => { - eprintln!( - "init: failed to read environments from {}: {err}", + init_error(&format!("failed to read environments from {}: {}", env_dirs .iter() .map(|dir| dir.display().to_string()) .collect::>() - .join(", ") - ); + .join(", "), err)); } } } @@ -134,7 +125,7 @@ fn main() { .schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned())); scheduler.step(&mut unit_store, &mut init_config); if let Err(err) = switch_stdio("/scheme/log") { - eprintln!("init: failed to switch stdio to '/scheme/log': {err}"); + init_error(&format!("failed to switch stdio to '/scheme/log': {}", err)); } let runtime_target = UnitId("00_runtime.target".to_owned()); @@ -158,15 +149,13 @@ fn main() { let entries = match config::config_for_dirs(&unit_store.config_dirs) { Ok(entries) => entries, Err(err) => { - eprintln!( - "init: failed to read configs from {}: {err}", + init_error(&format!("failed to read configs from {}: {}", unit_store .config_dirs .iter() .map(|dir| dir.display().to_string()) .collect::>() - .join(", ") - ); + .join(", "), err)); return; } }; @@ -210,7 +199,15 @@ fn main() { } loop { + // Process any pending jobs whose dependencies may now be satisfied. + scheduler.step(&mut unit_store, &mut init_config); + + // Non-blocking wait: WNOHANG = 1. Reap any exited children without + // blocking, then loop back to process any newly-unblocked pending jobs. let mut status = 0; - libredox::call::waitpid(0, &mut status, 0).unwrap(); + match libredox::call::waitpid(0, &mut status, 1) { + Ok(_pid) => {} + Err(err) => init_error(&format!("waitpid error: {}", err)), + } } } diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs index aa5980e53d..9b483219c8 100644 --- a/init/src/scheduler.rs +++ b/init/src/scheduler.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, VecDeque}; use std::io::Write; use crate::InitConfig; +use crate::color::{init_debug, init_error, init_info, status_ok, status_skip}; use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; pub struct Scheduler { @@ -32,7 +33,7 @@ impl Scheduler { let mut errors = vec![]; self.schedule_start(unit_store, unit_id, &mut errors); for error in errors { - eprintln!("init: {error}"); + init_error(&format!("{}", error)); } } @@ -68,6 +69,7 @@ impl Scheduler { match job.kind { JobKind::Start => { let unit = unit_store.unit_mut(&job.unit); + eprintln!("init: DBG job={}", job.unit.0); let mut blocked = false; for dep in &unit.info.requires_weak { @@ -107,31 +109,28 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { UnitKind::LegacyScript { script } => { for cmd in script.clone() { if config.log_debug { - eprintln!("init: running: {cmd:?}"); + init_debug(&format!("running: {cmd:?}")); } cmd.run(config); } } UnitKind::Service { service } => { + let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0); if config.skip_cmd.contains(&service.cmd) { - eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); + status_skip(&format!("Skipping {} ({})", desc, service.cmd)); return; } if config.log_debug { - eprintln!( - "Starting {} ({})", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - service.cmd, - ); + init_info(&format!("Starting {} ({})", desc, service.cmd)); + } else { + status_ok(&format!("Started {}", desc)); } service.spawn(&config.envs); } UnitKind::Target {} => { if config.log_debug { - eprintln!( - "Reached target {}", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - ); + init_info(&format!("Reached target {}", + unit.info.description.as_ref().unwrap_or(&unit.id.0))); } } } diff --git a/init/src/service.rs b/init/src/service.rs index ed0023e921..895aee6281 100644 --- a/init/src/service.rs +++ b/init/src/service.rs @@ -8,6 +8,7 @@ use std::{env, io}; use serde::Deserialize; +use crate::color::{init_error, init_warn, status_fail}; use crate::script::subst_env; #[derive(Clone, Debug, Deserialize)] @@ -52,7 +53,7 @@ impl Service { let mut child = match command.spawn() { Ok(child) => child, Err(err) => { - eprintln!("init: failed to execute {:?}: {}", command, err); + status_fail(&format!("failed to execute {:?}: {}", command, err)); return; } }; @@ -61,10 +62,10 @@ impl Service { ServiceType::Notify => match read_pipe.read_exact(&mut [0]) { Ok(()) => {} Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => { - eprintln!("init: {command:?} exited without notifying readiness"); + init_warn(&format!("{:?} exited without notifying readiness", command)); } Err(err) => { - eprintln!("init: failed to wait for {command:?}: {err}"); + init_error(&format!("failed to wait for {:?}: {}", command, err)); } }, ServiceType::Scheme(scheme) => { @@ -80,16 +81,16 @@ impl Service { errno: syscall::EINTR, }) => continue, Ok(0) => { - eprintln!("init: {command:?} exited without notifying readiness"); + init_warn(&format!("{:?} exited without notifying readiness", command)); return; } Ok(1) => break, Ok(n) => { - eprintln!("init: incorrect amount of fds {n} returned"); + init_error(&format!("incorrect amount of fds {} returned", n)); return; } Err(err) => { - eprintln!("init: failed to wait for {command:?}: {err}"); + init_error(&format!("failed to wait for {:?}: {}", command, err)); return; } } @@ -104,11 +105,11 @@ impl Service { match child.wait() { Ok(exit_status) => { if !exit_status.success() { - eprintln!("init: {command:?} failed with {exit_status}"); + status_fail(&format!("{:?} failed with {}", command, exit_status)); } } Err(err) => { - eprintln!("init: failed to wait for {:?}: {}", command, err) + init_error(&format!("failed to wait for {:?}: {}", command, err)) } } } diff --git a/ipcd/src/uds/stream.rs b/ipcd/src/uds/stream.rs index fe46550fd6..fefd3f6a50 100644 --- a/ipcd/src/uds/stream.rs +++ b/ipcd/src/uds/stream.rs @@ -180,7 +180,7 @@ pub struct Socket { options: HashSet, flags: usize, state: State, - awaiting: VecDeque, + awaiting: VecDeque<(usize, ucred)>, connection: Option, issued_token: Option, ucred: ucred, @@ -245,6 +245,7 @@ impl Socket { &mut self, primary_id: usize, awaiting_client_id: usize, + client_ucred: ucred, ctx: &CallerCtx, ) -> Result { if !self.is_listening() { @@ -254,15 +255,17 @@ impl Socket { ); return Err(Error::new(EINVAL)); } - Ok(Self::new( + Ok(Self { primary_id, - self.path.clone(), - State::Established, - self.options.clone(), - self.flags, - Some(Connection::new(awaiting_client_id)), - ctx, - )) + path: self.path.clone(), + state: State::Established, + options: self.options.clone(), + flags: self.flags, + awaiting: VecDeque::new(), + connection: Some(Connection::new(awaiting_client_id)), + issued_token: None, + ucred: client_ucred, + }) } fn establish(&mut self, new_socket: &mut Self, peer: usize) -> Result<()> { @@ -290,7 +293,7 @@ impl Socket { Ok(()) } - fn connect(&mut self, other: &mut Socket) -> Result<()> { + fn connect(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> { match self.state { State::Unbound | State::Bound => { // If the socket is unbound or bound, wait for the listener to start listening. @@ -306,12 +309,12 @@ impl Socket { } _ => return Err(Error::new(ECONNREFUSED)), } - self.connect_unchecked(other); + self.connect_unchecked(other, client_ucred); Ok(()) } - fn connect_unchecked(&mut self, other: &mut Socket) { - self.awaiting.push_back(other.primary_id); + fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) { + self.awaiting.push_back((other.primary_id, client_ucred)); other.state = State::Connecting; other.connection = Some(Connection::new(self.primary_id)); } @@ -499,7 +502,7 @@ impl<'sock> UdsStreamScheme<'sock> { }; match verb { SocketCall::Bind => self.handle_bind(id, &payload), - SocketCall::Connect => self.handle_connect(id, &payload), + SocketCall::Connect => self.handle_connect(id, &payload, ctx), SocketCall::SetSockOpt => self.handle_setsockopt( id, *metadata.get(1).ok_or(Error::new(EINVAL))? as i32, @@ -592,7 +595,7 @@ impl<'sock> UdsStreamScheme<'sock> { // and changes its own state to `Established`. // // After these three phases, the socket connection is considered established. - fn handle_connect(&mut self, id: usize, token_buf: &[u8]) -> Result { + fn handle_connect(&mut self, id: usize, token_buf: &[u8], ctx: &CallerCtx) -> Result { let token = read_num::(token_buf)?; let (listener_id, connecting_res) = { let listener_rc = self @@ -637,7 +640,8 @@ impl<'sock> UdsStreamScheme<'sock> { } // Phase 2: listener is now listening - listener.connect(&mut client)?; + let client_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ }; + listener.connect(&mut client, client_ucred)?; (listener_id, connecting_res) }; @@ -886,6 +890,7 @@ impl<'sock> UdsStreamScheme<'sock> { &mut self, listener_socket: &mut Socket, client_id: usize, + client_ucred: ucred, ctx: &CallerCtx, ) -> Result> { let (new_id, new) = { @@ -893,7 +898,7 @@ impl<'sock> UdsStreamScheme<'sock> { return Ok(None); // Client socket has been closed, nothing to accept }; let new_id = self.next_id; - let mut new = listener_socket.accept(new_id, client_id, ctx)?; + let mut new = listener_socket.accept(new_id, client_id, client_ucred, ctx)?; let mut client_socket = client_rc.borrow_mut(); client_socket.establish(&mut new, listener_socket.primary_id)?; @@ -925,14 +930,14 @@ impl<'sock> UdsStreamScheme<'sock> { } loop { // Try to accept a waiting connection - let Some(client_id) = socket.awaiting.pop_front() else { + let Some((client_id, client_ucred)) = socket.awaiting.pop_front() else { if flags & O_NONBLOCK == O_NONBLOCK { return Err(Error::new(EAGAIN)); } else { return Err(Error::new(EWOULDBLOCK)); } }; - return match self.accept_connection(socket, client_id, ctx) { + return match self.accept_connection(socket, client_id, client_ucred, ctx) { Ok(conn) => Ok(conn), Err(Error { errno: EAGAIN }) => continue, Err(e) => Err(e), @@ -1004,7 +1009,8 @@ impl<'sock> UdsStreamScheme<'sock> { ); return Err(Error::new(EPIPE)); } - socket.connect_unchecked(&mut new); + let pair_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ }; + socket.connect_unchecked(&mut new, pair_ucred); } // smoltcp sends writeable whenever a listener gets a @@ -1199,7 +1205,7 @@ impl<'sock> UdsStreamScheme<'sock> { } // Notify all waiting clients about listener closure - for client_id in &socket.awaiting { + for (client_id, _) in &socket.awaiting { if let Ok(client_rc) = self.get_socket(*client_id) { { let mut client = client_rc.borrow_mut(); diff --git a/logd/src/scheme.rs b/logd/src/scheme.rs index 070de3d678..b342d2341e 100644 --- a/logd/src/scheme.rs +++ b/logd/src/scheme.rs @@ -41,14 +41,30 @@ impl<'sock> LogScheme<'sock> { let mut kernel_sys_log = std::fs::File::open("/scheme/sys/log").unwrap(); + let _ = std::fs::create_dir_all("/var/log"); + let persistent_log: Option = OpenOptions::new() + .create(true) + .append(true) + .open("/var/log/system.log") + .ok(); + let (output_tx, output_rx) = mpsc::channel::(); std::thread::spawn(move || { let mut files: Vec = vec![]; let mut logs = VecDeque::new(); + let mut persistent = persistent_log; + if let Some(ref mut f) = persistent { + let _ = f.write(b"--- logd started --- +"); + } for cmd in output_rx { match cmd { OutputCmd::Log(line) => { + if let Some(ref mut f) = persistent { + let _ = f.write(&line); + let _ = f.flush(); + } for file in &mut files { let _ = file.write(&line); let _ = file.flush(); diff --git a/net/e1000d/src/itr.rs b/net/e1000d/src/itr.rs new file mode 100644 index 0000000000..8d2e52bdfd --- /dev/null +++ b/net/e1000d/src/itr.rs @@ -0,0 +1,9 @@ +pub const ITR_LOW_LATENCY: u32 = 64; pub const ITR_BULK: u32 = 256; pub const ITR_DEFAULT: u32 = 800; +#[derive(Clone,Copy,PartialEq)] pub enum ItrState { LowLatency, Moderate, Bulk } +pub struct ItrTracker { state: ItrState, current_itr: u32, packets_since_update: u32 } +impl ItrTracker { pub const fn new() -> Self { Self { state: ItrState::LowLatency, current_itr: ITR_LOW_LATENCY, packets_since_update: 0 } } +pub fn record_packet(&mut self, bytes: usize) { self.packets_since_update += 1; let _ = bytes; } +pub fn update(&mut self) -> u32 { let new_state = if self.packets_since_update < 8 { ItrState::LowLatency } else if self.packets_since_update < 64 { ItrState::Moderate } else { ItrState::Bulk }; +if new_state != self.state { self.state = new_state; self.current_itr = match self.state { ItrState::LowLatency => ITR_LOW_LATENCY, ItrState::Moderate => ITR_DEFAULT, ItrState::Bulk => ITR_BULK }; } +self.packets_since_update = 0; self.current_itr } +pub fn current_itr(&self) -> u32 { self.current_itr } } diff --git a/net/rtl8168d/src/phy.rs b/net/rtl8168d/src/phy.rs new file mode 100644 index 0000000000..b2f1e0cd52 --- /dev/null +++ b/net/rtl8168d/src/phy.rs @@ -0,0 +1,5 @@ +#[derive(Clone,Copy,PartialEq,Debug)] pub enum ChipVersion { Rtl8168b, Rtl8168c, Rtl8168cp, Rtl8168d, Rtl8168dp, Rtl8168e, Rtl8168evl, Rtl8168f, Rtl8168g, Rtl8168h, Rtl8168ep, Unknown } +pub fn identify_chip(rev: u8, mac0: u32, _m1: u32, _m2: u32, _m3: u32, _m4: u32) -> ChipVersion { match ((mac0>>20)&0x7, rev) { (0,_)=>ChipVersion::Rtl8168b, (1,0x00..=0x01)=>ChipVersion::Rtl8168c, (1,0x02)=>ChipVersion::Rtl8168cp, (2,_)=>ChipVersion::Rtl8168d, (3,r) if r<=0x02=>ChipVersion::Rtl8168e, (3,_)=>ChipVersion::Rtl8168evl, (4,_)=>ChipVersion::Rtl8168f, (5,_)=>ChipVersion::Rtl8168g, (6,_)=>ChipVersion::Rtl8168h, (7,_)=>ChipVersion::Rtl8168ep, _=>ChipVersion::Unknown } } +pub mod phy_regs { pub const BMCR: u32 = 0x00; pub const BMSR: u32 = 0x01; pub const BMCR_RESET: u16 = 1<<15; pub const BMCR_AUTONEG_ENABLE: u16 = 1<<12; pub const BMSR_LINK_STATUS: u16 = 1<<2; } +pub fn phy_link_up(read: &dyn Fn(u32)->u16) -> bool { read(phy_regs::BMSR) & phy_regs::BMSR_LINK_STATUS != 0 } +pub fn phy_reset(write: &dyn Fn(u32,u16), read: &dyn Fn(u32)->u16) -> bool { write(phy_regs::BMCR, phy_regs::BMCR_RESET); for _ in 0..500 { if read(phy_regs::BMCR) & phy_regs::BMCR_RESET == 0 { return true; } } false } diff --git a/storage/ahcid/src/ahci/ncq.rs b/storage/ahcid/src/ahci/ncq.rs new file mode 100644 index 0000000000..474f11d0f8 --- /dev/null +++ b/storage/ahcid/src/ahci/ncq.rs @@ -0,0 +1,12 @@ +use core::sync::atomic::{AtomicU32, Ordering}; +pub const NCQ_MAX_DEPTH: usize = 32; +pub struct NcqState { pub sactive: AtomicU32, pub pending: AtomicU32 } +impl NcqState { pub const fn new() -> Self { Self { sactive: AtomicU32::new(0), pending: AtomicU32::new(0) } } +pub fn allocate_tag(&self) -> Option { let active = self.pending.load(Ordering::Acquire); let free = !active; if free == 0 { return None; } let tag = free.trailing_zeros(); let mask = 1u32 << tag; self.pending.fetch_or(mask, Ordering::AcqRel); self.sactive.fetch_or(mask, Ordering::AcqRel); Some(tag) } +pub fn complete_tag(&self, tag: u32) { let mask = 1u32 << tag; self.sactive.fetch_and(!mask, Ordering::AcqRel); self.pending.fetch_and(!mask, Ordering::AcqRel); } +pub fn has_pending(&self) -> bool { self.pending.load(Ordering::Acquire) != 0 } } +pub fn build_ncq_read_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = [0u32;5]; f[0]=0x0000_8027; f[1]=0x0060|((sector_count as u32&0xFF)<<24); f[2]=(lba as u32&0xFF)|(((lba>>8) as u32&0xFF)<<8); let mid=((lba>>16)as u32&0xFF)|((tag&0x1F)<<3); f[3]=mid|(((lba>>24)as u32&0xFF)<<8)|(((lba>>32)as u32&0xFF)<<16)|(((lba>>40)as u32&0xFF)<<24); f[4]=(((sector_count>>8)as u32&0xFF)<<16)|(((sector_count>>8)as u32&0xFF)<<24); f } +pub fn build_ncq_write_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = build_ncq_read_fis(tag,lba,sector_count); f[1]=(f[1]&!0xFF00)|0x6100; f } +pub fn process_ncq_completions(old_sactive: u32, new_sactive: u32, ncq: &NcqState, completed: &mut [u32;NCQ_MAX_DEPTH]) -> usize { let mask = old_sactive & !new_sactive; if mask == 0 { return 0; } let mut count = 0; let mut m = mask; while m != 0 { let tag = m.trailing_zeros(); ncq.complete_tag(tag); completed[count]=tag; count+=1; m&=m-1; } count } +pub fn drive_supports_ncq(id: &[u16;256]) -> bool { id.get(76).map_or(false, |w| w&(1<<8)!=0) } +pub fn ncq_queue_depth(id: &[u16;256]) -> u32 { id.get(75).map_or(1, |w| { let d = (w&0x1F) as u32; if d>0 {(d+1).min(NCQ_MAX_DEPTH as u32)} else {1} }) }