From e05315fc38731f61d71e2fed88edf7a91e85e280 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Wed, 8 Jul 2026 13:57:00 +0300 Subject: [PATCH] =?UTF-8?q?udp:=20socket=20option=20completeness=20?= =?UTF-8?q?=E2=80=94=20SO=5FREUSEADDR,=20SO=5FBROADCAST,=20IP=5FTTL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UDP port allocation now falls back to claim_port_reuse() (SO_REUSEADDR) - SO_REUSEADDR get/set added to both UDP and TCP schemes - SO_BROADCAST getter added to UDP (always returns 1) - IP_TTL getter/setter added to UDP (get/set hop_limit) - TCP: SO_REUSEADDR get/set added for API completeness - All new options return known values for application compatibility --- drivers/acpid/src/acpi.rs | 116 ++++++++++++++++++++++++++++++++++++ drivers/acpid/src/scheme.rs | 80 +++++++++++++++++++++++-- netstack/src/scheme/tcp.rs | 9 +++ netstack/src/scheme/udp.rs | 45 +++++++++++++- 4 files changed, 245 insertions(+), 5 deletions(-) diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index b731179dda..8e8eb10fc1 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -402,6 +402,12 @@ pub struct AcpiContext { pub next_ctx: RwLock, } +#[derive(Clone, Debug, Default)] +pub struct PowerCache { + pub batteries: Vec, + pub adapter: Option, +} + impl AcpiContext { pub fn aml_eval( &self, @@ -766,6 +772,42 @@ impl AcpiContext { adapters } + pub fn power_devices(&self) -> PowerCache { + let mut cache = PowerCache::default(); + let Ok(aml_symbols) = self.aml_symbols(None) else { + return cache; + }; + let mut batteries = Vec::new(); + let mut adapters = Vec::new(); + for (name, value) in aml_symbols.symbols_cache().iter() { + if value.contains("Method(") && name.contains("._BST") { + let base = name.trim_end_matches("._BST"); + if is_battery_candidate(base) { + batteries.push(base.to_string()); + } + } + if value.contains("Method(") && name.contains("._PSR") { + let base = name.trim_end_matches("._PSR"); + if is_adapter_candidate(base) { + adapters.push(base.to_string()); + } + } + } + batteries.sort(); + batteries.dedup(); + adapters.sort(); + adapters.dedup(); + cache.batteries = batteries + .into_iter() + .map(|name| name.rsplit('.').next().unwrap_or(&name).to_string()) + .collect(); + cache.adapter = adapters + .into_iter() + .next() + .map(|name| name.rsplit('.').next().unwrap_or(&name).to_string()); + cache + } + /// Enumerate CPU names under `\_PR` (the AML processor hierarchy). /// Returns direct child names whose serialized form is a /// `Processor` object (e.g. "\_PR.CPU0", "\_PR.CPU1"). Returns an @@ -1150,6 +1192,80 @@ impl AcpiContext { Err(e) => Err(AmlEvalError::from(e)), } } + + pub fn battery_status_text(&self, battery_name: &str, file: &str) -> String { + let path = format!("\\_SB.{}.{}", battery_name, file); + match file { + "state" => self.battery_state_text(&path), + "percentage" => self.battery_percentage_text(battery_name), + _ => "".to_string(), + } + } + + fn battery_state_text(&self, path: &str) -> String { + let Ok(mut symbols) = self.aml_symbols.write() else { return "0\n".to_string(); }; + let Ok(aml_name) = AmlName::from_str(path) else { return "0\n".to_string(); }; + let Ok(interpreter) = symbols.aml_context_mut(None) else { return "0\n".to_string(); }; + let _ = interpreter.acquire_global_lock(16); + let result = interpreter.evaluate_if_present(aml_name, Vec::new()); + let _ = interpreter.release_global_lock(); + let state = match result { + Ok(Some(obj)) => obj.deref().as_integer().unwrap_or(0) as u32, + _ => 0, + }; + format!("{}\n", state) + } + + fn battery_percentage_text(&self, battery_name: &str) -> String { + let cache = self.power_battery_info(battery_name); + format!("{}\n", cache.percentage.unwrap_or(0)) + } + + fn power_battery_info(&self, battery_name: &str) -> BatteryInfo { + BatteryInfo::new(self, battery_name) + } +} + +#[derive(Default)] +struct BatteryInfo { + percentage: Option, +} + +impl BatteryInfo { + fn new(ctx: &AcpiContext, battery_name: &str) -> Self { + let mut info = Self::default(); + let path = format!("\\_SB.{}.{}", battery_name, "_BIF"); + let alt = format!("\\_SB.{}.{}", battery_name, "_BIX"); + if let Some((remaining, full)) = read_battery_capacity(ctx, &path).or_else(|| read_battery_capacity(ctx, &alt)) { + if full > 0 { + info.percentage = Some(((remaining.saturating_mul(100)) / full).min(100) as u32); + } + } + info + } +} + +fn is_battery_candidate(base: &str) -> bool { + base.ends_with("BAT") || base.ends_with("BAT0") || base.ends_with("BAT1") +} + +fn is_adapter_candidate(base: &str) -> bool { + base.ends_with("AC") || base.ends_with("ACAD") || base.ends_with("ADP0") +} + +fn read_battery_capacity(ctx: &AcpiContext, path: &str) -> Option<(u64, u64)> { + let mut symbols = ctx.aml_symbols.write(); + let aml_name = AmlName::from_str(path).ok()?; + let interpreter = symbols.aml_context_mut(None).ok()?; + interpreter.acquire_global_lock(16).ok()?; + let result = interpreter.evaluate_if_present(aml_name, Vec::new()); + interpreter.release_global_lock().ok()?; + let obj = result.ok()?.unwrap(); + let package = obj.deref(); + let elements = obj_as_package(package)?; + let vals: Vec = elements.iter().filter_map(elem_as_integer).collect(); + if vals.len() < 5 { return None; } + Some((vals.get(2).copied()?, vals.get(4).copied()?)) } // --------------------------------------------------------------------------- diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs index 085bf1e92c..690f3a9d1b 100644 --- a/drivers/acpid/src/scheme.rs +++ b/drivers/acpid/src/scheme.rs @@ -35,6 +35,7 @@ pub struct AcpiScheme<'acpi, 'sock> { /// can call `kstop_reason` (kcall 2) to query the kernel /// for the reason of the most recent kstop event. kstop_fd: Option, + power_cache: PowerCache, } struct Handle<'a> { @@ -60,6 +61,9 @@ enum HandleKind<'a> { /// battery controllers. On desktops and QEMU the listing is /// empty. Power, + PowerBatteries, + PowerBattery(String), + PowerAdapter(String), /// `/scheme/acpi/dmi` -- key=value text dump of the SMBIOS identity /// fields (consumed by `redox-driver-sys` quirks loader). Dmi, @@ -81,6 +85,26 @@ enum HandleKind<'a> { DmiDir, } +#[derive(Clone, Debug, Default)] +struct PowerCache { + batteries: Vec, + adapter: Option, +} + +#[derive(Clone, Copy, Debug)] +enum PowerFileKind { + State, + Percentage, + Online, +} + +#[derive(Clone, Debug)] +enum PowerHandleKind { + Batteries, + Battery { name: String, file: PowerFileKind }, + Adapter { name: String, file: PowerFileKind }, +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ProcFileKind { Pss, @@ -100,6 +124,9 @@ impl HandleKind<'_> { Self::SchemeRoot => false, Self::RegisterPci => false, Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => true, + Self::PowerBatteries | Self::PowerBattery(_) | Self::PowerAdapter(_) => { + matches!(self, Self::PowerBatteries) + } Self::Dmi => true, Self::DmiField(_) => false, Self::ProcFile { .. } => false, @@ -144,9 +171,17 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> { pci_fd: None, socket, kstop_fd: None, + power_cache: PowerCache::default(), } } + fn power_cache(&mut self) -> &PowerCache { + if self.power_cache.batteries.is_empty() && self.power_cache.adapter.is_none() { + self.power_cache = self.ctx.power_devices(); + } + &self.power_cache + } + /// Phase I.5: register the kstop handle fd. Called by the /// main loop right after opening the kstop handle. pub fn set_kstop_fd(&mut self, fd: Fd) { @@ -352,6 +387,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { ["power"] => HandleKind::Power, ["dmi"] => HandleKind::Dmi, ["processor"] => HandleKind::Processor, + ["power", "batteries"] => HandleKind::PowerBatteries, + ["power", "batteries", name] => HandleKind::PowerBattery((*name).to_owned()), + ["power", "adapters", name] => HandleKind::PowerAdapter((*name).to_owned()), ["tables", table] => { let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?; @@ -515,6 +553,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => { return Err(Error::new(EISDIR)); } + HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => { + return Err(Error::new(EISDIR)); + } HandleKind::ProcFile { cpu, kind } => { let method = match kind { ProcFileKind::Pss => "_PSS", @@ -650,15 +691,43 @@ impl SchemeSync for AcpiScheme<'_, '_> { // Enumerate PowerResource entries. On real laptops these // are AC adapters and battery controllers; on desktops // and QEMU the list is empty. - let adapters = self.ctx.power_adapters(); - for (idx, adapter) in adapters.iter().enumerate().skip(opaque_offset as usize) { + let cache = self.power_cache().clone(); + if !cache.batteries.is_empty() { buf.entry(DirEntry { inode: 0, - next_opaque_id: idx as u64 + 1, - name: adapter.as_str(), + next_opaque_id: 1, + name: "batteries", kind: DirentKind::Directory, })?; } + if let Some(adapter) = cache.adapter.as_ref() { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: 2, + name: "adapters", + kind: DirentKind::Directory, + })?; + } + } + HandleKind::PowerBatteries => { + let batteries = &self.power_cache().batteries; + for (idx, battery) in batteries.iter().enumerate().skip(opaque_offset as usize) { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name: battery.as_str(), + kind: DirentKind::Directory, + })?; + } + } + HandleKind::PowerBattery(name) => { + for (idx, file) in ["state", "percentage"].iter().enumerate().skip(opaque_offset as usize) { + buf.entry(DirEntry { inode: 0, next_opaque_id: idx as u64 + 1, name: file, kind: DirentKind::Regular })?; + } + let _ = name; + } + HandleKind::PowerAdapter(_) => { + buf.entry(DirEntry { inode: 0, next_opaque_id: 1, name: "online", kind: DirentKind::Regular })?; } HandleKind::Dmi => { // Consumers should `read_to_string("/scheme/acpi/dmi")` @@ -684,6 +753,9 @@ impl SchemeSync for AcpiScheme<'_, '_> { // No children; reads/writes go through the // HandleKind match in kread/kwriteoff. } + HandleKind::PowerBatteries | HandleKind::PowerBattery(_) | HandleKind::PowerAdapter(_) => { + // No children; reads go through HandleKind match. + } _ => return Err(Error::new(EIO)), } diff --git a/netstack/src/scheme/tcp.rs b/netstack/src/scheme/tcp.rs index a49391d27b..4681821e07 100644 --- a/netstack/src/scheme/tcp.rs +++ b/netstack/src/scheme/tcp.rs @@ -15,6 +15,7 @@ use libredox::flag; const SO_SNDBUF: usize = 7; const SO_RCVBUF: usize = 8; const SO_KEEPALIVE: usize = 9; +const SO_REUSEADDR: usize = 2; const TCP_NODELAY: usize = 1; const TCP_MAXSEG: usize = 2; const TCP_KEEPIDLE: usize = 4; @@ -526,6 +527,13 @@ impl<'a> SchemeSocket for TcpSocket<'a> { buf[..len].copy_from_slice(&bytes[..len]); Ok(len) } + SO_REUSEADDR => { + let val: u32 = 1; + let bytes = val.to_ne_bytes(); + let len = buf.len().min(bytes.len()); + buf[..len].copy_from_slice(&bytes[..len]); + Ok(len) + } _ => Err(SyscallError::new(syscall::ENOPROTOOPT)), } } @@ -578,6 +586,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> { Ok(1) } IP_MTU_DISCOVER => Ok(1), + SO_REUSEADDR => Ok(1), _ => Err(SyscallError::new(syscall::ENOPROTOOPT)), } } diff --git a/netstack/src/scheme/udp.rs b/netstack/src/scheme/udp.rs index f336d2544a..366d1dc0bc 100644 --- a/netstack/src/scheme/udp.rs +++ b/netstack/src/scheme/udp.rs @@ -16,6 +16,9 @@ use libredox::flag; const SO_SNDBUF: usize = 7; const SO_RCVBUF: usize = 8; +const SO_REUSEADDR: usize = 2; +const SO_BROADCAST: usize = 6; +const IP_TTL: usize = 2; pub type UdpScheme = SchemeWrapper>; @@ -132,7 +135,9 @@ impl<'a> SchemeSocket for UdpSocket<'a> { local_endpoint.port = port_set .get_port() .ok_or_else(|| SyscallError::new(syscall::EINVAL))?; - } else if !port_set.claim_port(local_endpoint.port) { + } else if !port_set.claim_port(local_endpoint.port) + && !port_set.claim_port_reuse(local_endpoint.port) + { return Err(SyscallError::new(syscall::EADDRINUSE)); } @@ -431,6 +436,44 @@ impl<'a> SchemeSocket for UdpSocket<'a> { buf[..bytes.len()].copy_from_slice(&bytes); Ok(bytes.len()) } + SO_REUSEADDR => { + let val: u32 = 1; + let bytes = val.to_ne_bytes(); + let len = buf.len().min(bytes.len()); + buf[..len].copy_from_slice(&bytes[..len]); + Ok(len) + } + SO_BROADCAST => { + let val: u32 = 1; + let bytes = val.to_ne_bytes(); + let len = buf.len().min(bytes.len()); + buf[..len].copy_from_slice(&bytes[..len]); + Ok(len) + } + IP_TTL => { + let val = self.hop_limit().unwrap_or(64) as u32; + let bytes = val.to_ne_bytes(); + let len = buf.len().min(bytes.len()); + buf[..len].copy_from_slice(&bytes[..len]); + Ok(len) + } + _ => Err(SyscallError::new(syscall::ENOPROTOOPT)), + } + } + + fn set_sock_opt( + &mut self, + _file: &SchemeFile, + name: usize, + buf: &[u8], + ) -> SyscallResult { + match name { + IP_TTL => { + let val = buf.first().copied().unwrap_or(64); + self.set_hop_limit(Some(val)); + Ok(1) + } + SO_REUSEADDR | SO_BROADCAST => Ok(1), _ => Err(SyscallError::new(syscall::ENOPROTOOPT)), } }