Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c7f6172a2 | |||
| d98330a7de | |||
| 29166263bb | |||
| e54484e553 | |||
| 2e5e516587 | |||
| b906ad688a | |||
| 0ca545d35a | |||
| 5a43628d58 |
+178
-10
@@ -1,3 +1,4 @@
|
|||||||
|
use alloc::collections::VecDeque;
|
||||||
use alloc::string::{String, ToString};
|
use alloc::string::{String, ToString};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
@@ -17,12 +18,13 @@ use redox_rt::proc::FdGuard;
|
|||||||
// local/docs/INITNSMGR-CONCURRENCY-DESIGN.md.
|
// local/docs/INITNSMGR-CONCURRENCY-DESIGN.md.
|
||||||
use redox_rt::sync::Mutex;
|
use redox_rt::sync::Mutex;
|
||||||
use redox_scheme::{
|
use redox_scheme::{
|
||||||
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
|
CallerCtx, Id, IntoTag, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior,
|
||||||
scheme::{SchemeState, SchemeSync},
|
Socket, Tag,
|
||||||
|
scheme::{Op, OpFdPathLike, SchemeState, SchemeSync},
|
||||||
};
|
};
|
||||||
use syscall::Stat;
|
use syscall::Stat;
|
||||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||||
use syscall::{CallFlags, FobtainFdFlags, error::*, schemev2::NewFdFlags};
|
use syscall::{CallFlags, FobtainFdFlags, error::*, flag::O_NONBLOCK, schemev2::NewFdFlags};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Namespace {
|
struct Namespace {
|
||||||
@@ -102,12 +104,23 @@ enum Handle {
|
|||||||
List(NamespaceAccess),
|
List(NamespaceAccess),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PendingOpen {
|
||||||
|
tag: Tag,
|
||||||
|
cap_fd: Arc<FdGuard>,
|
||||||
|
reference: String,
|
||||||
|
flags: usize,
|
||||||
|
fcntl_flags: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_PENDING_OPENS: usize = 64;
|
||||||
|
|
||||||
pub struct NamespaceScheme<'sock> {
|
pub struct NamespaceScheme<'sock> {
|
||||||
socket: &'sock Socket,
|
socket: &'sock Socket,
|
||||||
handles: HashMap<usize, Handle>,
|
handles: HashMap<usize, Handle>,
|
||||||
root_namespace: Namespace,
|
root_namespace: Namespace,
|
||||||
next_id: usize,
|
next_id: usize,
|
||||||
scheme_creation_cap: FdGuard,
|
scheme_creation_cap: FdGuard,
|
||||||
|
pending_opens: VecDeque<PendingOpen>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
|
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
|
||||||
@@ -124,6 +137,7 @@ impl<'sock> NamespaceScheme<'sock> {
|
|||||||
root_namespace: Namespace { schemes },
|
root_namespace: Namespace { schemes },
|
||||||
next_id: 0,
|
next_id: 0,
|
||||||
scheme_creation_cap,
|
scheme_creation_cap,
|
||||||
|
pending_opens: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +230,120 @@ impl<'sock> NamespaceScheme<'sock> {
|
|||||||
self.next_id += 1;
|
self.next_id += 1;
|
||||||
Ok(next_id)
|
Ok(next_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_scheme_open_nonblock(
|
||||||
|
&mut self,
|
||||||
|
openat: OpFdPathLike<usize>,
|
||||||
|
) -> Option<Response> {
|
||||||
|
let ns_fd = openat.fd;
|
||||||
|
let fcntl_flags = openat.fcntl_flags;
|
||||||
|
let flags = *openat.flags();
|
||||||
|
let path_owned = openat.path().to_string();
|
||||||
|
let tag = openat.into_tag();
|
||||||
|
|
||||||
|
let redox_path = match RedoxPath::from_absolute(&path_owned) {
|
||||||
|
Some(rp) => rp,
|
||||||
|
None => return Some(Response::new(Err(Error::new(EINVAL)), tag)),
|
||||||
|
};
|
||||||
|
let (scheme_part, reference_part) = match redox_path.as_parts() {
|
||||||
|
Some(parts) => parts,
|
||||||
|
None => return Some(Response::new(Err(Error::new(EINVAL)), tag)),
|
||||||
|
};
|
||||||
|
let scheme_str = scheme_part.as_ref().to_string();
|
||||||
|
let reference_str = reference_part.as_ref().to_string();
|
||||||
|
|
||||||
|
let ns_access = match self.handles.get(&ns_fd) {
|
||||||
|
Some(Handle::Access(access)) => access.clone(),
|
||||||
|
_ => return Some(Response::new(Err(Error::new(ENOENT)), tag)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let cap_fd = {
|
||||||
|
let ns = ns_access.namespace.lock();
|
||||||
|
match ns.get_scheme_fd(&scheme_str) {
|
||||||
|
Some(arc_fd) => arc_fd.clone(),
|
||||||
|
None => {
|
||||||
|
log::info!("Scheme {:?} not found in namespace", scheme_str);
|
||||||
|
return Some(Response::new(Err(Error::new(ENODEV)), tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let client_nonblock = flags & O_NONBLOCK != 0;
|
||||||
|
let nonblock_flags = flags | O_NONBLOCK;
|
||||||
|
|
||||||
|
match syscall::openat(
|
||||||
|
cap_fd.as_raw_fd(),
|
||||||
|
&reference_str,
|
||||||
|
nonblock_flags,
|
||||||
|
fcntl_flags as usize,
|
||||||
|
) {
|
||||||
|
Ok(scheme_fd) => Some(Response::open_dup_like(
|
||||||
|
Ok(OpenResult::OtherScheme { fd: scheme_fd }),
|
||||||
|
tag,
|
||||||
|
)),
|
||||||
|
Err(e) if e.errno == EAGAIN && !client_nonblock => {
|
||||||
|
if self.pending_opens.len() >= MAX_PENDING_OPENS {
|
||||||
|
let result = syscall::openat(
|
||||||
|
cap_fd.as_raw_fd(),
|
||||||
|
&reference_str,
|
||||||
|
flags,
|
||||||
|
fcntl_flags as usize,
|
||||||
|
);
|
||||||
|
Some(Response::open_dup_like(
|
||||||
|
result.map(|fd| OpenResult::OtherScheme { fd }),
|
||||||
|
tag,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
self.pending_opens.push_back(PendingOpen {
|
||||||
|
tag,
|
||||||
|
cap_fd,
|
||||||
|
reference: reference_str,
|
||||||
|
flags,
|
||||||
|
fcntl_flags,
|
||||||
|
});
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => Some(Response::open_dup_like(Err(e), tag)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retry_pending_opens(&mut self, socket: &Socket) {
|
||||||
|
let mut pending = mem::take(&mut self.pending_opens);
|
||||||
|
|
||||||
|
while let Some(p) = pending.pop_front() {
|
||||||
|
let nonblock_flags = p.flags | O_NONBLOCK;
|
||||||
|
match syscall::openat(
|
||||||
|
p.cap_fd.as_raw_fd(),
|
||||||
|
&p.reference,
|
||||||
|
nonblock_flags,
|
||||||
|
p.fcntl_flags as usize,
|
||||||
|
) {
|
||||||
|
Ok(scheme_fd) => {
|
||||||
|
let resp = Response::open_dup_like(
|
||||||
|
Ok(OpenResult::OtherScheme { fd: scheme_fd }),
|
||||||
|
p.tag,
|
||||||
|
);
|
||||||
|
let _ = socket.write_response(resp, SignalBehavior::Restart);
|
||||||
|
}
|
||||||
|
Err(e) if e.errno == EAGAIN => {
|
||||||
|
pending.push_back(p);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let resp = Response::open_dup_like(Err(e), p.tag);
|
||||||
|
let _ = socket.write_response(resp, SignalBehavior::Restart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.pending_opens = pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_pending_open(&mut self, id: Id) {
|
||||||
|
if let Some(idx) = self.pending_opens.iter().position(|p| p.tag.id() == id) {
|
||||||
|
let _ = self.pending_opens.remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
|
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
|
||||||
@@ -506,6 +634,20 @@ where
|
|||||||
T::from_le_bytes_slice(buffer)
|
T::from_le_bytes_slice(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` when the path targets a scheme other than `namespace` itself.
|
||||||
|
/// Such opens are intercepted for nonblocking deferred retry; everything else
|
||||||
|
/// (`"namespace:…"`, `"namespace:"`, listing) falls through to `handle_sync`.
|
||||||
|
fn is_scheme_forwarded(path: &str) -> bool {
|
||||||
|
let Some(rp) = RedoxPath::from_absolute(path) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some((scheme, _)) = rp.as_parts() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let s = scheme.as_ref();
|
||||||
|
!s.is_empty() && s != "namespace"
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(
|
pub fn run(
|
||||||
sync_pipe: FdGuard,
|
sync_pipe: FdGuard,
|
||||||
socket: Socket,
|
socket: Socket,
|
||||||
@@ -540,16 +682,40 @@ pub fn run(
|
|||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
match req.kind() {
|
match req.kind() {
|
||||||
RequestKind::Call(req) => {
|
RequestKind::Call(call_req) => {
|
||||||
let resp = req.handle_sync(&mut scheme, &mut state);
|
let caller = call_req.caller();
|
||||||
|
let resp_opt = match call_req.op() {
|
||||||
|
Ok(op) => {
|
||||||
|
let intercept = matches!(
|
||||||
|
&op,
|
||||||
|
Op::OpenAt(ref o) if is_scheme_forwarded(o.path())
|
||||||
|
);
|
||||||
|
if intercept {
|
||||||
|
match op {
|
||||||
|
Op::OpenAt(openat) => {
|
||||||
|
scheme.handle_scheme_open_nonblock(openat)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(op.handle_sync(caller, &mut scheme, &mut state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(this) => Some(Response::new(Err(Error::new(ENOSYS)), this)),
|
||||||
|
};
|
||||||
|
|
||||||
if !socket
|
if let Some(resp) = resp_opt {
|
||||||
.write_response(resp, SignalBehavior::Restart)
|
if !socket
|
||||||
.expect("bootstrap: failed to write scheme response to kernel")
|
.write_response(resp, SignalBehavior::Restart)
|
||||||
{
|
.expect("bootstrap: failed to write scheme response to kernel")
|
||||||
break;
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RequestKind::Cancellation(cancel_req) => {
|
||||||
|
scheme.cancel_pending_open(cancel_req.id);
|
||||||
|
}
|
||||||
RequestKind::OnClose { id } => scheme.on_close(id),
|
RequestKind::OnClose { id } => scheme.on_close(id),
|
||||||
RequestKind::SendFd(sendfd_request) => {
|
RequestKind::SendFd(sendfd_request) => {
|
||||||
let result = scheme.on_sendfd(&sendfd_request);
|
let result = scheme.on_sendfd(&sendfd_request);
|
||||||
@@ -564,6 +730,8 @@ pub fn run(
|
|||||||
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheme.retry_pending_opens(&socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
|||||||
+47
-6
@@ -121,12 +121,18 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
|||||||
).subsec_nanos();
|
).subsec_nanos();
|
||||||
|
|
||||||
let socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind udp");
|
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
|
// 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
|
// 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
|
// 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
|
// 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+.
|
// 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_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");
|
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);
|
init_dhcp_header(&mut discover, current_mac, tid);
|
||||||
let disc_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPDISCOVER, OPT_END];
|
let disc_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPDISCOVER, OPT_END];
|
||||||
discover.options[..disc_opts.len()].copy_from_slice(disc_opts);
|
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"); }
|
if verbose { println!("DHCP: Sent Discover"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +201,7 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
|||||||
OPT_END,
|
OPT_END,
|
||||||
];
|
];
|
||||||
request.options[..req_opts.len()].copy_from_slice(req_opts);
|
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"); }
|
if verbose { println!("DHCP: Sent Request"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +229,10 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
|||||||
renew.ciaddr = offer.yiaddr;
|
renew.ciaddr = offer.yiaddr;
|
||||||
let rn_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
let rn_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
||||||
renew.options[..rn_opts.len()].copy_from_slice(rn_opts);
|
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();
|
socket.set_read_timeout(Some(t2.saturating_sub(t1))).ok();
|
||||||
@@ -242,13 +251,12 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
if verbose { println!("DHCP: entering REBIND state"); }
|
if verbose { println!("DHCP: entering REBIND state"); }
|
||||||
let bind_socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind rebind");
|
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();
|
let mut rebind = Dhcp::default();
|
||||||
init_dhcp_header(&mut rebind, current_mac, tid.wrapping_add(2));
|
init_dhcp_header(&mut rebind, current_mac, tid.wrapping_add(2));
|
||||||
rebind.ciaddr = offer.yiaddr;
|
rebind.ciaddr = offer.yiaddr;
|
||||||
let rb_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
let rb_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
|
||||||
rebind.options[..rb_opts.len()].copy_from_slice(rb_opts);
|
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();
|
bind_socket.set_read_timeout(Some(Duration::from_secs(10))).ok();
|
||||||
if let Ok(_) = bind_socket.recv(&mut ack_data) {
|
if let Ok(_) = bind_socket.recv(&mut ack_data) {
|
||||||
if verbose { println!("DHCP: rebound"); }
|
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))
|
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 {
|
impl Default for Dhcp {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Dhcp {
|
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) {
|
if let Err(err) = dhcp(iface, verbose) {
|
||||||
eprintln!("dhcpd: {err}");
|
eprintln!("dhcpd: {err}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
|
|||||||
@@ -442,6 +442,7 @@ pub struct AcpiContext {
|
|||||||
pub fan_devices: RwLock<Vec<String>>,
|
pub fan_devices: RwLock<Vec<String>>,
|
||||||
|
|
||||||
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
|
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
|
||||||
|
pub wmi_registry: RwLock<Option<crate::wmi::WmiRegistry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -576,6 +577,7 @@ impl AcpiContext {
|
|||||||
sleep_button_events: RwLock::new(0),
|
sleep_button_events: RwLock::new(0),
|
||||||
fan_devices: RwLock::new(Vec::new()),
|
fan_devices: RwLock::new(Vec::new()),
|
||||||
wake_registry: RwLock::new(None),
|
wake_registry: RwLock::new(None),
|
||||||
|
wmi_registry: RwLock::new(None),
|
||||||
|
|
||||||
sdt_order: RwLock::new(Vec::new()),
|
sdt_order: RwLock::new(Vec::new()),
|
||||||
dmi: None,
|
dmi: None,
|
||||||
|
|||||||
@@ -178,7 +178,9 @@ impl AmlPhysMemHandler {
|
|||||||
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
|
||||||
Some(libredox::Fd::new(pci_fd.raw()))
|
Some(libredox::Fd::new(pci_fd.raw()))
|
||||||
} else {
|
} 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
|
None
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ mod gpe;
|
|||||||
mod notifications;
|
mod notifications;
|
||||||
mod power_events;
|
mod power_events;
|
||||||
mod wake;
|
mod wake;
|
||||||
|
mod wmi;
|
||||||
|
|
||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
@@ -118,6 +119,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
}
|
}
|
||||||
*acpi_context.wake_registry.write() = Some(wake_registry);
|
*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?
|
// TODO: I/O permission bitmap?
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[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");
|
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 } => {
|
power_events::PowerEvent::Notify { device, value } => {
|
||||||
log::debug!("acpid: device notification {} = {:#x}", 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 {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ pub enum PowerEvent {
|
|||||||
EcQuery(u8),
|
EcQuery(u8),
|
||||||
LidChanged(bool),
|
LidChanged(bool),
|
||||||
Notify { device: String, value: u64 },
|
Notify { device: String, value: u64 },
|
||||||
|
Wmi {
|
||||||
|
notify_id: u8,
|
||||||
|
eventcode: u32,
|
||||||
|
key: &'static str,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ECDT (Embedded Controller Boot Resources Table) payload after the
|
/// 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).
|
// Drain AML notifications (from _Qxx methods and any other Notify).
|
||||||
let lid_path = context.lid_device.read().clone();
|
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() {
|
for (device, value) in context.notifications().drain() {
|
||||||
log::info!("acpid: notify {} = {:#x}", device, value);
|
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 let Some(lid) = &lid_path {
|
||||||
if &device == lid {
|
if &device == lid {
|
||||||
let state = read_lid_state(context);
|
let state = read_lid_state(context);
|
||||||
|
|||||||
@@ -1070,10 +1070,27 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
}
|
}
|
||||||
let new_fd = libredox::Fd::new(new_fd);
|
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() {
|
if self.pci_fd.is_some() {
|
||||||
return Err(Error::new(EINVAL));
|
log::warn!(
|
||||||
} else {
|
"acpid: replacing previously-registered PCI fd; AML symbol cache will rebuild on next request"
|
||||||
self.pci_fd = Some(new_fd);
|
);
|
||||||
|
}
|
||||||
|
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)
|
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)]
|
#![feature(non_exhaustive_omitted_patterns_lint)]
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use log::{debug, info, trace, warn};
|
use log::{debug, info, trace, warn};
|
||||||
use pci_types::capability::PciCapability;
|
use pci_types::capability::PciCapability;
|
||||||
@@ -18,6 +19,7 @@ use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, Pci
|
|||||||
|
|
||||||
mod cfg_access;
|
mod cfg_access;
|
||||||
mod driver_handler;
|
mod driver_handler;
|
||||||
|
mod events;
|
||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
pub struct Func {
|
pub struct Func {
|
||||||
@@ -284,11 +286,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
common::file_level(),
|
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");
|
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 socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket");
|
||||||
let handler = Blocking::new(&socket, 16);
|
let handler = Blocking::new(&socket, 16);
|
||||||
|
|
||||||
@@ -342,6 +344,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
}
|
}
|
||||||
debug!("Enumeration complete, now starting pci scheme");
|
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)
|
register_sync_scheme(&socket, "pci", &mut scheme)
|
||||||
.expect("failed to register pci scheme to namespace");
|
.expect("failed to register pci scheme to namespace");
|
||||||
|
|
||||||
|
|||||||
+83
-13
@@ -6,7 +6,7 @@ use redox_scheme::{CallerCtx, OpenResult};
|
|||||||
use scheme_utils::HandleMap;
|
use scheme_utils::HandleMap;
|
||||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
||||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
|
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::schemev2::NewFdFlags;
|
||||||
use syscall::ENOLCK;
|
use syscall::ENOLCK;
|
||||||
|
|
||||||
@@ -14,8 +14,9 @@ use crate::cfg_access::Pcie;
|
|||||||
|
|
||||||
pub struct PciScheme {
|
pub struct PciScheme {
|
||||||
handles: HandleMap<HandleWrapper>,
|
handles: HandleMap<HandleWrapper>,
|
||||||
pub pcie: Pcie,
|
pub pcie: std::sync::Arc<Pcie>,
|
||||||
pub tree: BTreeMap<PciAddress, crate::Func>,
|
pub tree: BTreeMap<PciAddress, crate::Func>,
|
||||||
|
events: std::sync::Arc<crate::events::EventLog>,
|
||||||
}
|
}
|
||||||
enum Handle {
|
enum Handle {
|
||||||
TopLevel { entries: Vec<String> },
|
TopLevel { entries: Vec<String> },
|
||||||
@@ -23,6 +24,10 @@ enum Handle {
|
|||||||
Device,
|
Device,
|
||||||
Config { addr: PciAddress },
|
Config { addr: PciAddress },
|
||||||
Channel { addr: PciAddress, st: ChannelState },
|
Channel { addr: PciAddress, st: ChannelState },
|
||||||
|
Aer,
|
||||||
|
Pciehp,
|
||||||
|
AerSeq,
|
||||||
|
PciehpSeq,
|
||||||
SchemeRoot,
|
SchemeRoot,
|
||||||
}
|
}
|
||||||
struct HandleWrapper {
|
struct HandleWrapper {
|
||||||
@@ -33,7 +38,13 @@ impl Handle {
|
|||||||
fn is_file(&self) -> bool {
|
fn is_file(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self,
|
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 {
|
fn is_dir(&self) -> bool {
|
||||||
@@ -95,16 +106,27 @@ impl SchemeSync for PciScheme {
|
|||||||
let path = path.trim_matches('/');
|
let path = path.trim_matches('/');
|
||||||
|
|
||||||
let handle = if path.is_empty() {
|
let handle = if path.is_empty() {
|
||||||
Handle::TopLevel {
|
let mut entries: Vec<String> = self
|
||||||
entries: self
|
.tree
|
||||||
.tree
|
.iter()
|
||||||
.iter()
|
// FIXME remove replacement of : once the old scheme format is no longer supported.
|
||||||
// FIXME remove replacement of : once the old scheme format is no longer supported.
|
.map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
||||||
.map(|(addr, _)| format!("{}", addr).replace(':', "--"))
|
.collect::<Vec<_>>();
|
||||||
.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" {
|
} else if path == "access" {
|
||||||
Handle::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 {
|
} else {
|
||||||
let idx = path.find('/').unwrap_or(path.len());
|
let idx = path.find('/').unwrap_or(path.len());
|
||||||
let (addr_str, after) = path.split_at(idx);
|
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::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
|
||||||
Handle::Config { .. } => (config_size, MODE_CHR | 0o600),
|
Handle::Config { .. } => (config_size, MODE_CHR | 0o600),
|
||||||
Handle::Access | Handle::Channel { .. } => (0, 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)),
|
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||||
};
|
};
|
||||||
stat.st_size = len as u64;
|
stat.st_size = len as u64;
|
||||||
@@ -186,6 +212,39 @@ impl SchemeSync for PciScheme {
|
|||||||
addr: _,
|
addr: _,
|
||||||
ref mut st,
|
ref mut st,
|
||||||
} => Self::read_channel(st, buf),
|
} => 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)),
|
Handle::SchemeRoot => Err(Error::new(EBADF)),
|
||||||
_ => Err(Error::new(EBADF)),
|
_ => Err(Error::new(EBADF)),
|
||||||
}
|
}
|
||||||
@@ -219,7 +278,13 @@ impl SchemeSync for PciScheme {
|
|||||||
return Ok(buf);
|
return Ok(buf);
|
||||||
}
|
}
|
||||||
Handle::Device => DEVICE_CONTENTS,
|
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));
|
return Err(Error::new(ENOTDIR));
|
||||||
}
|
}
|
||||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||||
@@ -383,11 +448,16 @@ impl PciScheme {
|
|||||||
if self.pcie.has_ecam() { 4096 } else { 256 }
|
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 {
|
Self {
|
||||||
handles: HandleMap::new(),
|
handles: HandleMap::new(),
|
||||||
pcie,
|
pcie,
|
||||||
tree: BTreeMap::new(),
|
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> {
|
fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
|
||||||
|
|||||||
+23
-10
@@ -130,16 +130,6 @@ fn main() {
|
|||||||
scheduler
|
scheduler
|
||||||
.schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned()));
|
.schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned()));
|
||||||
scheduler.step(&mut unit_store, &mut init_config);
|
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());
|
let runtime_target = UnitId("00_runtime.target".to_owned());
|
||||||
scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone());
|
scheduler.schedule_start_and_report_errors(&mut unit_store, runtime_target.clone());
|
||||||
@@ -155,6 +145,29 @@ fn main() {
|
|||||||
Path::new("/usr"),
|
Path::new("/usr"),
|
||||||
Path::new("/etc"),
|
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
|
// 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()));
|
// 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);
|
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)
|
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<()> {
|
fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||||
let adapters = get_all_network_adapters()?;
|
let adapters = wait_for_network_adapters();
|
||||||
if adapters.is_empty() {
|
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
trace!("found {} network adapter(s)", adapters.len());
|
trace!("found {} network adapter(s)", adapters.len());
|
||||||
|
|||||||
Reference in New Issue
Block a user