diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index 94a1eb17..3fd91156 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -25,6 +25,8 @@ use amlserde::{AmlSerde, AmlSerdeValue}; #[cfg(target_arch = "x86_64")] pub mod dmar; +#[cfg(target_arch = "x86_64")] +use self::dmar::Dmar; use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler}; /// The raw SDT header struct, as defined by the ACPI specification. @@ -379,6 +381,12 @@ pub struct AcpiContext { tables: Vec, dsdt: Option, fadt: Option, + pm1a_cnt_blk: u64, + pm1b_cnt_blk: u64, + slp_typa_s5: u8, + slp_typb_s5: u8, + reset_reg: Option, + reset_value: u8, aml_symbols: RwLock, @@ -424,6 +432,63 @@ 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); + } @@ while let Some((descriptor, len)) = AnyDescriptor::parse(&data[i..]) { descriptors.push(descriptor); i += len; } + let descriptor_count = descriptors.len(); + + if i < data.len() { + log::warn!( + "port {} slot {} config {} descriptor parsing stopped early at byte {} of {}", + port_id, + slot, + index, + i, + data.len() + ); + } + 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)>, @@ -444,6 +509,12 @@ impl AcpiContext { tables, dsdt: None, fadt: None, + pm1a_cnt_blk: 0, + pm1b_cnt_blk: 0, + slp_typa_s5: 0, + slp_typb_s5: 0, + reset_reg: None, + reset_value: 0, // Temporary values aml_symbols: RwLock::new(AmlSymbols::new(ec)), @@ -458,7 +529,10 @@ impl AcpiContext { } Fadt::init(&mut this); - //TODO (hangs on real hardware): Dmar::init(&this); + // DMAR (Intel VT-d) init — previously disabled due to iterator bug (type_bytes copied + // instead of len_bytes in DmarRawIter). Safe to call now: on AMD systems, no DMAR table + // exists and this returns early with a warning. + Dmar::init(&this); this } @@ -562,92 +636,83 @@ impl AcpiContext { aml_symbols.symbol_cache = FxHashMap::default(); } - /// Set Power State - /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf - /// - search for PM1a - /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details - pub fn set_global_s_state(&self, state: u8) { - if state != 5 { - return; - } - let fadt = match self.fadt() { - Some(fadt) => fadt, - None => { - log::error!("Cannot set global S-state due to missing FADT."); - return; - } - }; - - let port = fadt.pm1a_control_block as u16; - let mut val = 1 << 13; - - let aml_symbols = self.aml_symbols.read(); + pub fn acpi_shutdown(&self) { + let pm1a_value = (u16::from(self.slp_typa_s5) << 10) | 0x2000; + let pm1b_value = (u16::from(self.slp_typb_s5) << 10) | 0x2000; - let s5_aml_name = match acpi::aml::namespace::AmlName::from_str("\\_S5") { - Ok(aml_name) => aml_name, - Err(error) => { - log::error!("Could not build AmlName for \\_S5, {:?}", error); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + let Ok(pm1a_port) = u16::try_from(self.pm1a_cnt_blk) else { + log::error!("PM1a_CNT_BLK address is invalid: {:#X}", self.pm1a_cnt_blk); return; - } - }; + }; - let s5 = match &aml_symbols.aml_context { - Some(aml_context) => match aml_context.namespace.lock().get(s5_aml_name) { - Ok(s5) => s5, - Err(error) => { - log::error!("Cannot set S-state, missing \\_S5, {:?}", error); - return; + log::warn!( + "Shutdown with ACPI PM1a_CNT outw(0x{:X}, 0x{:X})", + pm1a_port, + pm1a_value + ); + Pio::::new(pm1a_port).write(pm1a_value); + + if self.pm1b_cnt_blk != 0 { + match u16::try_from(self.pm1b_cnt_blk) { + Ok(pm1b_port) => { + log::warn!( + "Shutdown with ACPI PM1b_CNT outw(0x{:X}, 0x{:X})", + pm1b_port, + pm1b_value + ); + Pio::::new(pm1b_port).write(pm1b_value); + } + Err(_) => { + log::error!("PM1b_CNT_BLK address is invalid: {:#X}", self.pm1b_cnt_blk); + } } - }, - None => { - log::error!("Cannot set S-state, AML context not initialized"); - return; } - }; + } - let package = match s5.deref() { - acpi::aml::object::Object::Package(package) => package, - _ => { - log::error!("Cannot set S-state, \\_S5 is not a package"); - return; - } - }; + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + { + log::error!( + "Cannot shutdown with ACPI PM1_CNT writes on this architecture (PM1a={:#X}, PM1b={:#X})", + self.pm1a_cnt_blk, + self.pm1b_cnt_blk + ); + } + } - let slp_typa = match package[0].deref() { - acpi::aml::object::Object::Integer(i) => i.to_owned(), - _ => { - log::error!("typa is not an Integer"); - return; + pub fn acpi_reboot(&self) { + match self.reset_reg { + Some(reset_reg) => { + log::warn!( + "Reboot with ACPI reset register {:?} value {:#X}", + reset_reg, + self.reset_value + ); + reset_reg.write_u8(self.reset_value); } - }; - let slp_typb = match package[1].deref() { - acpi::aml::object::Object::Integer(i) => i.to_owned(), - _ => { - log::error!("typb is not an Integer"); - return; + None => { + log::error!("Cannot reboot with ACPI: no reset register present in FADT"); } - }; - - log::trace!("Shutdown SLP_TYPa {:X}, SLP_TYPb {:X}", slp_typa, slp_typb); - val |= slp_typa as u16; - - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - { - log::warn!("Shutdown with ACPI outw(0x{:X}, 0x{:X})", port, val); - Pio::::new(port).write(val); } + } - // TODO: Handle SLP_TYPb + /// Set Power State + /// See https://uefi.org/sites/default/files/resources/ACPI_6_1.pdf + /// - search for PM1a + /// See https://forum.osdev.org/viewtopic.php?t=16990 for practical details + pub fn set_global_s_state(&self, state: u8) { + if state != 5 { + return; + } - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - { - log::error!( - "Cannot shutdown with ACPI outw(0x{:X}, 0x{:X}) on this architecture", - port, - val - ); + if self.fadt().is_none() { + log::error!("Cannot set global S-state due to missing FADT."); + return; } + self.acpi_shutdown(); + loop { core::hint::spin_loop(); } @@ -707,7 +772,7 @@ unsafe impl plain::Plain for FadtStruct {} #[repr(C, packed)] #[derive(Clone, Copy, Debug, Default)] -pub struct GenericAddressStructure { +pub struct GenericAddress { address_space: u8, bit_width: u8, bit_offset: u8, @@ -715,11 +780,77 @@ pub struct GenericAddressStructure { address: u64, } diff --git a/netstack/src/scheme/icmp.rs b/netstack/src/scheme/icmp.rs index 6365e2a7..b391bae1 100644 --- a/netstack/src/scheme/icmp.rs +++ b/netstack/src/scheme/icmp.rs @@ -3,7 +3,7 @@ use smoltcp::socket::icmp::{ Endpoint as IcmpEndpoint, PacketBuffer as IcmpSocketBuffer, PacketMetadata as IcmpPacketMetadata, Socket as IcmpSocket, }; -use smoltcp::wire::{Icmpv4Packet, Icmpv4Repr, IpAddress, IpListenEndpoint}; +use smoltcp::wire::{Icmpv4Packet, Icmpv4Repr, IpAddress, IpListenEndpoint, IpProtocol, UdpPacket}; use std::mem; use std::str; use syscall; @@ -16,6 +16,10 @@ use crate::router::Router; pub type IcmpScheme = SchemeWrapper>; +const ICMP_UDP_TRACE_EVENT_LEN: usize = 12; +const ICMP_UDP_TRACE_KIND_TIME_EXCEEDED: u8 = 1; +const ICMP_UDP_TRACE_KIND_DST_UNREACHABLE: u8 = 2; + enum IcmpSocketType { Echo, Udp, @@ -27,6 +31,79 @@ pub struct IcmpData { ident: u16, } +fn encode_udp_trace_event( + kind: u8, + code: u8, + responder: std::net::Ipv4Addr, + source_port: u16, + dest_port: u16, + buf: &mut [u8], +) -> SyscallResult { + if buf.len() < ICMP_UDP_TRACE_EVENT_LEN { + return Err(SyscallError::new(syscall::EINVAL)); + } + + buf[0] = kind; + buf[1] = code; + buf[2..4].fill(0); + buf[4..8].copy_from_slice(&responder.octets()); + buf[8..10].copy_from_slice(&source_port.to_be_bytes()); + buf[10..12].copy_from_slice(&dest_port.to_be_bytes()); + Ok(ICMP_UDP_TRACE_EVENT_LEN) +} + +fn build_udp_trace_event( + icmp_repr: &Icmpv4Repr<'_>, + responder_ip: IpAddress, + remote_ip: IpAddress, + local_port: u16, + buf: &mut [u8], +) -> Option> { + let (kind, code, header, data) = match icmp_repr { + Icmpv4Repr::TimeExceeded { + reason, + header, + data, + } => ( + ICMP_UDP_TRACE_KIND_TIME_EXCEEDED, + u8::from(*reason), + header, + *data, + ), + Icmpv4Repr::DstUnreachable { + reason, + header, + data, + } => ( + ICMP_UDP_TRACE_KIND_DST_UNREACHABLE, + u8::from(*reason), + header, + *data, + ), + _ => return None, + }; + + if header.next_header != IpProtocol::Udp || remote_ip != IpAddress::Ipv4(header.dst_addr) { + return None; + } + + let udp_packet = UdpPacket::new_checked(data).ok()?; + if udp_packet.src_port() != local_port { + return None; + } + + let IpAddress::Ipv4(responder) = responder_ip; + + Some(encode_udp_trace_event( + kind, + code, + responder, + udp_packet.src_port(), + udp_packet.dst_port(), + buf, + )) +} + impl<'a> SchemeSocket for IcmpSocket<'a> { type SchemeDataT = PortSet; type DataT = IcmpData; @@ -126,6 +203,21 @@ impl<'a> SchemeSocket for IcmpSocket<'a> { let ip = IpAddress::from_str(addr).map_err(|_| syscall::Error::new(syscall::EINVAL))?; + let ident = match parts.next() { + Some(port) if !port.is_empty() => { + let port = port + .parse::() + .map_err(|_| syscall::Error::new(syscall::EINVAL))?; + if !ident_set.claim_port(port) { + return Err(SyscallError::new(syscall::EADDRINUSE)); + } + port + } + Some(_) | None => ident_set + .get_port() + .ok_or_else(|| SyscallError::new(syscall::EINVAL))?, + }; + let socket = IcmpSocket::new( IcmpSocketBuffer::new( vec![IcmpPacketMetadata::EMPTY; Smolnetd::SOCKET_BUFFER_SIZE], @@ -138,9 +230,6 @@ impl<'a> SchemeSocket for IcmpSocket<'a> { ); let handle = socket_set.add(socket); let icmp_socket = socket_set.get_mut::(handle); - let ident = ident_set - .get_port() - .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; icmp_socket .bind(IcmpEndpoint::Udp(IpListenEndpoint::from(ident))) .map_err(|_| syscall::Error::new(syscall::EINVAL))?; @@ -213,22 +302,39 @@ impl<'a> SchemeSocket for IcmpSocket<'a> { return Ok(0); } while self.can_recv(&file.data) { - let (payload, _) = self.recv().expect("Can't recv icmp packet"); + let (payload, responder_ip) = self.recv().expect("Can't recv icmp packet"); let icmp_packet = Icmpv4Packet::new_unchecked(&payload); //TODO: replace default with actual caps - let icmp_repr = Icmpv4Repr::parse(&icmp_packet, &Default::default()).unwrap(); + let Ok(icmp_repr) = Icmpv4Repr::parse(&icmp_packet, &Default::default()) else { + continue; + }; - if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr { - if buf.len() < mem::size_of::() + data.len() { - return Err(SyscallError::new(syscall::EINVAL)); - } - buf[0..2].copy_from_slice(&seq_no.to_be_bytes()); + match file.data.socket_type { + IcmpSocketType::Echo => { + if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr { + if buf.len() < mem::size_of::() + data.len() { + return Err(SyscallError::new(syscall::EINVAL)); + } + buf[0..2].copy_from_slice(&seq_no.to_be_bytes()); - for i in 0..data.len() { - buf[mem::size_of::() + i] = data[i]; - } + for i in 0..data.len() { + buf[mem::size_of::() + i] = data[i]; + } - return Ok(mem::size_of::() + data.len()); + return Ok(mem::size_of::() + data.len()); + } + } + IcmpSocketType::Udp => { + if let Some(result) = build_udp_trace_event( + &icmp_repr, + responder_ip, + file.data.ip, + file.data.ident, + buf, + ) { + return result; + } + } } } @@ -264,7 +370,10 @@ impl<'a> SchemeSocket for IcmpSocket<'a> { Ok(i) } IcmpSocketType::Udp => { - let path = format!("/scheme/icmp/udp/{}", socket_file.data.ip); + let path = format!( + "/scheme/icmp/udp/{}/{}", + socket_file.data.ip, socket_file.data.ident + ); let path = path.as_bytes(); let mut i = 0; @@ -316,3 +425,123 @@ impl<'a> SchemeSocket for IcmpSocket<'a> { Ok(0) } } + +#[cfg(test)] +mod tests { + use super::{ + build_udp_trace_event, ICMP_UDP_TRACE_KIND_DST_UNREACHABLE, + ICMP_UDP_TRACE_KIND_TIME_EXCEEDED, + }; + use smoltcp::wire::{ + Icmpv4DstUnreachable, Icmpv4Repr, Icmpv4TimeExceeded, IpAddress, IpProtocol, Ipv4Address, + Ipv4Repr, + }; + + fn udp_header(source_port: u16, dest_port: u16) -> [u8; 8] { + let mut header = [0_u8; 8]; + header[0..2].copy_from_slice(&source_port.to_be_bytes()); + header[2..4].copy_from_slice(&dest_port.to_be_bytes()); + header[4..6].copy_from_slice(&(8_u16).to_be_bytes()); + header + } + + #[test] + fn emits_time_exceeded_trace_event_for_matching_udp_probe() { + let remote = Ipv4Address::new(203, 0, 113, 9); + let responder = Ipv4Address::new(192, 0, 2, 1); + let local_port = 42_000; + let dest_port = 33_434; + let udp = udp_header(local_port, dest_port); + let icmp_repr = Icmpv4Repr::TimeExceeded { + reason: Icmpv4TimeExceeded::TtlExpired, + header: Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 2, 15), + dst_addr: remote, + next_header: IpProtocol::Udp, + payload_len: udp.len(), + hop_limit: 1, + }, + data: &udp, + }; + let mut encoded = [0_u8; 12]; + + let written = build_udp_trace_event( + &icmp_repr, + IpAddress::Ipv4(responder), + IpAddress::Ipv4(remote), + local_port, + &mut encoded, + ) + .expect("expected traceroute event") + .expect("expected successful encoding"); + + assert_eq!(written, encoded.len()); + assert_eq!(encoded[0], ICMP_UDP_TRACE_KIND_TIME_EXCEEDED); + assert_eq!(encoded[1], u8::from(Icmpv4TimeExceeded::TtlExpired)); + assert_eq!(&encoded[4..8], &responder.octets()); + assert_eq!(u16::from_be_bytes([encoded[8], encoded[9]]), local_port); + assert_eq!(u16::from_be_bytes([encoded[10], encoded[11]]), dest_port); + } + + #[test] + fn ignores_udp_trace_event_for_wrong_destination() { + let remote = Ipv4Address::new(203, 0, 113, 9); + let other_remote = Ipv4Address::new(203, 0, 113, 19); + let udp = udp_header(42_000, 33_434); + let icmp_repr = Icmpv4Repr::TimeExceeded { + reason: Icmpv4TimeExceeded::TtlExpired, + header: Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 2, 15), + dst_addr: remote, + next_header: IpProtocol::Udp, + payload_len: udp.len(), + hop_limit: 1, + }, + data: &udp, + }; + + assert!(build_udp_trace_event( + &icmp_repr, + IpAddress::Ipv4(Ipv4Address::new(192, 0, 2, 1)), + IpAddress::Ipv4(other_remote), + 42_000, + &mut [0_u8; 12], + ) + .is_none()); + } + + #[test] + fn emits_destination_unreachable_trace_event() { + let remote = Ipv4Address::new(203, 0, 113, 9); + let responder = Ipv4Address::new(203, 0, 113, 9); + let local_port = 42_000; + let dest_port = 33_450; + let udp = udp_header(local_port, dest_port); + let icmp_repr = Icmpv4Repr::DstUnreachable { + reason: Icmpv4DstUnreachable::PortUnreachable, + header: Ipv4Repr { + src_addr: Ipv4Address::new(10, 0, 2, 15), + dst_addr: remote, + next_header: IpProtocol::Udp, + payload_len: udp.len(), + hop_limit: 64, + }, + data: &udp, + }; + let mut encoded = [0_u8; 12]; + + let written = build_udp_trace_event( + &icmp_repr, + IpAddress::Ipv4(responder), + IpAddress::Ipv4(remote), + local_port, + &mut encoded, + ) + .expect("expected traceroute event") + .expect("expected successful encoding"); + + assert_eq!(written, encoded.len()); + assert_eq!(encoded[0], ICMP_UDP_TRACE_KIND_DST_UNREACHABLE); + assert_eq!(encoded[1], u8::from(Icmpv4DstUnreachable::PortUnreachable)); + } +} +impl GenericAddress { + pub fn is_empty(&self) -> bool { + self.address == 0 + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + pub fn write_u8(&self, value: u8) { + match self.address_space { + 0 => { + let address_val = self.address; + let Ok(address) = usize::try_from(address_val) else { + log::error!( + "Reset register physical address is invalid: {:#X}", + address_val + ); + return; + }; + let page = address / PAGE_SIZE * PAGE_SIZE; + let offset = address % PAGE_SIZE; + let virt = unsafe { + common::physmap( + page, + PAGE_SIZE, + common::Prot::RW, + common::MemoryType::default(), + ) + }; + + match virt { + Ok(virt) => unsafe { + (virt as *mut u8).add(offset).write_volatile(value); + let _ = libredox::call::munmap(virt, PAGE_SIZE); + }, + Err(error) => { + log::error!("Failed to map ACPI reset register: {}", error); + } + } + } + 1 => match u16::try_from(self.address) { + Ok(port) => { + Pio::::new(port).write(value); + } + Err(_) => { + let address_val = self.address; + log::error!("Reset register I/O port is invalid: {:#X}", address_val); + } + }, + address_space => { + log::warn!( + "Unsupported ACPI reset register address space {} for {:?}", + address_space, + self + ); + } + } + } + + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + pub fn write_u8(&self, _value: u8) { + log::error!( + "Cannot access ACPI reset register {:?} on this architecture", + self + ); + } +} + #[repr(C, packed)] #[derive(Clone, Copy, Debug)] pub struct FadtAcpi2Struct { // 12 byte structure; see below for details - pub reset_reg: GenericAddressStructure, + pub reset_reg: GenericAddress, pub reset_value: u8, reserved3: [u8; 3], @@ -728,14 +859,14 @@ pub struct FadtAcpi2Struct { pub x_firmware_control: u64, pub x_dsdt: u64, - pub x_pm1a_event_block: GenericAddressStructure, - pub x_pm1b_event_block: GenericAddressStructure, - pub x_pm1a_control_block: GenericAddressStructure, - pub x_pm1b_control_block: GenericAddressStructure, - pub x_pm2_control_block: GenericAddressStructure, - pub x_pm_timer_block: GenericAddressStructure, - pub x_gpe0_block: GenericAddressStructure, - pub x_gpe1_block: GenericAddressStructure, + pub x_pm1a_event_block: GenericAddress, + pub x_pm1b_event_block: GenericAddress, + pub x_pm1a_control_block: GenericAddress, + pub x_pm1b_control_block: GenericAddress, + pub x_pm2_control_block: GenericAddress, + pub x_pm_timer_block: GenericAddress, + pub x_gpe0_block: GenericAddress, + pub x_gpe1_block: GenericAddress, } unsafe impl plain::Plain for FadtAcpi2Struct {} @@ -793,9 +924,27 @@ impl Fadt { None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"), }; - log::debug!("FACP at {:X}", { dsdt_ptr }); + let pm1a_evt_blk = u64::from(fadt.pm1a_event_block); + let pm1b_evt_blk = u64::from(fadt.pm1b_event_block); + let pm1a_cnt_blk = u64::from(fadt.pm1a_control_block); + let pm1b_cnt_blk = u64::from(fadt.pm1b_control_block); + let (reset_reg, reset_value) = match fadt.acpi_2_struct() { + Some(fadt2) if !fadt2.reset_reg.is_empty() => { + (Some(fadt2.reset_reg), fadt2.reset_value) + } + _ => (None, 0), + }; - let dsdt_sdt = match Sdt::load_from_physical(fadt.dsdt as usize) { + log::debug!("FACP at {:X}", { dsdt_ptr }); + log::debug!( + "FADT power blocks: PM1a_EVT={:#X}, PM1b_EVT={:#X}, PM1a_CNT={:#X}, PM1b_CNT={:#X}", + pm1a_evt_blk, + pm1b_evt_blk, + pm1a_cnt_blk, + pm1b_cnt_blk + ); + + let dsdt_sdt = match Sdt::load_from_physical(dsdt_ptr) { Ok(dsdt) => dsdt, Err(error) => { log::error!("Failed to load DSDT: {}", error); @@ -803,8 +952,48 @@ impl Fadt { } }; + let (slp_typa_s5, slp_typb_s5) = match AmlName::from_str("\\_S5") { + Ok(s5_name) => match context.aml_eval(s5_name, Vec::new()) { + Ok(AmlSerdeValue::Package { contents }) => match (contents.get(0), contents.get(1)) + { + ( + Some(AmlSerdeValue::Integer(slp_typa)), + Some(AmlSerdeValue::Integer(slp_typb)), + ) => match (u8::try_from(*slp_typa), u8::try_from(*slp_typb)) { + (Ok(slp_typa_s5), Ok(slp_typb_s5)) => (slp_typa_s5, slp_typb_s5), + _ => { + log::warn!("\\_S5 values do not fit in u8: {:?}", contents); + (0, 0) + } + }, + _ => { + log::warn!("\\_S5 package did not contain two integers: {:?}", contents); + (0, 0) + } + }, + Ok(value) => { + log::warn!("\\_S5 returned unexpected AML value: {:?}", value); + (0, 0) + } + Err(error) => { + log::warn!("Failed to evaluate \\_S5: {:?}", error); + (0, 0) + } + }, + Err(error) => { + log::warn!("Could not build AmlName for \\_S5: {:?}", error); + (0, 0) + } + }; + context.fadt = Some(fadt.clone()); context.dsdt = Some(Dsdt(dsdt_sdt.clone())); + context.pm1a_cnt_blk = pm1a_cnt_blk; + context.pm1b_cnt_blk = pm1b_cnt_blk; + context.slp_typa_s5 = slp_typa_s5; + context.slp_typb_s5 = slp_typb_s5; + context.reset_reg = reset_reg; + context.reset_value = reset_value; context.tables.push(dsdt_sdt); } diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index c42b379a..3024f58e 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -471,13 +471,17 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> { let type_bytes = <[u8; 2]>::try_from(type_bytes) .expect("expected a 2-byte slice to be convertible to [u8; 2]"); - let len_bytes = <[u8; 2]>::try_from(type_bytes) + let len_bytes = <[u8; 2]>::try_from(len_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; - let len = usize::try_from(len).expect("expected u16 to fit within usize"); + // Validate minimum entry header size and prevent infinite loops + if len < 4 || len > self.bytes.len() { + return None; + } + + let ty = u16::from_ne_bytes(type_bytes); if len > remainder.len() { log::warn!("DMAR remapping structure length was smaller than the remaining length of the table."); diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs index 0933f638..d4b0f3d0 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs @@ -7,11 +7,14 @@ use std::sync::Arc; use ::acpi::aml::op_region::{RegionHandler, RegionSpace}; use event::{EventFlags, RawEventQueue}; use redox_scheme::{ - scheme::{register_sync_scheme, SchemeState, SchemeSync}, + scheme::{register_sync_scheme, Op, SchemeState, SchemeSync}, RequestKind, Response, SignalBehavior, Socket, }; use syscall::{EAGAIN, EWOULDBLOCK}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use common::io::{Io, Pio}; + mod acpi; mod aml_physmem; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -103,6 +106,7 @@ fn daemon(daemon: daemon::Daemon) -> ! { libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace"); let mut mounted = true; + let mut reboot_requested = false; while mounted { let Some(event) = event_queue .next() @@ -130,7 +134,34 @@ fn daemon(daemon: daemon::Daemon) -> ! { }; match req.kind() { RequestKind::Call(call) => { - let response = call.handle_sync(&mut scheme, &mut state); + let caller = call.caller(); + let op = match call.op() { + Ok(op) => op, + Err(call) => { + let response = Response::new( + Err(syscall::Error::new(syscall::ENOSYS)), + call, + ); + socket + .write_response(response, SignalBehavior::Restart) + .expect("acpid: failed to write response"); + continue; + } + }; + + if let Op::OpenAt(openat) = &op { + if openat.path().contains("reboot") { + log::info!( + "Received reboot request from acpi scheme path: {}", + openat.path() + ); + reboot_requested = true; + mounted = false; + break; + } + } + + let response = op.handle_sync(caller, &mut scheme, &mut state); socket .write_response(response, SignalBehavior::Restart) .expect("acpid: failed to write response"); @@ -162,9 +193,19 @@ fn daemon(daemon: daemon::Daemon) -> ! { drop(shutdown_pipe); drop(event_queue); - acpi_context.set_global_s_state(5); + if reboot_requested { + acpi_context.acpi_reboot(); + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + { + log::warn!("Falling back to keyboard controller reset."); + Pio::::new(0x64).write(0xFE); + } + } else { + acpi_context.set_global_s_state(5); + } - unreachable!("System should have shut down before this is entered"); + unreachable!("System should have shut down or rebooted before this is entered"); } fn main() { diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index c2caf804..95acdb57 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) } @@ -318,6 +354,10 @@ impl PciScheme { func.enabled = false; } } + Some(HandleWrapper { + inner: Handle::Config { .. }, + .. + }) => {} _ => {} } } @@ -343,6 +383,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/init/src/scheduler.rs b/init/src/scheduler.rs index 670a5526..24ce3d68 100644 --- a/init/src/scheduler.rs +++ b/init/src/scheduler.rs @@ -1,8 +1,8 @@ use std::collections::VecDeque; -use crate::InitConfig; use crate::script::Command; use crate::unit::{Unit, UnitId, UnitKind, UnitStore}; +use crate::InitConfig; pub struct Scheduler { pending: VecDeque, @@ -96,14 +96,9 @@ fn run(unit: &mut Unit, config: &mut InitConfig) { eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" ")); return; } - if config.log_debug { - eprintln!( - "Starting {} ({})", - unit.info.description.as_ref().unwrap_or(&unit.id.0), - service.cmd, - ); - } + eprintln!("init: starting {} ({:?})", service.cmd, &service.type_,); service.spawn(&config.envs); + eprintln!("init: started {} done", service.cmd,); } UnitKind::Target {} => { if config.log_debug { diff --git a/drivers/storage/usbscsid/src/main.rs b/drivers/storage/usbscsid/src/main.rs --- a/drivers/storage/usbscsid/src/main.rs +++ b/drivers/storage/usbscsid/src/main.rs @@ fn main() { daemon::Daemon::new(daemon); } fn daemon(daemon: daemon::Daemon) -> ! { - let mut args = env::args().skip(1); + if let Err(err) = run(daemon) { + eprintln!("usbscsid: startup failed: {err}"); + std::process::exit(1); + } + + std::process::exit(0); +} + +fn run(daemon: daemon::Daemon) -> Result<(), String> { + let mut args = env::args().skip(1); const USAGE: &'static str = "usbscsid "; - let scheme = args.next().expect(USAGE); - let port = args - .next() - .expect(USAGE) - .parse::() - .expect("Expected port ID"); - let protocol = args - .next() - .expect(USAGE) - .parse::() - .expect("protocol has to be a number 0-255"); + let scheme = args.next().ok_or_else(|| USAGE.to_string())?; + let port_arg = args.next().ok_or_else(|| USAGE.to_string())?; + let protocol_arg = args.next().ok_or_else(|| USAGE.to_string())?; + let port = port_arg + .parse::() + .map_err(|err| format!("invalid port id `{port_arg}`: {err}"))?; + let protocol = protocol_arg + .parse::() + .map_err(|err| format!("invalid protocol `{protocol_arg}`: {err}"))?; @@ - let handle = - XhciClientHandle::new(scheme.to_owned(), port).expect("Failed to open XhciClientHandle"); + let handle = XhciClientHandle::new(scheme.to_owned(), port) + .map_err(|err| format!("failed to open XHCI client handle: {err}"))?; let desc = handle .get_standard_descs() - .expect("Failed to get standard descriptors"); + .map_err(|err| format!("failed to get standard descriptors: {err}"))?; @@ - .expect("Failed to find suitable configuration"); + .ok_or_else(|| "failed to find suitable BOT configuration".to_string())?; @@ - .expect("Failed to configure endpoints"); + .map_err(|err| format!("failed to configure endpoints: {err}"))?; - let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) - .expect("Failed to setup protocol"); + let mut protocol = protocol::setup(&handle, protocol, &desc, &conf_desc, &if_desc) + .map_err(|err| format!("failed to setup protocol: {err}"))?; @@ - let mut scsi = Scsi::new(&mut *protocol).expect("usbscsid: failed to setup SCSI"); + let mut scsi = + Scsi::new(&mut *protocol).map_err(|err| format!("failed to setup SCSI: {err}"))?; println!("SCSI initialized"); - let mut buffer = [0u8; 512]; - scsi.read(&mut *protocol, 0, &mut buffer).unwrap(); - println!("DISK CONTENT: {}", base64::encode(&buffer[..])); - let event_queue = event::EventQueue::new().unwrap(); + let event_queue = + event::EventQueue::new().map_err(|err| format!("failed to create event queue: {err}"))?; @@ - .unwrap(); + .map_err(|err| format!("failed to subscribe scheme event handle: {err}"))?; for event in event_queue { - match event.unwrap().user_data { - Event::Scheme => driver_block::FuturesExecutor - .block_on(scheme.tick()) - .unwrap(), + let event = match event { + Ok(event) => event, + Err(err) => { + eprintln!("usbscsid: event queue error: {err}"); + continue; + } + }; + + match event.user_data { + Event::Scheme => { + if let Err(err) = driver_block::FuturesExecutor.block_on(scheme.tick()) { + eprintln!("usbscsid: scheme tick failed: {err}"); + } + } } } - std::process::exit(0); + Ok(()) } diff --git a/drivers/storage/usbscsid/src/protocol/mod.rs b/drivers/storage/usbscsid/src/protocol/mod.rs --- a/drivers/storage/usbscsid/src/protocol/mod.rs +++ b/drivers/storage/usbscsid/src/protocol/mod.rs @@ #[error("attempted recovery failed")] RecoveryFailed, + #[error("unsupported USB mass-storage protocol 0x{0:02X}")] + UnsupportedProtocol(u8), + #[error("protocol error")] ProtocolError(&'static str), } @@ pub fn setup<'a>( handle: &'a XhciClientHandle, protocol: u8, - dev_desc: &DevDesc, + _dev_desc: &DevDesc, conf_desc: &ConfDesc, if_desc: &IfDesc, -) -> Option> { +) -> Result, ProtocolError> { match protocol { - 0x50 => Some(Box::new( - BulkOnlyTransport::init(handle, conf_desc, if_desc).unwrap(), - )), - _ => None, + 0x50 => BulkOnlyTransport::init(handle, conf_desc, if_desc) + .map(|transport| Box::new(transport) as Box), + _ => Err(ProtocolError::UnsupportedProtocol(protocol)), } } diff --git a/drivers/storage/usbscsid/src/scsi/mod.rs b/drivers/storage/usbscsid/src/scsi/mod.rs --- a/drivers/storage/usbscsid/src/scsi/mod.rs +++ b/drivers/storage/usbscsid/src/scsi/mod.rs @@ #[error("overflow")] Overflow(&'static str), + + #[error("SCSI command failed: {0}")] + CommandFailed(&'static str), } @@ if let SendCommandStatus { kind: SendCommandStatusKind::Failed, .. } = protocol.send_command( &self.command_buffer[..10], DeviceReqData::In(&mut self.data_buffer[..initial_alloc_len as usize]), )? { self.get_ff_sense(protocol, 252)?; - panic!("{:?}", self.res_ff_sense_data()); + eprintln!( + "usbscsid: MODE SENSE(10) failed; sense data: {:?}", + self.res_ff_sense_data() + ); + return Err(ScsiError::CommandFailed("MODE SENSE(10)")); } diff --git a/drivers/usb/xhcid/src/xhci/event.rs b/drivers/usb/xhcid/src/xhci/event.rs --- a/drivers/usb/xhcid/src/xhci/event.rs +++ b/drivers/usb/xhcid/src/xhci/event.rs @@ #[test] fn grow_preserves_existing_ring_state() { - let mut ring = EventRing::new::(true).unwrap(); + let mut ring = EventRing::new::<{ super::super::CONTEXT_64 }>(true).unwrap(); @@ - ring.grow::(true, old_len + 64) + ring.grow::<{ super::super::CONTEXT_64 }>(true, old_len + 64) .unwrap(); diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ let dev_desc = self.get_desc(port_id, slot).await?; debug!("Got the full device descriptor!"); + info!( + "port {} slot {} device {:04X}:{:04X} class {}.{} proto {} has {} configuration(s)", + port_id, + slot, + dev_desc.vendor, + dev_desc.product, + dev_desc.class, + dev_desc.sub_class, + dev_desc.protocol, + dev_desc.config_descs.len() + ); self.port_states.get_mut(&port_id).unwrap().dev_desc = Some(dev_desc); @@ debug!("Updated the default control pipe"); + info!("port {} slot {} starting subdriver matching", port_id, slot); match self.spawn_drivers(port_id) { Ok(()) => (), @@ trace!("Got config and device descriptors on port {}", port); let drivers_usercfg: &DriversConfig = &DRIVERS_CONFIG; + + if config_desc.interface_descs.is_empty() { + warn!( + "No interface descriptors found for port {} in configuration {}; no subdrivers can be matched", + port, + config_desc.configuration_value + ); + } for ifdesc in config_desc.interface_descs.iter() { diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ pub enum AnyDescriptor { // These are the ones that I have found, but there are more. Device(usb::DeviceDescriptor), Config(usb::ConfigDescriptor), Interface(usb::InterfaceDescriptor), Endpoint(usb::EndpointDescriptor), Hid(usb::HidDescriptor), SuperSpeedCompanion(usb::SuperSpeedCompanionDescriptor), SuperSpeedPlusCompanion(usb::SuperSpeedPlusIsochCmpDescriptor), + Unknown { kind: u8 }, } impl AnyDescriptor { @@ - _ => { - //panic!("Descriptor unknown {}: bytes {:#0x?}", kind, bytes); - return None; - } + _ => Self::Unknown { kind }, }, len.into(), )) @@ - let mut interface_descs = SmallVec::new(); + let mut interface_descs = SmallVec::<[IfDesc; 1]>::new(); let mut iter = descriptors.into_iter().peekable(); @@ Some(AnyDescriptor::Hid(h)) if idesc.class == 3 => { hid_descs.push(h.into()); continue; } + Some(AnyDescriptor::Unknown { kind }) => { + log::warn!( + "port {} slot {} iface {} skipping unknown descriptor kind {} while collecting endpoints", + port_id, + slot, + idesc.number, + kind + ); + continue; + } Some(unexpected) => { log::warn!("expected endpoint, got {:X?}", unexpected); break; @@ } else { log::warn!("expected interface, got {:?}", item); // TODO //break; } } + + log::info!( + "port {} slot {} config {} parsed {} descriptor entries into {} interface(s)", + port_id, + slot, + index, + descriptor_count, + interface_descs.len() + ); + + for if_desc in interface_descs.iter() { + let number: u8 = if_desc.number; + let alternate_setting: u8 = if_desc.alternate_setting; + let class: u8 = if_desc.class; + let sub_class: u8 = if_desc.sub_class; + let protocol: u8 = if_desc.protocol; + let endpoint_count: usize = if_desc.endpoints.len(); + log::info!( + "port {} slot {} config {} iface {} alt {} class {}.{} proto {} endpoints {}", + port_id, + slot, + index, + number, + alternate_setting, + class, + sub_class, + protocol, + endpoint_count + ); + } diff --git a/drivers/audio/ac97d/src/main.rs b/drivers/audio/ac97d/src/main.rs --- a/drivers/audio/ac97d/src/main.rs +++ b/drivers/audio/ac97d/src/main.rs @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! { let pci_config = pcid_handle.config(); let mut name = pci_config.func.name(); name.push_str("_ac97"); + + common::setup_logging( + "audio", + "pci", + &name, + common::output_level(), + common::file_level(), + ); + + let bar0 = match pci_config.func.bars[0] { + pcid_interface::PciBar::Port(port) => port, + ref other => { + log::warn!( + "ac97d: unsupported BAR0 layout for {}: expected port BAR, found {}", + pci_config.func.display(), + other.display(), + ); + std::process::exit(0); + } + }; + let bar1 = match pci_config.func.bars[1] { + pcid_interface::PciBar::Port(port) => port, + ref other => { + log::warn!( + "ac97d: unsupported BAR1 layout for {}: expected port BAR, found {}", + pci_config.func.display(), + other.display(), + ); + std::process::exit(0); + } + }; - - let bar0 = pci_config.func.bars[0].expect_port(); - let bar1 = pci_config.func.bars[1].expect_port(); @@ println!(" + ac97 {}", pci_config.func.display()); - - common::setup_logging( - "audio", - "pci", - &name, - common::output_level(), - common::file_level(), - ); diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs --- a/drivers/input/ps2d/src/main.rs +++ b/drivers/input/ps2d/src/main.rs @@ use common::acquire_port_io_rights; use event::{user_data, EventQueue}; use inputd::ProducerHandle; +use log::{error, warn}; @@ mod controller; mod mouse; mod state; mod vm; + +fn exit_bootsafe(daemon: &mut Option, reason: &str) -> ! { + warn!("ps2d: {}; disabling PS/2 input for this boot", reason); + if let Some(daemon) = daemon.take() { + daemon.ready(); + } + process::exit(0); +} @@ + let mut daemon = Some(daemon); + + if let Err(err) = acquire_port_io_rights() { + exit_bootsafe(&mut daemon, &format!("failed to get I/O permission: {err}")); + } - let input = ProducerHandle::new().expect("ps2d: failed to open input producer"); + let input = match ProducerHandle::new() { + Ok(input) => input, + Err(err) => exit_bootsafe(&mut daemon, &format!("failed to open input producer: {err}")), + }; @@ - let event_queue: EventQueue = - EventQueue::new().expect("ps2d: failed to create event queue"); + let event_queue: EventQueue = match EventQueue::new() { + Ok(event_queue) => event_queue, + Err(err) => exit_bootsafe(&mut daemon, &format!("failed to create event queue: {err}")), + }; @@ - .open("/scheme/serio/0") - .expect("ps2d: failed to open /scheme/serio/0"); + .open("/scheme/serio/0") + .unwrap_or_else(|err| { + exit_bootsafe(&mut daemon, &format!("failed to open /scheme/serio/0: {err}")) + }); - event_queue - .subscribe( - key_file.as_raw_fd() as usize, - Source::Keyboard, - event::EventFlags::READ, - ) - .unwrap(); + if let Err(err) = event_queue.subscribe( + key_file.as_raw_fd() as usize, + Source::Keyboard, + event::EventFlags::READ, + ) { + exit_bootsafe(&mut daemon, &format!("failed to subscribe keyboard serio fd: {err}")); + } @@ - .open("/scheme/serio/1") - .expect("ps2d: failed to open /scheme/serio/1"); + .open("/scheme/serio/1") + .unwrap_or_else(|err| { + exit_bootsafe(&mut daemon, &format!("failed to open /scheme/serio/1: {err}")) + }); - event_queue - .subscribe( - mouse_file.as_raw_fd() as usize, - Source::Mouse, - event::EventFlags::READ, - ) - .unwrap(); + if let Err(err) = event_queue.subscribe( + mouse_file.as_raw_fd() as usize, + Source::Mouse, + event::EventFlags::READ, + ) { + exit_bootsafe(&mut daemon, &format!("failed to subscribe mouse serio fd: {err}")); + } @@ - .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)) - .expect("ps2d: failed to open /scheme/time"); + .open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC)) + .unwrap_or_else(|err| { + exit_bootsafe(&mut daemon, &format!("failed to open /scheme/time: {err}")) + }); - event_queue - .subscribe( - time_file.as_raw_fd() as usize, - Source::Time, - event::EventFlags::READ, - ) - .unwrap(); + if let Err(err) = event_queue.subscribe( + time_file.as_raw_fd() as usize, + Source::Time, + event::EventFlags::READ, + ) { + exit_bootsafe(&mut daemon, &format!("failed to subscribe timer fd: {err}")); + } - libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace"); + if let Err(err) = libredox::call::setrens(0, 0) { + exit_bootsafe(&mut daemon, &format!("failed to enter null namespace: {err}")); + } - daemon.ready(); - - let mut ps2d = Ps2d::new(input, time_file); + let mut ps2d = match Ps2d::new(input, time_file) { + Ok(ps2d) => ps2d, + Err(err) => exit_bootsafe( + &mut daemon, + &format!("PS/2 controller initialization failed: {err:?}"), + ), + }; + + daemon.take().unwrap().ready(); let mut data = [0; 256]; - for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) { + for event in event_queue { + let event = match event { + Ok(event) => event.user_data, + Err(err) => { + error!("ps2d: failed to get next event: {err}"); + break; + } + }; // There are some gotchas with ps/2 controllers that require this weird diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs --- a/drivers/input/ps2d/src/state.rs +++ b/drivers/input/ps2d/src/state.rs @@ -use crate::controller::Ps2; +use crate::controller::{Error as Ps2Error, Ps2}; @@ impl Ps2d { - pub fn new(input: ProducerHandle, time_file: File) -> Self { + pub fn new(input: ProducerHandle, time_file: File) -> Result { let mut ps2 = Ps2::new(); - ps2.init().expect("failed to initialize"); + ps2.init()?; @@ if !this.vmmouse { // This triggers initializing the mouse this.handle_mouse(None); } - this + Ok(this) } diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs --- a/drivers/usb/xhcid/src/main.rs +++ b/drivers/usb/xhcid/src/main.rs @@ +use std::convert::TryFrom; use std::fs::File; +use std::io; use std::sync::Arc; use pcid_interface::irq_helpers::read_bsp_apic_id; #[cfg(target_arch = "x86_64")] -use pcid_interface::irq_helpers::{ - allocate_first_msi_interrupt_on_bsp, allocate_single_interrupt_vector_for_msi, -}; +use pcid_interface::irq_helpers::allocate_single_interrupt_vector; use pcid_interface::{PciFeature, PciFeatureInfo, PciFunctionHandle}; +#[cfg(target_arch = "x86_64")] +use pcid_interface::{MsiSetFeatureInfo, SetFeatureInfo}; @@ mod usb; mod xhci; +#[cfg(target_arch = "x86_64")] +fn allocate_msix_interrupt( + pcid_handle: &mut PciFunctionHandle, + msix_info: pcid_interface::msi::MsixInfo, +) -> io::Result { + let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; + let destination_id = read_bsp_apic_id()?; + let (vector, interrupt_handle) = allocate_single_interrupt_vector(destination_id)? + .ok_or_else(|| io::Error::new(io::ErrorKind::WouldBlock, "no interrupt vectors left"))?; + let lapic_id = u8::try_from(destination_id) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "BSP apic id out of range"))?; + let msg_addr_and_data = pcid_interface::msi::MsiAddrAndData { + addr: pcid_interface::msi::x86::message_address(lapic_id, false, false), + data: pcid_interface::msi::x86::message_data_edge_triggered( + pcid_interface::msi::x86::DeliveryMode::Fixed, + vector, + ), + }; + + let table_entry_pointer = info.table_entry_pointer(0); + table_entry_pointer.write_addr_and_data(msg_addr_and_data); + table_entry_pointer.unmask(); + + pcid_handle.enable_feature(PciFeature::MsiX); + log::debug!("Enabled MSI-X"); + + Ok(interrupt_handle) +} + +#[cfg(target_arch = "x86_64")] +fn allocate_msi_interrupt(pcid_handle: &mut PciFunctionHandle) -> io::Result { + let destination_id = read_bsp_apic_id()?; + let (vector, interrupt_handle) = allocate_single_interrupt_vector(destination_id)? + .ok_or_else(|| io::Error::new(io::ErrorKind::WouldBlock, "no interrupt vectors left"))?; + let lapic_id = u8::try_from(destination_id) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "BSP apic id out of range"))?; + let set_feature_info = MsiSetFeatureInfo { + multi_message_enable: Some(0), + message_address_and_data: Some(pcid_interface::msi::MsiAddrAndData { + addr: pcid_interface::msi::x86::message_address(lapic_id, false, false), + data: pcid_interface::msi::x86::message_data_edge_triggered( + pcid_interface::msi::x86::DeliveryMode::Fixed, + vector, + ), + }), + mask_bits: None, + }; + + pcid_handle.set_feature_info(SetFeatureInfo::Msi(set_feature_info)); + pcid_handle.enable_feature(PciFeature::Msi); + log::debug!("Enabled MSI"); + + Ok(interrupt_handle) +} @@ let has_msi = all_pci_features.iter().any(|feature| feature.is_msi()); let has_msix = all_pci_features.iter().any(|feature| feature.is_msix()); if has_msix { - let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) { - PciFeatureInfo::Msi(_) => panic!(), - PciFeatureInfo::MsiX(s) => s, - }; - let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) }; - - // Allocate one msi vector. - - let method = { - // primary interrupter - let k = 0; - - let table_entry_pointer = info.table_entry_pointer(k); - - let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id"); - 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); - table_entry_pointer.unmask(); - - (Some(interrupt_handle), InterruptMethod::Msi) - }; - - pcid_handle.enable_feature(PciFeature::MsiX); - log::debug!("Enabled MSI-X"); - - method - } else if has_msi { - let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle); - (Some(interrupt_handle), InterruptMethod::Msi) - } else if let Some(irq) = pci_config.func.legacy_interrupt_line { + match pcid_handle.feature_info(PciFeature::MsiX) { + PciFeatureInfo::MsiX(msix_info) => match allocate_msix_interrupt(pcid_handle, msix_info) + { + Ok(interrupt_handle) => return (Some(interrupt_handle), InterruptMethod::Msi), + Err(err) => { + log::warn!("xhcid: MSI-X setup failed, falling back: {err}"); + } + }, + feature_info => { + log::warn!( + "xhcid: MSI-X feature probe returned unexpected descriptor {:?}; falling back", + feature_info + ); + } + } + } + + if has_msi { + match allocate_msi_interrupt(pcid_handle) { + Ok(interrupt_handle) => return (Some(interrupt_handle), InterruptMethod::Msi), + Err(err) => { + log::warn!("xhcid: MSI setup failed, falling back: {err}"); + } + } + } + + if let Some(irq) = pci_config.func.legacy_interrupt_line { log::debug!("Legacy IRQ {}", irq); @@ log::info!("XHCI {}", pci_config.func.display()); let scheme_name = format!("usb.{}", name); - let socket = Socket::create().expect("xhcid: failed to create usb scheme"); + let socket = match Socket::create() { + Ok(socket) => socket, + Err(err) => { + log::error!("xhcid: failed to create usb scheme: {err}"); + std::process::exit(0); + } + }; let mut state = SchemeState::new(); - let hci = Arc::new( - Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) - .expect("xhcid: failed to allocate device"), - ); - register_sync_scheme(&socket, &scheme_name, &mut &*hci) - .expect("xhcid: failed to regsiter scheme to namespace"); + let hci = match Xhci::::new(scheme_name.clone(), address, interrupt_method, pcid_handle) { + Ok(hci) => Arc::new(hci), + Err(err) => { + log::error!("xhcid: failed to allocate device: {err}"); + std::process::exit(0); + } + }; + if let Err(err) = register_sync_scheme(&socket, &scheme_name, &mut &*hci) { + log::error!("xhcid: failed to register scheme to namespace: {err}"); + std::process::exit(0); + } diff --git a/drivers/usb/xhcid/src/xhci/device_enumerator.rs b/drivers/usb/xhcid/src/xhci/device_enumerator.rs --- a/drivers/usb/xhcid/src/xhci/device_enumerator.rs +++ b/drivers/usb/xhcid/src/xhci/device_enumerator.rs @@ impl DeviceEnumerator { + fn is_port_disabled_state(flags: &PortFlags) -> bool { + flags.contains(PortFlags::PP) + && flags.contains(PortFlags::CCS) + && !flags.contains(PortFlags::PED) + && !flags.contains(PortFlags::PR) + } + + fn wait_for_stable_port_flags( + &self, + port_id: PortId, + mut flags: PortFlags, + ) -> Option { + const MAX_ATTEMPTS: usize = 8; + const STABILIZE_DELAY: Duration = Duration::from_millis(20); + + if flags.contains(PortFlags::PED) || Self::is_port_disabled_state(&flags) { + return Some(flags); + } + + for attempt in 0..MAX_ATTEMPTS { + debug!( + "Port {} reported transient flags {:?}; waiting for a stable state ({}/{})", + port_id, + flags, + attempt + 1, + MAX_ATTEMPTS + ); + std::thread::sleep(STABILIZE_DELAY); + + flags = { + let ports = self.hci.ports.lock().unwrap(); + let index = port_id.root_hub_port_index(); + if index >= ports.len() { + warn!( + "Port {} disappeared while waiting for a stable state", + port_id + ); + return None; + } + ports[index].flags() + }; + + if flags.contains(PortFlags::PED) || Self::is_port_disabled_state(&flags) { + return Some(flags); + } + } + + None + } + pub fn new(hci: Arc>) -> Self { let request_queue = hci.device_enumerator_receiver.clone(); DeviceEnumerator { hci, request_queue } } @@ loop { debug!("Start Device Enumerator Loop"); let request = match self.request_queue.recv() { Ok(req) => req, - Err(err) => { - panic!("Failed to received an enumeration request! error: {}", err) - } + Err(err) => { + warn!("xhcid: device enumerator shutting down: {}", err); + return; + } }; @@ - if flags.contains(PortFlags::CCS) { + if flags.contains(PortFlags::CCS) { + let Some(flags) = self.wait_for_stable_port_flags(port_id, flags) else { + warn!( + "Port {} never reached a stable connected state; ignoring this change", + port_id + ); + continue; + }; + debug!( "Received Device Connect Port Status Change Event with port flags {:?}", flags ); @@ //If the port isn't enabled (i.e. it's a USB2 port), we need to reset it if it isn't resetting already //A USB3 port won't generate a Connect Status Change until it's already enabled, so this check //will always be skipped for USB3 ports if !flags.contains(PortFlags::PED) { - let disabled_state = flags.contains(PortFlags::PP) - && flags.contains(PortFlags::CCS) - && !flags.contains(PortFlags::PED) - && !flags.contains(PortFlags::PR); - - if !disabled_state { - panic!( - "Port {} isn't in the disabled state! Current flags: {:?}", - port_id, flags - ); - } else { - debug!("Port {} has entered the disabled state.", port_id); - } + debug!("Port {} has entered the disabled state.", port_id); @@ if !enabled_state { warn!( "Port {} isn't in the enabled state! Current flags: {:?}", port_id, flags ); - } else { - debug!( - "Port {} is in the enabled state. Proceeding with enumeration", - port_id - ); + continue; } + debug!( + "Port {} is in the enabled state. Proceeding with enumeration", + port_id + ); } diff --git a/drivers/usb/xhcid/src/xhci/irq_reactor.rs b/drivers/usb/xhcid/src/xhci/irq_reactor.rs --- a/drivers/usb/xhcid/src/xhci/irq_reactor.rs +++ b/drivers/usb/xhcid/src/xhci/irq_reactor.rs @@ let port_id = PortId { root_hub_port_num, route_string: 0, }; trace!("Received Port Status Change Request on port {}", port_id); - self.device_enumerator_sender - .send(DeviceEnumerationRequest { port_id }) - .expect( - format!( - "Failed to transmit device numeration request on port {}", - port_id - ) - .as_str(), - ); + if let Err(err) = self + .device_enumerator_sender + .send(DeviceEnumerationRequest { port_id }) + { + warn!( + "Failed to transmit device enumeration request on port {}: {}", + port_id, + err + ); + return; + } { let mut ports = self.hci.ports.lock().unwrap();