Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d98330a7de | |||
| 29166263bb | |||
| e54484e553 | |||
| 2e5e516587 | |||
| b906ad688a | |||
| 0ca545d35a | |||
| 5a43628d58 |
+47
-6
@@ -121,12 +121,18 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
).subsec_nanos();
|
||||
|
||||
let socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind udp");
|
||||
try_fmt!(socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "failed to connect");
|
||||
let broadcast = SocketAddr::from(([255, 255, 255, 255], 67));
|
||||
// 8s (was 30s): a DHCP server that is present answers a DISCOVER in well
|
||||
// under a second, so a long read timeout only serves to stall boot when no
|
||||
// server responds (e.g. QEMU user-net where the limited broadcast is not
|
||||
// routed, or a link with no DHCP). Failing fast keeps the network stage off
|
||||
// the critical path to login instead of hanging the boot for ~30s+.
|
||||
//
|
||||
// Do NOT call connect() to the broadcast address. UDP connect()
|
||||
// filters incoming packets by source address — an OFFER from the
|
||||
// DHCP server's actual IP (e.g. QEMU SLIRP at 10.0.2.2) would be
|
||||
// dropped because it doesn't match the connected broadcast peer.
|
||||
// Use send_to() per-packet and let recv() accept any source.
|
||||
try_fmt!(socket.set_read_timeout(Some(Duration::new(8, 0))), "failed to set read timeout");
|
||||
try_fmt!(socket.set_write_timeout(Some(Duration::new(8, 0))), "failed to set write timeout");
|
||||
|
||||
@@ -142,7 +148,7 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
init_dhcp_header(&mut discover, current_mac, tid);
|
||||
let disc_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPDISCOVER, OPT_END];
|
||||
discover.options[..disc_opts.len()].copy_from_slice(disc_opts);
|
||||
try_fmt!(send_dhcp(&discover, &socket), "failed to send discover");
|
||||
try_fmt!(send_dhcp_to(&discover, &socket, broadcast), "failed to send discover");
|
||||
if verbose { println!("DHCP: Sent Discover"); }
|
||||
}
|
||||
|
||||
@@ -195,7 +201,7 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
OPT_END,
|
||||
];
|
||||
request.options[..req_opts.len()].copy_from_slice(req_opts);
|
||||
try_fmt!(send_dhcp(&request, &socket), "failed to send request");
|
||||
try_fmt!(send_dhcp_to(&request, &socket, broadcast), "failed to send request");
|
||||
if verbose { println!("DHCP: Sent Request"); }
|
||||
}
|
||||
|
||||
@@ -223,7 +229,10 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
renew.ciaddr = offer.yiaddr;
|
||||
let rn_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
||||
renew.options[..rn_opts.len()].copy_from_slice(rn_opts);
|
||||
try_fmt!(send_dhcp(&renew, &socket), "failed to send renew");
|
||||
try_fmt!(
|
||||
send_dhcp_to(&renew, &socket, broadcast),
|
||||
"failed to send renew"
|
||||
);
|
||||
}
|
||||
|
||||
socket.set_read_timeout(Some(t2.saturating_sub(t1))).ok();
|
||||
@@ -242,13 +251,12 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
||||
Err(_) => {
|
||||
if verbose { println!("DHCP: entering REBIND state"); }
|
||||
let bind_socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind rebind");
|
||||
try_fmt!(bind_socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "rebind connect");
|
||||
let mut rebind = Dhcp::default();
|
||||
init_dhcp_header(&mut rebind, current_mac, tid.wrapping_add(2));
|
||||
rebind.ciaddr = offer.yiaddr;
|
||||
let rb_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
||||
rebind.options[..rb_opts.len()].copy_from_slice(rb_opts);
|
||||
let _ = send_dhcp(&rebind, &bind_socket);
|
||||
let _ = send_dhcp_to(&rebind, &bind_socket, broadcast);
|
||||
bind_socket.set_read_timeout(Some(Duration::from_secs(10))).ok();
|
||||
if let Ok(_) = bind_socket.recv(&mut ack_data) {
|
||||
if verbose { println!("DHCP: rebound"); }
|
||||
@@ -332,6 +340,23 @@ fn send_dhcp(pkt: &Dhcp, socket: &UdpSocket) -> Result<(), String> {
|
||||
socket.send(data).map(|_| ()).map_err(|e| format!("send: {}", e))
|
||||
}
|
||||
|
||||
/// `send_to(addr, ...)` variant — used for the DISCOVER, REQUEST, and
|
||||
/// RENEW/REBIND transmissions, which all target 255.255.255.255:67.
|
||||
/// `connect()`-based sends drop incoming packets from off-broadcast
|
||||
/// sources, which would lose the OFFER/ACK the DHCP server emits from
|
||||
/// its own IP. Per-packet `send_to` + unfiltered `recv` is the only
|
||||
/// shape that actually completes the four-message handshake.
|
||||
fn send_dhcp_to(
|
||||
pkt: &Dhcp,
|
||||
socket: &UdpSocket,
|
||||
addr: SocketAddr,
|
||||
) -> Result<(), String> {
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts(pkt as *const Dhcp as *const u8, std::mem::size_of::<Dhcp>())
|
||||
};
|
||||
socket.send_to(data, addr).map(|_| ()).map_err(|e| format!("send_to: {}", e))
|
||||
}
|
||||
|
||||
impl Default for Dhcp {
|
||||
fn default() -> Self {
|
||||
Dhcp {
|
||||
@@ -354,6 +379,22 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
// driver-manager attaches the NIC asynchronously, and smolnetd only brings up
|
||||
// /scheme/netcfg/ifaces/<iface> after it binds the adapter, so on a fresh boot
|
||||
// this path may not exist yet when dhcpd runs (10_dhcpd only requires_weak
|
||||
// that 10_smolnetd *started*). Wait (bounded) for the interface to appear
|
||||
// instead of failing immediately with "Can't open ...". A genuinely NIC-less
|
||||
// machine falls through after the window and exits cleanly (0), no error.
|
||||
let mac_path = format!("/scheme/netcfg/ifaces/{iface}/mac");
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(20);
|
||||
while std::fs::File::open(&mac_path).is_err() {
|
||||
if std::time::Instant::now() >= deadline {
|
||||
eprintln!("dhcpd: {iface} never appeared in /scheme/netcfg (no NIC?); skipping DHCP");
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(250));
|
||||
}
|
||||
|
||||
if let Err(err) = dhcp(iface, verbose) {
|
||||
eprintln!("dhcpd: {err}");
|
||||
process::exit(1);
|
||||
|
||||
@@ -442,6 +442,7 @@ pub struct AcpiContext {
|
||||
pub fan_devices: RwLock<Vec<String>>,
|
||||
|
||||
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
|
||||
pub wmi_registry: RwLock<Option<crate::wmi::WmiRegistry>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -576,6 +577,7 @@ impl AcpiContext {
|
||||
sleep_button_events: RwLock::new(0),
|
||||
fan_devices: RwLock::new(Vec::new()),
|
||||
wake_registry: RwLock::new(None),
|
||||
wmi_registry: RwLock::new(None),
|
||||
|
||||
sdt_order: RwLock::new(Vec::new()),
|
||||
dmi: None,
|
||||
|
||||
@@ -178,7 +178,9 @@ impl AmlPhysMemHandler {
|
||||
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
||||
Some(libredox::Fd::new(pci_fd.raw()))
|
||||
} else {
|
||||
log::error!("pci_fd is not registered");
|
||||
log::warn!(
|
||||
"acpid: AmlPhysMemHandler created without PCI fd; AML init will retry once pcid sends it via scheme:acpid sendfd"
|
||||
);
|
||||
None
|
||||
};
|
||||
Self {
|
||||
|
||||
@@ -19,6 +19,7 @@ mod gpe;
|
||||
mod notifications;
|
||||
mod power_events;
|
||||
mod wake;
|
||||
mod wmi;
|
||||
|
||||
mod scheme;
|
||||
|
||||
@@ -118,6 +119,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
}
|
||||
*acpi_context.wake_registry.write() = Some(wake_registry);
|
||||
|
||||
// WMI (PNP0C14) discovery: enumerate event GUIDs from _WDG so WMI
|
||||
// hotkey events (brightness/touchpad/rfkill on LG and other laptops)
|
||||
// can be decoded via _WED when the firmware sends a WMI notify.
|
||||
let wmi_registry = wmi::WmiRegistry::discover(&acpi_context);
|
||||
*acpi_context.wmi_registry.write() = Some(wmi_registry);
|
||||
|
||||
// TODO: I/O permission bitmap?
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||
@@ -292,6 +299,16 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
power_events::PowerEvent::Notify { device, value } => {
|
||||
log::debug!("acpid: device notification {} = {:#x}", device, value);
|
||||
}
|
||||
power_events::PowerEvent::Wmi {
|
||||
notify_id,
|
||||
eventcode,
|
||||
key,
|
||||
} => {
|
||||
log::info!(
|
||||
"acpid: WMI hotkey event notify_id={:#x} eventcode={:#x} key={}",
|
||||
notify_id, eventcode, key
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -20,6 +20,11 @@ pub enum PowerEvent {
|
||||
EcQuery(u8),
|
||||
LidChanged(bool),
|
||||
Notify { device: String, value: u64 },
|
||||
Wmi {
|
||||
notify_id: u8,
|
||||
eventcode: u32,
|
||||
key: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
/// ECDT (Embedded Controller Boot Resources Table) payload after the
|
||||
@@ -272,8 +277,27 @@ pub fn handle_sci(context: &AcpiContext) -> Vec<PowerEvent> {
|
||||
|
||||
// Drain AML notifications (from _Qxx methods and any other Notify).
|
||||
let lid_path = context.lid_device.read().clone();
|
||||
let wmi_path = context
|
||||
.wmi_registry
|
||||
.read()
|
||||
.as_ref()
|
||||
.and_then(|r| r.device_path());
|
||||
for (device, value) in context.notifications().drain() {
|
||||
log::info!("acpid: notify {} = {:#x}", device, value);
|
||||
if let Some(wmi_dev) = &wmi_path {
|
||||
if &device == wmi_dev {
|
||||
if let Some(registry) = context.wmi_registry.read().as_ref() {
|
||||
if let Some(wmi_event) = registry.handle_notify(context, value as u8) {
|
||||
events.push(PowerEvent::Wmi {
|
||||
notify_id: wmi_event.notify_id,
|
||||
eventcode: wmi_event.eventcode,
|
||||
key: wmi_event.key,
|
||||
});
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(lid) = &lid_path {
|
||||
if &device == lid {
|
||||
let state = read_lid_state(context);
|
||||
|
||||
@@ -1070,10 +1070,27 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
}
|
||||
let new_fd = libredox::Fd::new(new_fd);
|
||||
|
||||
// Allow replacement: pcid may resend the fd after a restart,
|
||||
// and the previous one-shot EINVAL left aml init permanently
|
||||
// broken if the first sendfd raced with an early aml request.
|
||||
if self.pci_fd.is_some() {
|
||||
return Err(Error::new(EINVAL));
|
||||
} else {
|
||||
self.pci_fd = Some(new_fd);
|
||||
log::warn!(
|
||||
"acpid: replacing previously-registered PCI fd; AML symbol cache will rebuild on next request"
|
||||
);
|
||||
}
|
||||
self.pci_fd = Some(new_fd);
|
||||
|
||||
// Kick aml symbol init now that pci_fd is registered. The
|
||||
// next aml request will either find a working cache or get a
|
||||
// fresh error log here; either way the failure mode is
|
||||
// observable rather than silently deferred to "the next
|
||||
// caller retries".
|
||||
match self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
||||
Ok(_) => log::info!("acpid: AML symbols initialized on PCI fd registration"),
|
||||
Err(err) => {
|
||||
log::error!("acpid: AML symbol init failed after PCI fd registration");
|
||||
log::error!("acpid: AML error detail: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(num_fds)
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
use log::{debug, info, warn};
|
||||
|
||||
use acpi::aml::namespace::AmlName;
|
||||
use amlserde::AmlSerdeValue;
|
||||
use std::str::FromStr;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::acpi::AcpiContext;
|
||||
|
||||
const ACPI_WMI_EVENT: u8 = 1 << 3;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct GuidBlock {
|
||||
pub guid: [u8; 16],
|
||||
pub notify_id: u8,
|
||||
pub instance_count: u8,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
impl GuidBlock {
|
||||
pub const SIZE: usize = 20;
|
||||
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
if data.len() < Self::SIZE {
|
||||
return None;
|
||||
}
|
||||
let mut guid = [0u8; 16];
|
||||
guid.copy_from_slice(&data[0..16]);
|
||||
let notify_id = data[16];
|
||||
let instance_count = data[18];
|
||||
let flags = data[19];
|
||||
Some(Self {
|
||||
guid,
|
||||
notify_id,
|
||||
instance_count,
|
||||
flags,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_event(&self) -> bool {
|
||||
(self.flags & ACPI_WMI_EVENT) != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WmiEvent {
|
||||
pub notify_id: u8,
|
||||
pub eventcode: u32,
|
||||
pub key: &'static str,
|
||||
}
|
||||
|
||||
pub struct WmiRegistry {
|
||||
device_path: RwLock<Option<String>>,
|
||||
event_notify_ids: RwLock<Vec<u8>>,
|
||||
}
|
||||
|
||||
fn lg_key_for_eventcode(eventcode: u32) -> &'static str {
|
||||
match eventcode {
|
||||
0x70 => "lg-control-panel",
|
||||
0x74 => "touchpad-toggle",
|
||||
0xf020000 => "read-mode",
|
||||
0x10000000 => "kbd-backlight",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
impl WmiRegistry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
device_path: RwLock::new(None),
|
||||
event_notify_ids: RwLock::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_path(&self) -> Option<String> {
|
||||
self.device_path.read().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn has_notify_id(&self, notify_id: u8) -> bool {
|
||||
self.event_notify_ids.read().unwrap().contains(¬ify_id)
|
||||
}
|
||||
|
||||
pub fn discover(context: &AcpiContext) -> Self {
|
||||
let registry = Self::new();
|
||||
const CANDIDATES: &[&str] = &[
|
||||
"\\_SB.PC00.WMID",
|
||||
"\\_SB.PCI0.WMID",
|
||||
"\\_SB.WMID",
|
||||
"\\_SB.PC00.LPCB.WMID",
|
||||
"\\_SB.PCI0.LPCB.WMID",
|
||||
];
|
||||
for path in CANDIDATES {
|
||||
let Ok(hid) = context.evaluate_acpi_method(path, "_HID", &[]) else {
|
||||
continue;
|
||||
};
|
||||
if hid.first().copied() != Some(0x0C14) {
|
||||
continue;
|
||||
}
|
||||
info!("acpid: WMI device at {}", path);
|
||||
*registry.device_path.write().unwrap() = Some(path.to_string());
|
||||
registry.enumerate_event_guids(context, path);
|
||||
break;
|
||||
}
|
||||
if registry.device_path().is_none() {
|
||||
debug!("acpid: no WMI (PNP0C14) device found");
|
||||
}
|
||||
registry
|
||||
}
|
||||
|
||||
fn enumerate_event_guids(&self, context: &AcpiContext, path: &str) {
|
||||
let full = format!("{path}._WDG");
|
||||
let Ok(name) = AmlName::from_str(&full) else {
|
||||
warn!("acpid: invalid _WDG path {}", full);
|
||||
return;
|
||||
};
|
||||
let value = match context.aml_eval(name, Vec::new()) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
warn!("acpid: _WDG evaluation failed at {}: {:?}", path, err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let buffer = match value {
|
||||
AmlSerdeValue::Buffer(b) => b,
|
||||
_ => {
|
||||
warn!("acpid: _WDG at {} is not a buffer", path);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut notify_ids = Vec::new();
|
||||
for chunk in buffer.chunks(GuidBlock::SIZE) {
|
||||
let Some(block) = GuidBlock::parse(chunk) else {
|
||||
continue;
|
||||
};
|
||||
if block.is_event() {
|
||||
notify_ids.push(block.notify_id);
|
||||
}
|
||||
}
|
||||
let count = notify_ids.len();
|
||||
*self.event_notify_ids.write().unwrap() = notify_ids;
|
||||
info!(
|
||||
"acpid: WMI enumerated {} event GUID(s) from _WDG ({} total bytes)",
|
||||
count,
|
||||
buffer.len()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn handle_notify(&self, context: &AcpiContext, notify_id: u8) -> Option<WmiEvent> {
|
||||
if !self.has_notify_id(notify_id) {
|
||||
return None;
|
||||
}
|
||||
let path = self.device_path()?;
|
||||
let full = format!("{path}._WED");
|
||||
let Ok(name) = AmlName::from_str(&full) else {
|
||||
return None;
|
||||
};
|
||||
let value = context
|
||||
.aml_eval(name, vec![AmlSerdeValue::Integer(notify_id as u64)])
|
||||
.ok()?;
|
||||
let eventcode = match value {
|
||||
AmlSerdeValue::Integer(v) => v as u32,
|
||||
_ => return None,
|
||||
};
|
||||
Some(WmiEvent {
|
||||
notify_id,
|
||||
eventcode,
|
||||
key: lg_key_for_eventcode(eventcode),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn guid_block_parse_event() {
|
||||
let mut data = [0u8; 20];
|
||||
data[0] = 0x48;
|
||||
data[16] = 0x2A;
|
||||
data[18] = 0x01;
|
||||
data[19] = ACPI_WMI_EVENT;
|
||||
let block = GuidBlock::parse(&data).unwrap();
|
||||
assert_eq!(block.guid[0], 0x48);
|
||||
assert_eq!(block.notify_id, 0x2A);
|
||||
assert_eq!(block.instance_count, 1);
|
||||
assert!(block.is_event());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guid_block_parse_not_event() {
|
||||
let data = [0u8; 20];
|
||||
let block = GuidBlock::parse(&data).unwrap();
|
||||
assert!(!block.is_event());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guid_block_too_short() {
|
||||
assert!(GuidBlock::parse(&[0u8; 19]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keymap_covers_lg_codes() {
|
||||
assert_eq!(lg_key_for_eventcode(0x10000000), "kbd-backlight");
|
||||
assert_eq!(lg_key_for_eventcode(0x74), "touchpad-toggle");
|
||||
assert_eq!(lg_key_for_eventcode(0x70), "lg-control-panel");
|
||||
assert_eq!(lg_key_for_eventcode(0xf020000), "read-mode");
|
||||
assert_eq!(lg_key_for_eventcode(0xdead), "unknown");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
//! AER and PCIe-native-hotplug event producers.
|
||||
//!
|
||||
//! A poller thread scans every enumerated device every 500 ms:
|
||||
//! - AER: reads the PCIe AER extended capability's uncorrectable and
|
||||
//! correctable error status registers, emits one line per event, and
|
||||
//! write-1-to-clears the bits (the registers are W1C by spec).
|
||||
//! - pciehp: reads the Slot Status register of hot-plug-capable ports and
|
||||
//! emits one line per event bit (presence detect, DLL state, attention
|
||||
//! button, MRL sensor, power fault), then W1C-clears them.
|
||||
//!
|
||||
//! Events accumulate in capped in-memory logs served read-only as
|
||||
//! `/scheme/pci/aer` and `/scheme/pci/pciehp`.
|
||||
//!
|
||||
//! # Sequence numbers (dedup protocol)
|
||||
//!
|
||||
//! Every emitted event line is prefixed with `seq=<n>:` where `<n>` is a
|
||||
//! process-global monotonic counter (`AtomicU64`, lock-free). Consumers use
|
||||
//! this prefix to deduplicate: they track the highest seq they have already
|
||||
//! processed and only emit events whose seq is strictly greater. This
|
||||
//! replaces the old content-hash approach which was order-dependent and
|
||||
//! silently dropped events whose hash fell below the running maximum.
|
||||
//!
|
||||
//! The `high_water_mark` tracks the highest seq ever issued, even after the
|
||||
//! corresponding line has been evicted from the ring buffer. This lets
|
||||
//! consumers query `/scheme/pci/aer_seq` / `/scheme/pci/pciehp_seq` for an
|
||||
//! atomic "what's the latest?" check without parsing the whole log.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{debug, info};
|
||||
use pci_types::{ConfigRegionAccess, PciAddress};
|
||||
|
||||
use crate::cfg_access::Pcie;
|
||||
|
||||
const MAX_EVENTS: usize = 256;
|
||||
const POLL_INTERVAL_MS: u64 = 500;
|
||||
|
||||
const AER_CAP_ID: u16 = 0x0001;
|
||||
const AER_UNCORRECTABLE_STATUS: u16 = 0x04;
|
||||
const AER_CORRECTABLE_STATUS: u16 = 0x10;
|
||||
|
||||
const PCIE_CAP_ID: u8 = 0x10;
|
||||
const SLOT_CAPABILITIES: u16 = 0x0C;
|
||||
const SLOT_CONTROL_STATUS: u16 = 0x18;
|
||||
const SLOT_CAP_HOT_PLUG_CAPABLE: u32 = 1 << 6;
|
||||
|
||||
const SLTSTA_ATTENTION_BUTTON: u32 = 1 << 0;
|
||||
const SLTSTA_POWER_FAULT: u32 = 1 << 1;
|
||||
const SLTSTA_MRL_SENSOR: u32 = 1 << 2;
|
||||
const SLTSTA_PRESENCE_DETECT: u32 = 1 << 3;
|
||||
const SLTSTA_DLL_STATE: u32 = 1 << 5;
|
||||
|
||||
|
||||
fn scheme_name(addr: &PciAddress) -> String {
|
||||
format!("{}", addr).replace(':', "--")
|
||||
}
|
||||
|
||||
pub struct EventLog {
|
||||
aer: Mutex<VecDeque<String>>,
|
||||
pciehp: Mutex<VecDeque<String>>,
|
||||
seq_counter: AtomicU64,
|
||||
high_water_mark: AtomicU64,
|
||||
}
|
||||
|
||||
impl EventLog {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
aer: Mutex::new(VecDeque::new()),
|
||||
pciehp: Mutex::new(VecDeque::new()),
|
||||
seq_counter: AtomicU64::new(0),
|
||||
high_water_mark: AtomicU64::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate the next monotonic sequence number and advance
|
||||
/// `high_water_mark` if necessary. The counter starts at 1 so seq 0
|
||||
/// is never a valid event (consumers can safely treat seq 0 as
|
||||
/// "nothing seen yet").
|
||||
fn next_seq(&self) -> u64 {
|
||||
let seq = self.seq_counter.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
let mut current = self.high_water_mark.load(Ordering::Relaxed);
|
||||
while seq > current {
|
||||
match self.high_water_mark.compare_exchange_weak(
|
||||
current,
|
||||
seq,
|
||||
Ordering::Relaxed,
|
||||
Ordering::Relaxed,
|
||||
) {
|
||||
Ok(_) => break,
|
||||
Err(actual) => current = actual,
|
||||
}
|
||||
}
|
||||
seq
|
||||
}
|
||||
|
||||
/// Returns the highest sequence number ever issued by this `EventLog`,
|
||||
/// even if the corresponding event line has been evicted from the ring
|
||||
/// buffer. Returns 0 when no events have been emitted yet.
|
||||
pub fn latest_seq(&self) -> u64 {
|
||||
self.high_water_mark.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn push(&self, log: &Mutex<VecDeque<String>>, line: String) {
|
||||
let seq = self.next_seq();
|
||||
let prefixed = format!("seq={seq}:{line}");
|
||||
let mut queue = log.lock().unwrap_or_else(|e| e.into_inner());
|
||||
if queue.len() >= MAX_EVENTS {
|
||||
queue.pop_front();
|
||||
}
|
||||
queue.push_back(prefixed);
|
||||
}
|
||||
|
||||
fn snapshot(log: &Mutex<VecDeque<String>>) -> String {
|
||||
let queue = log.lock().unwrap_or_else(|e| e.into_inner());
|
||||
queue.iter().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn push_aer(&self, line: String) {
|
||||
self.push(&self.aer, line);
|
||||
}
|
||||
|
||||
pub fn push_pciehp(&self, line: String) {
|
||||
self.push(&self.pciehp, line);
|
||||
}
|
||||
|
||||
pub fn aer_snapshot(&self) -> String {
|
||||
Self::snapshot(&self.aer)
|
||||
}
|
||||
|
||||
pub fn pciehp_snapshot(&self) -> String {
|
||||
Self::snapshot(&self.pciehp)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_event_poller(
|
||||
pcie: Arc<Pcie>,
|
||||
devices: Vec<PciAddress>,
|
||||
log: Arc<EventLog>,
|
||||
) -> thread::JoinHandle<()> {
|
||||
thread::Builder::new()
|
||||
.name("pcid-events".to_string())
|
||||
.spawn(move || {
|
||||
info!(
|
||||
"pcid-events: AER/pciehp poller started ({} devices, {} ms poll)",
|
||||
devices.len(),
|
||||
POLL_INTERVAL_MS
|
||||
);
|
||||
loop {
|
||||
poll_aer(&pcie, &devices, &log);
|
||||
poll_pciehp(&pcie, &devices, &log);
|
||||
thread::sleep(Duration::from_millis(POLL_INTERVAL_MS));
|
||||
}
|
||||
})
|
||||
.expect("spawn pcid event poller")
|
||||
}
|
||||
|
||||
fn poll_aer(pcie: &Pcie, devices: &[PciAddress], log: &EventLog) {
|
||||
for addr in devices {
|
||||
let Some(cap_off) = find_extended_capability(pcie, *addr, AER_CAP_ID) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let uncorrectable = unsafe { pcie.read(*addr, cap_off + AER_UNCORRECTABLE_STATUS) };
|
||||
if uncorrectable != 0 && uncorrectable != u32::MAX {
|
||||
debug!("AER: uncorrectable error on {addr}: 0x{uncorrectable:08x}");
|
||||
log.push_aer(format!(
|
||||
"severity=NonFatal device={} raw=uncorrectable_status=0x{uncorrectable:08x}\n", scheme_name(addr)
|
||||
));
|
||||
unsafe { pcie.write(*addr, cap_off + AER_UNCORRECTABLE_STATUS, uncorrectable) };
|
||||
}
|
||||
|
||||
let correctable = unsafe { pcie.read(*addr, cap_off + AER_CORRECTABLE_STATUS) };
|
||||
if correctable != 0 && correctable != u32::MAX {
|
||||
debug!("AER: correctable error on {addr}: 0x{correctable:08x}");
|
||||
log.push_aer(format!(
|
||||
"severity=Correctable device={} raw=correctable_status=0x{correctable:08x}\n", scheme_name(addr)
|
||||
));
|
||||
unsafe { pcie.write(*addr, cap_off + AER_CORRECTABLE_STATUS, correctable) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_pciehp(pcie: &Pcie, devices: &[PciAddress], log: &EventLog) {
|
||||
for addr in devices {
|
||||
let Some(cap_off) = find_standard_capability(pcie, *addr, PCIE_CAP_ID) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let slot_capabilities = unsafe { pcie.read(*addr, cap_off + SLOT_CAPABILITIES) };
|
||||
if slot_capabilities == u32::MAX || slot_capabilities & SLOT_CAP_HOT_PLUG_CAPABLE == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let status = unsafe { pcie.read(*addr, cap_off + SLOT_CONTROL_STATUS) } >> 16;
|
||||
if status == 0 || status == 0xFFFF {
|
||||
continue;
|
||||
}
|
||||
|
||||
if status & SLTSTA_ATTENTION_BUTTON != 0 {
|
||||
log.push_pciehp(format!("kind=attention_button device={}\n", scheme_name(addr)));
|
||||
}
|
||||
if status & SLTSTA_POWER_FAULT != 0 {
|
||||
log.push_pciehp(format!("kind=power_fault device={}\n", scheme_name(addr)));
|
||||
}
|
||||
if status & SLTSTA_MRL_SENSOR != 0 {
|
||||
log.push_pciehp(format!("kind=mrl_sensor device={}\n", scheme_name(addr)));
|
||||
}
|
||||
if status & SLTSTA_PRESENCE_DETECT != 0 {
|
||||
log.push_pciehp(format!("kind=presence_detect device={}\n", scheme_name(addr)));
|
||||
}
|
||||
if status & SLTSTA_DLL_STATE != 0 {
|
||||
log.push_pciehp(format!("kind=dll_state device={}\n", scheme_name(addr)));
|
||||
}
|
||||
|
||||
// Write-1-to-clear the event bits in Slot Status (upper word of the
|
||||
// Slot Control/Status dword), preserving the current Slot Control
|
||||
// value in the lower word.
|
||||
let control_status = unsafe { pcie.read(*addr, cap_off + SLOT_CONTROL_STATUS) };
|
||||
let clear_value = (control_status & 0x0000_FFFF) | (status << 16);
|
||||
unsafe { pcie.write(*addr, cap_off + SLOT_CONTROL_STATUS, clear_value) };
|
||||
}
|
||||
}
|
||||
|
||||
fn find_extended_capability(pcie: &Pcie, addr: PciAddress, cap_id: u16) -> Option<u16> {
|
||||
let mut offset = 0x100u16;
|
||||
for _ in 0..48 {
|
||||
if offset == 0 {
|
||||
return None;
|
||||
}
|
||||
let header = unsafe { pcie.read(addr, offset) };
|
||||
if header == u32::MAX || header == 0 {
|
||||
return None;
|
||||
}
|
||||
if (header & 0xFFFF) as u16 == cap_id {
|
||||
return Some(offset);
|
||||
}
|
||||
offset = ((header >> 20) & 0xFFF) as u16;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn find_standard_capability(pcie: &Pcie, addr: PciAddress, cap_id: u8) -> Option<u16> {
|
||||
let command_status = unsafe { pcie.read(addr, 0x04) };
|
||||
if (command_status >> 16) & 0x10 == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut ptr = (unsafe { pcie.read(addr, 0x34) } & 0xFC) as u16;
|
||||
for _ in 0..48 {
|
||||
if ptr == 0 {
|
||||
return None;
|
||||
}
|
||||
let entry = unsafe { pcie.read(addr, ptr) };
|
||||
if (entry & 0xFF) as u8 == cap_id {
|
||||
return Some(ptr);
|
||||
}
|
||||
ptr = ((entry >> 8) & 0xFC) as u16;
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#![feature(non_exhaustive_omitted_patterns_lint)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use log::{debug, info, trace, warn};
|
||||
use pci_types::capability::PciCapability;
|
||||
@@ -18,6 +19,7 @@ use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, Pci
|
||||
|
||||
mod cfg_access;
|
||||
mod driver_handler;
|
||||
mod events;
|
||||
mod scheme;
|
||||
|
||||
pub struct Func {
|
||||
@@ -284,11 +286,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
let pcie = Pcie::new();
|
||||
let pcie = Arc::new(Pcie::new());
|
||||
|
||||
info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV");
|
||||
|
||||
let mut scheme = scheme::PciScheme::new(pcie);
|
||||
let mut scheme = scheme::PciScheme::new(Arc::clone(&pcie));
|
||||
let socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket");
|
||||
let handler = Blocking::new(&socket, 16);
|
||||
|
||||
@@ -342,6 +344,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
}
|
||||
debug!("Enumeration complete, now starting pci scheme");
|
||||
|
||||
let event_log = Arc::new(events::EventLog::new());
|
||||
let devices: Vec<PciAddress> = scheme.tree.keys().copied().collect();
|
||||
scheme.set_event_log(Arc::clone(&event_log));
|
||||
let _events_thread = events::spawn_event_poller(Arc::clone(&pcie), devices, event_log);
|
||||
|
||||
register_sync_scheme(&socket, "pci", &mut scheme)
|
||||
.expect("failed to register pci scheme to namespace");
|
||||
|
||||
|
||||
+83
-13
@@ -6,7 +6,7 @@ use redox_scheme::{CallerCtx, OpenResult};
|
||||
use scheme_utils::HandleMap;
|
||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
|
||||
use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT};
|
||||
use syscall::flag::{MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_STAT};
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
use syscall::ENOLCK;
|
||||
|
||||
@@ -14,8 +14,9 @@ use crate::cfg_access::Pcie;
|
||||
|
||||
pub struct PciScheme {
|
||||
handles: HandleMap<HandleWrapper>,
|
||||
pub pcie: Pcie,
|
||||
pub pcie: std::sync::Arc<Pcie>,
|
||||
pub tree: BTreeMap<PciAddress, crate::Func>,
|
||||
events: std::sync::Arc<crate::events::EventLog>,
|
||||
}
|
||||
enum Handle {
|
||||
TopLevel { entries: Vec<String> },
|
||||
@@ -23,6 +24,10 @@ enum Handle {
|
||||
Device,
|
||||
Config { addr: PciAddress },
|
||||
Channel { addr: PciAddress, st: ChannelState },
|
||||
Aer,
|
||||
Pciehp,
|
||||
AerSeq,
|
||||
PciehpSeq,
|
||||
SchemeRoot,
|
||||
}
|
||||
struct HandleWrapper {
|
||||
@@ -33,7 +38,13 @@ impl Handle {
|
||||
fn is_file(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Access | Self::Config { .. } | Self::Channel { .. }
|
||||
Self::Access
|
||||
| Self::Config { .. }
|
||||
| Self::Channel { .. }
|
||||
| Self::Aer
|
||||
| Self::Pciehp
|
||||
| Self::AerSeq
|
||||
| Self::PciehpSeq
|
||||
)
|
||||
}
|
||||
fn is_dir(&self) -> bool {
|
||||
@@ -95,16 +106,27 @@ impl SchemeSync for PciScheme {
|
||||
let path = path.trim_matches('/');
|
||||
|
||||
let handle = if path.is_empty() {
|
||||
Handle::TopLevel {
|
||||
entries: self
|
||||
.tree
|
||||
.iter()
|
||||
// FIXME remove replacement of : once the old scheme format is no longer supported.
|
||||
.map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
let mut entries: Vec<String> = self
|
||||
.tree
|
||||
.iter()
|
||||
// FIXME remove replacement of : once the old scheme format is no longer supported.
|
||||
.map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
||||
.collect::<Vec<_>>();
|
||||
entries.push("aer".to_string());
|
||||
entries.push("aer_seq".to_string());
|
||||
entries.push("pciehp".to_string());
|
||||
entries.push("pciehp_seq".to_string());
|
||||
Handle::TopLevel { entries }
|
||||
} else if path == "access" {
|
||||
Handle::Access
|
||||
} else if path == "aer" {
|
||||
Handle::Aer
|
||||
} else if path == "aer_seq" {
|
||||
Handle::AerSeq
|
||||
} else if path == "pciehp" {
|
||||
Handle::Pciehp
|
||||
} else if path == "pciehp_seq" {
|
||||
Handle::PciehpSeq
|
||||
} else {
|
||||
let idx = path.find('/').unwrap_or(path.len());
|
||||
let (addr_str, after) = path.split_at(idx);
|
||||
@@ -142,6 +164,10 @@ impl SchemeSync for PciScheme {
|
||||
Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
|
||||
Handle::Config { .. } => (config_size, MODE_CHR | 0o600),
|
||||
Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
|
||||
Handle::Aer => (self.events.aer_snapshot().len(), MODE_FILE | 0o444),
|
||||
Handle::Pciehp => (self.events.pciehp_snapshot().len(), MODE_FILE | 0o444),
|
||||
Handle::AerSeq => (self.events.latest_seq().to_string().len(), MODE_FILE | 0o444),
|
||||
Handle::PciehpSeq => (self.events.latest_seq().to_string().len(), MODE_FILE | 0o444),
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
};
|
||||
stat.st_size = len as u64;
|
||||
@@ -186,6 +212,39 @@ impl SchemeSync for PciScheme {
|
||||
addr: _,
|
||||
ref mut st,
|
||||
} => Self::read_channel(st, buf),
|
||||
Handle::Aer => {
|
||||
let data = self.events.aer_snapshot();
|
||||
let bytes = data.as_bytes();
|
||||
let offset = usize::try_from(_offset).map_err(|_| Error::new(EINVAL))?;
|
||||
if offset >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let count = (bytes.len() - offset).min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
||||
Ok(count)
|
||||
}
|
||||
Handle::Pciehp => {
|
||||
let data = self.events.pciehp_snapshot();
|
||||
let bytes = data.as_bytes();
|
||||
let offset = usize::try_from(_offset).map_err(|_| Error::new(EINVAL))?;
|
||||
if offset >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let count = (bytes.len() - offset).min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
||||
Ok(count)
|
||||
}
|
||||
Handle::AerSeq | Handle::PciehpSeq => {
|
||||
let data = self.events.latest_seq().to_string();
|
||||
let bytes = data.as_bytes();
|
||||
let offset = usize::try_from(_offset).map_err(|_| Error::new(EINVAL))?;
|
||||
if offset >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let count = (bytes.len() - offset).min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
||||
Ok(count)
|
||||
}
|
||||
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||
_ => Err(Error::new(EBADF)),
|
||||
}
|
||||
@@ -219,7 +278,13 @@ impl SchemeSync for PciScheme {
|
||||
return Ok(buf);
|
||||
}
|
||||
Handle::Device => DEVICE_CONTENTS,
|
||||
Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => {
|
||||
Handle::Access
|
||||
| Handle::Config { .. }
|
||||
| Handle::Channel { .. }
|
||||
| Handle::Aer
|
||||
| Handle::Pciehp
|
||||
| Handle::AerSeq
|
||||
| Handle::PciehpSeq => {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
}
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
@@ -383,11 +448,16 @@ impl PciScheme {
|
||||
if self.pcie.has_ecam() { 4096 } else { 256 }
|
||||
}
|
||||
|
||||
pub fn new(pcie: Pcie) -> Self {
|
||||
pub fn set_event_log(&mut self, events: std::sync::Arc<crate::events::EventLog>) {
|
||||
self.events = events;
|
||||
}
|
||||
|
||||
pub fn new(pcie: std::sync::Arc<Pcie>) -> Self {
|
||||
Self {
|
||||
handles: HandleMap::new(),
|
||||
pcie,
|
||||
tree: BTreeMap::new(),
|
||||
events: std::sync::Arc::new(crate::events::EventLog::new()),
|
||||
}
|
||||
}
|
||||
fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
|
||||
|
||||
+23
-10
@@ -130,16 +130,6 @@ fn main() {
|
||||
scheduler
|
||||
.schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned()));
|
||||
scheduler.step(&mut unit_store, &mut init_config);
|
||||
// NOTE: init/daemon stdio is deliberately kept on the kernel console
|
||||
// rather than being redirected into /scheme/log. Redirecting made the boot
|
||||
// "go dark" after logd on text/recovery ISOs (bare/mini): the graphical
|
||||
// bootlog (fbbootlogd, VT1) does not reliably surface /scheme/log to the
|
||||
// framebuffer, and logd's console mirror is racy, so the console showed
|
||||
// nothing between logd and the login VT — indistinguishable from a hang.
|
||||
// Keeping stdio on the console makes the full boot observable on
|
||||
// `-serial` and the framebuffer. (A quiet/splash mode for the graphical
|
||||
// full ISO can re-enable the redirect once fbbootlogd rendering is fixed.)
|
||||
let _ = switch_stdio;
|
||||
|
||||
let runtime_target = UnitId("00_runtime.target".to_owned());
|
||||
scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone());
|
||||
@@ -155,6 +145,29 @@ fn main() {
|
||||
Path::new("/usr"),
|
||||
Path::new("/etc"),
|
||||
);
|
||||
|
||||
// Pin init/daemon stdio to the dedicated boot-log VT (fbcon/1) before the
|
||||
// /usr units run. Rationale: stdio otherwise stays on the kernel console
|
||||
// (/scheme/debug), which fbcond renders onto the *active* VT. Once the
|
||||
// `29_activate_console` unit switches the active VT to 2 for the getty
|
||||
// login, any daemon that logs afterwards — late async driver attach
|
||||
// (i2c-hidd, evdevd, dw-acpi-i2cd) or smolnetd's bounded NIC wait — paints
|
||||
// its output on top of the login banner/prompt, so the prompt is never the
|
||||
// last thing on screen. Pinning daemon output to VT1 (a fixed VT, not the
|
||||
// active one) keeps the boot log fully visible — VT1 is the active console
|
||||
// during boot, and fbcond still mirrors it to the serial line — while
|
||||
// leaving VT2 clean for a login prompt that lands last.
|
||||
//
|
||||
// This is deliberately NOT the old `/scheme/log` redirect that made boot
|
||||
// "go dark": that depended on fbbootlogd surfacing /scheme/log to the
|
||||
// framebuffer (racy/unreliable). A real VT renders directly, so full-boot
|
||||
// observability is preserved. Best-effort: if fbcon/1 cannot be opened
|
||||
// (e.g. fbcond absent on a headless/serial-only boot), stdio stays on the
|
||||
// kernel console and the boot remains observable exactly as before.
|
||||
if let Err(err) = switch_stdio("/scheme/fbcon/1") {
|
||||
eprintln!("INIT: keeping stdio on kernel console (fbcon/1 unavailable: {err})");
|
||||
}
|
||||
|
||||
{
|
||||
// FIXME introduce multi-user.target unit and replace the config dir iteration
|
||||
// scheduler.schedule_start_and_report_errors(&mut unit_store, UnitId("multi-user.target".to_owned()));
|
||||
|
||||
+47
-11
@@ -35,22 +35,58 @@ fn get_all_network_adapters() -> Result<Vec<String>> {
|
||||
adapters.push(scheme);
|
||||
}
|
||||
|
||||
if adapters.is_empty() {
|
||||
// No NIC present is a normal state on a diskless/headless/bare machine,
|
||||
// or simply before an unsupported NIC's driver attaches. Report it as
|
||||
// info and let the caller exit cleanly instead of emitting a spurious
|
||||
// ERROR + non-zero exit on every boot of a NIC-less system.
|
||||
info!("no network adapter found; network stack idle");
|
||||
return Ok(adapters);
|
||||
}
|
||||
info!("Found {} network adapter(s): {:?}", adapters.len(), adapters);
|
||||
Ok(adapters)
|
||||
}
|
||||
|
||||
/// Wait for at least one NIC driver to register its `network.*` scheme.
|
||||
///
|
||||
/// driver-manager attaches drivers ASYNCHRONOUSLY, so on a real boot the NIC's
|
||||
/// `network.*` scheme does not exist yet when smolnetd starts (10_smolnetd only
|
||||
/// requires_weak that driver-manager has *started*). The previous code scanned
|
||||
/// `/scheme` exactly once and, finding nothing, exited — losing the race with
|
||||
/// virtio-netd and leaving `/scheme/netcfg` absent, so dhcpd/netctl failed with
|
||||
/// "Can't open /scheme/netcfg/ifaces/eth0/mac". Poll for a bounded window
|
||||
/// instead. smolnetd is oneshot_async, so this wait never blocks init/boot; a
|
||||
/// genuinely NIC-less machine simply gets an empty list after the window and
|
||||
/// exits cleanly.
|
||||
fn wait_for_network_adapters() -> Vec<String> {
|
||||
use std::time::{Duration, Instant};
|
||||
let deadline = Instant::now() + Duration::from_secs(20);
|
||||
let mut logged_wait = false;
|
||||
loop {
|
||||
match get_all_network_adapters() {
|
||||
Ok(adapters) if !adapters.is_empty() => {
|
||||
info!("Found {} network adapter(s): {:?}", adapters.len(), adapters);
|
||||
return adapters;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if Instant::now() >= deadline {
|
||||
return Vec::new();
|
||||
}
|
||||
if !logged_wait {
|
||||
info!("no network adapter yet; waiting for a NIC driver to attach...");
|
||||
logged_wait = true;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(250));
|
||||
}
|
||||
}
|
||||
|
||||
fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||
let adapters = get_all_network_adapters()?;
|
||||
let adapters = wait_for_network_adapters();
|
||||
if adapters.is_empty() {
|
||||
// Nothing to service; exit cleanly (see get_all_network_adapters).
|
||||
// No `network.*` scheme registered within the wait window. Two cases:
|
||||
// (a) genuinely NIC-less machine -> expected, harmless;
|
||||
// (b) a NIC IS present but its driver never registered `network.*`
|
||||
// -> a driver bug or a STALE/broken NIC driver binary that no
|
||||
// longer speaks the current scheme protocol.
|
||||
// If this box has a NIC, this is case (b): rebuild the net driver
|
||||
// (virtio-netd / e1000d / rtl8168d). Exit cleanly either way (smolnetd
|
||||
// is oneshot_async, so waiting never blocked boot/login).
|
||||
warn!(
|
||||
"no network.* scheme after waiting; network stack idle. If a NIC is \
|
||||
present, its driver failed to register it (driver bug or stale driver binary)."
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
trace!("found {} network adapter(s)", adapters.len());
|
||||
|
||||
Reference in New Issue
Block a user