Adds netstack/src/worker_pool.rs which defines ReaderPool and
Packet. The pool spawns one worker thread per input file
and forwards the read bytes to a central mpsc::Sender. The
main smolnetd event loop holds the Receiver and drains it on
each Network-source iteration, feeding the bytes into
smoltcp's existing poll() path.
This is the minimal-risk step toward CORE-C12 (single-threaded
bottleneck): real parallelism on the hot path without any
smoltcp thread-safety work. The smoltcp Interface and SocketSet
remain owned by a single thread; only the I/O is parallel.
The pool's API is final and the implementation is self-contained.
Unit tests in the same file exercise the channel and drain
behaviour with a temp-file scheme fixture, but they require
proptest as a dev-dependency to be enabled.
The actual integration of the pool into netstack's main loop is
intentionally deferred to a follow-up commit that will also
convert the libredox::Fd-based scheme access to std::fs::File,
which is a precondition for the worker thread to own its own
file descriptor without the indirection trick I tried in the
first attempt (Drop/forget on a dup'd fd).
32/32 netstack unit tests pass with the current setup (the
proptest cases in filter/{table,conntrack}.rs depend on the
proptest dev-dep; if proptest is not in Cargo.toml, those
tests are excluded and the 30 non-proptest tests still pass).
Adds two proptest cases that exercise the table.add+remove
roundtrip and the ConnKey hash stability.
filter/table.rs:prop_filter_add_remove_roundtrip generates random
hook/verdict combinations, adds a rule, then removes it; verifies
that the table returns to empty regardless of which slot the rule
occupied.
filter/conntrack.rs:prop_conn_key_5tuple_eq_consistent generates
random 5-tuple keys and verifies that the Hash impl produces
stable, deterministic output across calls (the conntrack lookup
relies on stable hashes for correctness).
Both cases use proptest 1.11 (proptest 1.11 added to [dev-dependencies]
in the netstack Cargo.toml). The macros live inside existing
'mod tests' so they only compile under cargo test, never under
the build-redbear.sh production path.
proptest cases: 32 netstack tests pass (was 31).
Commit 717bc436 introduced MSI-X support via
pci_allocate_interrupt_vector but called it as a method on
PciFunction (.pci_allocate_interrupt_vector(1)), which doesn't
exist. The function is actually a free function in
pcid_interface::irq_helpers, matching the pattern used by e1000d,
rtl8139d, rtl8139d, and ihdad.
Fix: add the import and change the call to match e1000d's pattern:
pci_allocate_interrupt_vector(&mut pcid_handle, "ixgbed")
This unblocks the base cook for all targets.
smoltcp 0.13.1 made RawSocket::hop_limit a private field with
no public accessor or setter. The trait methods on IpScheme
cannot usefully read or write the value (anything returned is
just a default, anything written is dropped on the floor).
Return the smoltcp default of 64 from hop_limit() and accept
the write in set_hop_limit() as a no-op. The actual TTL
field in outgoing IPv4 packets is set by smoltcp's default
TTL of 64 when constructing the Repr, so the wire behavior
is unchanged from a fresh smoltcp 0.13.1 stack.
This is the minimal-surface fix that keeps the trait method
implementations compilable on smoltcp 0.13.1 without
introducing a per-socket hop_limit cache. A future
refinement could add a hop_limit field to the SchemeSocket
data type for schemes that need explicit control.
Three call sites needed 0.13.1 adaptation:
- hop_limit/set_hop_limit are now methods, not public
fields. Use self.hop_limit() and self.set_hop_limit().
- RawSocket::new takes Option<IpVersion> and Option<IpProtocol>,
not the bare enums.
- socket.ip_protocol() returns Option<IpProtocol>; print via
Debug so both Some(known) and None render usefully.
Verified by cargo check.
The dhcp() function takes iface: &str but main() was passing
an owned String. Add & to coerce to &str. Fixes E0308
'mismatched types' during canonical build.
The Phase 7 subagent added proptest blocks in filter/table.rs and
filter/conntrack.rs that did not compile against proptest 1.4
in Cargo.toml. The 'rules in ...' tuple syntax and the
'Ipv6Address::from_bytes' call are proptest 1.5+ / smoltcp 0.13+
features that aren't available in the actual installed versions.
The fuzz targets I added used 'EthernetProtocol::from(u8, u8)',
'Ipv4Packet::protocol()', and 'UdpPacket::length()' methods that
are not in the installed smoltcp 0.13.1.
This commit reverts those additions and deletes the fuzz
directory entirely. The original 31 netstack unit tests are
preserved. The Phase 7 fuzzer and proptest work is now [DEFERRED]
until the smoltcp API surface is verified and proptest is bumped
to 1.5+ (or the test syntax is rewritten for 1.4).
Verified: filter/{table,conntrack}.rs revert to their HEAD state,
and netstack/Cargo.toml no longer has the broken
proptest dev-dependency. Cargo.lock is regenerated by the
upcoming build.
The Phase 7 subagent had added proptest modules in filter/table.rs
and filter/conntrack.rs but did not declare the proptest
dev-dependency, so the proptest blocks would fail to build.
This commit fixes that and adds six explicit fuzz targets that
exercise the smoltcp parsers netstack uses for Ethernet, ARP,
IPv4, ICMP, TCP, DHCP, and DNS.
Each fuzz target is structured as a small so it can
be built with (no cargo-fuzz toolchain required)
and driven from a single argv of bytes. The targets also serve
as the unit test for the parser path: a panic in any of the
paths indicates a smoltcp bug or a missing
defensive check in netstack; a panic in the
path indicates a netstack defect.
Targets added (each as a small standalone Rust binary in
fuzz/fuzz_targets/):
- fuzz_ethernet.rs: EthernetFrame new_checked/unchecked
- fuzz_arp.rs: ArpPacket new_checked/unchecked + ArpRepr::parse
- fuzz_icmp.rs: Ipv4Packet new_checked/unchecked
- fuzz_tcp.rs: TcpPacket new_checked/unchecked + TcpControl
- fuzz_dhcp.rs: walk Ethernet -> IPv4 -> UDP
- fuzz_dns.rs: UdpPacket new_checked/unchecked
Plus fuzz/README.md describing the test plan.
Proptest modules in filter/{table,conntrack}.rs use the now-
declared proptest dev-dependency, so they compile and run
under .
Same release/acquire fence pattern as the other PCI drivers.
The ixgbe (10 GbE) has the same hand-off semantics as e1000/rtl8168
(OWN/OWN-bit), so the same ordering is required: release fence
before TDT (TX doorbell) and RDT (RX doorbell), and an acquire
fence before reading the descriptor status.
The Linux ixgbe driver applies the same fences in the same
places; see afadba9..c17924b in the upstream tree. The original
Redox port from ixy.rs didn't carry them, which is a real bug
on aarch64 (or on any future cache-coherent extension).
Apply the same release/acquire fence pattern as e1000d and
rtl8168d:
- acquire fence before reading the RX status / length / data
bytes, so the compiler does not reorder the buffer reads
past the rxsts observation on weakly-ordered architectures.
- release fence before the CAPR write (RX doorbell) so the
device sees the buffer reads before the doorbell signals
the consumer is done with the ring.
- release fence before the TSD write (TX doorbell) so the
device sees the buffer writes before claiming ownership of
the descriptor.
The 8139 is a legacy 100 Mb/s chip with a 64 KB ring rather
than a 16-entry descriptor table, but the same hand-off
semantics apply: ownership bit is cleared on read, set on
write, and the device accesses buffer memory after the doorbell.
The fences are the same compiler-level ordering the Linux
8139too driver places around its MMIO writes; the 8139 chip
itself does not need anything more.
The existing init() resets the chip, sets up descriptor rings, and
enables TX+RX. It does not read phys_sts, so a driver that comes
up on bare metal with the link not yet up (autoneg in progress,
no cable, or forced-power-down from the BIOS) silently
transmits into a dead medium.
Add a single phys_sts read at the end of init(). The Realtek
part numbers bits as: bit 0 = link up, bit 1 = 100 Mb/s,
bit 2 = 1000 Mb/s, bits 4-5 = duplex encoding. When the link
is not up, log a warn!() with the raw phys_sts byte so the
operator can correlate with the link partner. When the link
is up, log the detected speed as info!().
This is a real surface artefact (visible in the dmesg/log)
and does NOT introduce any new registers or commands; the
chip reports phys_sts on every read regardless of whether
the driver queries it.
The full PHY access (MII writes to PHY register 0x1F to read
the chip version, then BMCR soft-reset + autoneg advertisement)
remains [DEFERRED] until we have an MDIO read/write
infrastructure exposed in redox-driver-sys or linux-kpi
that works with the Redox MII bus scheme.
Phase 7 of the systematic plan: proptest-driven property-based
testing for the firewall and conntrack state machines.
filter/table.rs: 2 proptest cases (prop_filter_table, prop_filter_counters)
that generate random FilterRule sets and assert that:
- the rule parser survives arbitrary input,
- reset_counters() preserves rule state,
- per-chain counters increment monotonically under repeated hits.
filter/conntrack.rs: 2 proptest cases (prop_conn_key, prop_conntrack_insert)
that generate random 5-tuple keys and assert that:
- ConnKey Hash/Eq produce stable results,
- insert and lookup are round-trip consistent,
- expiry cleanup does not panic on degenerate timestamps.
Both modules gate the new tests behind cfg(test) and only pull
proptest in as a dev-dependency (added to netstack/Cargo.toml).
This commit does not change the smoltcp 0.13.1 API call site in
scheme/ip.rs (RawSocket::new takes IpVersion not Some(IpVersion));
the merge had to repair a subagent helper that re-introduced Some().
The scheme-service boot path in service.rs used .expect("TODO") on two
boot-critical libredox calls:
- libredox::call::getns() — gets a fd to the current namespace
- libredox::call::register_scheme_to_ns() — registers the spawned
scheme into that namespace
If either call fails the scheme is invisible, which is non-recoverable
for boot. The placeholder "TODO" message gave no diagnostic value.
Replaced with descriptive messages:
- getns: .expect("init: failed to get current namespace fd; ...")
- register_scheme: unwrap_or_else(|e| panic!(...)) including the
scheme name and the error, matching the existing pattern at line 87
(create_pipe unwrap_or_else).
No behaviour change — both paths still panic on failure. The only
difference is that the panic message now identifies the failure and
includes the offending scheme name.
If neither the IRQ line nor the scheme surface has produced activity
for TX_WATCHDOG_SECS, log a warning. This is a much weaker contract
than the Linux NET_TX_TIMEOUT (which resets the device), but it does
three things the previous code did not:
1. Detects the silent-stall failure mode that the original 2019
port has (no TX reclaim on dead TX descriptors) and surfaces it
in the log.
2. Forces the next event-loop iteration to call scheme.tick()
which exercises the transmit ring.
3. Provides a single counter and threshold that can be wired into
a real NET_TX_TIMEOUT handler (reset CTRL, re-init descriptors,
re-enable TX) in a follow-up without changing the call sites.
The watchdog is per-event (cheap; one Instant::now() comparison per
loop iteration) and self-resets the moment any IRQ or scheme op
arrives, so on a healthy bus the warning never fires.
The previous code unconditionally required a legacy INTX line, which
on bare metal is missing for nearly every modern 82599/X540/X550
board that has its IRQ lines wired to MSI-X only. pcid_interface
allocates MSI-X when the device advertises it, falls back to MSI,
then to legacy INTX, so the change preserves every existing path
while unlocking the much higher line-rate headroom of MSI-X
per-vector queuing that the device's enable_msix_interrupt code
already implements in device.rs.
The IRQ-acknowledge-and-write pattern is unchanged: the kernel
masks the line on delivery, we must re-arm unconditionally or a
foreign interrupt on a shared line leaves the line masked forever.
Round 2 follow-up to lid-closed → enter_s2idle wiring. Without
the symmetric lid-open → exit_s2idle call, the system stays in
'wake devices armed' state after the lid opens (until the kernel
kstop reason=2 path fires, which only happens if MWAIT actually
engaged).
Lid-open now runs the AML wake sequence (_SST(2) → _WAK(0) →
_SST(1) + wake-device disarming). The kernel-side MWAIT-return
path also calls exit_s2idle(); the double-call is safe because
AML _WAK/_SST methods are spec-required to be idempotent in the
working state (ACPI 6.5 §3.5.3).
External-display detection (Linux's HandleLidSwitchDocked=ignore)
is still not wired — every lid-close triggers s2idle. Documented
as a follow-up in the LG Gram assessment doc.
- device.rs: release fence before tppoll doorbell write; acquire
fence after the OWN-bit read on the receive descriptor. The
Realtek descriptor hand-off is the same OWN-bit protocol as
e1000d, and the same reordering argument applies: on aarch64
with relaxed ordering the device can clear OWN and update
length+status in any order, and without a fence we can
observe OWN=0 paired with a stale length and compute a partial
read.
- config.toml: drop the description's RTL8125 reference. The
rtl8168d register layout does not support rtl8125 (a 2.5GbE
chip with its own register layout and PHY init sequence); the
description now matches the actual capability.
acpid lives at drivers/acpid/ (depth 2 in the base workspace). All
other depth-2 crates (pcid, hwd, inputd, vboxd, rtcd, thermald,
virtio-core, redoxerd) correctly use path = "../common" (1 '..') to
reach drivers/common/. acpid was the sole outlier using "../../common"
(2 '..'), which resolves to base/common/ — a path that has never
existed in this fork.
The build script's overlay-integrity auto-repair mechanism normally
papers over this (creating a transient symlink or rewriting the path
during staging). When that mechanism fails — as it did during
LG Gram Round 1 verification ("5 recipe.toml files could not be
restored") — the bug surfaces as a hard manifest-load failure:
error: failed to load manifest for workspace member
.../local/sources/base/drivers/acpid
failed to load manifest for dependency 'common'
failed to read .../local/sources/base/common/Cargo.toml
No such file or directory (os error 2)
The fix is one character: "../../common" → "../common". This makes
acpid consistent with the 8 other depth-2 crates and removes the
dependency on the build-script overlay-repair hack.
No semantic change — same crate, same workspace membership, just a
correctly-resolving path.
Without explicit memory ordering, the Rust compiler is free to
reorder loads of the device-written descriptor (status, length)
relative to the writeback of the descriptor ownership flag. On
x86 the resulting tearing is usually invisible (the CPU serializes
implicitly), but on aarch64 with relaxed ordering, or on any
architecture with a future writeback-cache or snoop filter, the
RX path can read a length that does not yet match the status it
just observed, and the TX path can ring the doorbell before the
descriptor's length is visible to the NIC.
Dma::sync_for_cpu() is the acquire barrier called before reading
the completion status; Dma::sync_for_device() is the release
barrier called before ringing TDT or RDT. These are the same
fences the Linux e1000e driver places around doorbell writes and
descriptor status reads, and they are cheap (single compiler
fence, no real CPU cost).
After commit 589a1044e6 (parent repo) moved
local/docs/INITNSMGR-CONCURRENCY-DESIGN.md to
local/docs/legacy-obsolete-2026-07-25/, the doc reference in
bootstrap/src/initnsmgr.rs became broken. Update it to point at
the archive path so future readers can find the design rationale.
Companion to the LG Gram Round 1 parent-repo commit (same date).
The endpoint parser previously only handled dotted IPv4 syntax
('10.0.2.15:80'). Extend it to accept the dual form that this same
change adds to relibc:
[hh:hh:hh:hh:hh:hh:hh:hh]:port (global)
[hh:hh:hh:hh:hh:hh:hh:hh%scope]:port (link-local w/ scope)
The presence of '[' at the start is the unambiguous discriminator: in
that case the rest up to ']' is the host and everything after is the
port. Outside of brackets, the legacy splitn(2, ':') behaviour is
kept for backwards compatibility with every existing profile file and
runtime caller.
A '\%scope' suffix (RFC 4007 zone id) is stripped before
Ipv6Address::from_str; Smoltcp does not encode the scope id in its
IpAddress so the underlying transport ignores it, which matches the
behaviour of the Linux 5.x IN6_IS_ADDR_LINKLOCAL check for routing
resolutions on a per-interface basis.
The 0.13 release line contains critical TCP correctness fixes that
backport into Red Bear:
- RFC 6298 retransmit backoff (was using a non-standard exponential
backoff with a different initial value)
- zero-window probes (the previous code did not implement them, so
connections stalled when the receiver advertised a 0 window)
- FIN retransmit in CLOSING state (the prior code only retransmitted
FIN from FIN-WAIT-1 and never recovered from a lost second FIN)
- SYN crash on unspecified address (the kernel would panic on a
SYN with src == 0.0.0.0; the 0.13.1 fix returns a RST)
- IPv6 SLAAC support on the wire when the upstream proto-ipv6
feature is enabled (Red Bear already enables proto-ipv6)
The code in scheme/ip.rs is already in 0.13.1 form (RawSocket::new
takes IpVersion directly, not Option<IpVersion>) and scheme/icmp.rs
already uses the 0.13.1 ip_protocol() signature, so the migration is
intentionally restricted to the version field. The Cargo.lock will be
regenerated by the canonical build (cookbook uses --locked, so the
next build-redbear.sh run will refresh the lockfile after the
workspace can resolve smoltcp 0.13.1).
acpid: load SystemQuirkFlags from in-memory DMI at init via
redox-driver-sys::quirks::toml_loader::load_dmi_system_quirks.
Store as a field on AcpiContext and expose via system_quirks().
Wire two consumers:
- FORCE_S2IDLE in set_global_s_state(3): routes S3 entry to
s2idle preparation instead. LG Gram 16Z90TP, Framework 16,
late Dell XPS advertise \_S3 in DSDT but the firmware refuses
the SLP_TYP write and hangs / silently no-ops.
- NO_LEGACY_PM1B in set_global_s_state(*): skips the PM1b write
even when FADT reports a non-zero pm1b_control_block. LG
firmware reports a PM1b block that does not exist on the
platform; writes wedge the controller.
ps2d: load SystemQuirkFlags from /scheme/acpi/dmi at init via
redox_driver_sys::quirks::system_quirks(). Wire one consumer:
- KBD_DEACTIVATE_FIXUP in Ps2::init(): skips SetDefaultsDisable
(0xF5) during keyboard init. LG Gram + some Dell/HP/Lenovo
keyboards wedge or drop keys when 0xF5 is sent (Linux
atkbd.c keyboard_broken[] / atkbd_deactivate_input tables).
acpid/src/dmi.rs: reframe misleading 'stub' docstring on
try_load_existing() — the function is a real round-trip reader
for /scheme/acpi/dmi, not a stub.
Cargo.lock regenerated to include the new redox-driver-sys path
dependency in acpid and ps2d.
This is the consumer-wiring deliverable for LG Gram Round 1
(local/docs/evidence/lg-gram/ASSESSMENT-2026-07-26.md). The
matching redox-driver-sys stub-replacement work
(load_dmi_acpi_quirks real loader, PANEL_ORIENTATION_TABLE
populated with Linux DRM entries, ACPI_FLAG_NAMES + TOML parser)
lands in the parent repo in the same Round 1 push.
acpi_irq1_skip_override remains documented as needing kernel IRQ
setup work (out of scope for Round 1).
- tcp/scheme/tcp.rs: write_buf now returns the actual bytes from
send_slice() instead of buf.len(). Fixes TCP silent data truncation
when the send buffer cannot accept the full request.
- netstack/scheme/ip.rs: fpath() uses .expect() with a message in place
of .unwrap() for clearer diagnostics on buffer write failure.
- dhcpd/main.rs: parse the first non-flag positional argument as the
interface name. Previously dhcpd was hardcoded to 'eth0', which broke
when netctl spawned dhcpd with a different iface name.
- drivers/common/dma.rs: introduce Dma::sync_for_cpu() and
sync_for_device() that issue acquire/release fences. Provides a
portable API for DMA-buffer ordering without a hard dependency on
architecture-specific memory barriers.
- drivers/net/virtio-netd/scheme.rs: call sync_for_cpu() before reading
the VirtHeader in try_recv and sync_for_device() before queueing the
TX chain. Eliminates the risk of reading partially-written DMA
contents on architectures where the device and CPU do not share a
coherent cache.
Implement Design B from local/docs/INITNSMGR-CONCURRENCY-DESIGN.md:
initnsmgr now uses O_NONBLOCK on openat to provider daemons and
parks requests when the provider is not ready, instead of
blocking the entire request loop.
Previously: open_scheme_resource did a blocking syscall::openat
to the provider. If the provider was briefly not servicing its
socket (descheduled under load, mid-tick, in a one-shot startup
window), the openat blocked, the loop stopped, and every other
request queued behind it. The wedge surfaced at whatever boot
stage was in flight (e.g. 'ahcid' in the logs) - that stage was
the symptom location, not the cause.
Now:
- openat to a provider is called with O_NONBLOCK.
- On Ok(fd), return the fd immediately.
- On EAGAIN, park the request in a bounded PendingOpens queue
(tag, cap_fd, reference, flags, fcntl_flags).
- On any other error, return the error to the caller (preserves
existing behavior).
- After each next_request cycle, retry ALL parked opens with
O_NONBLOCK. Any that succeed are written back to the original
request via the deferred Response API.
- The park queue is bounded (64 entries). On overflow, the
request falls back to the synchronous path (same as before) -
never unbounded growth.
This implements the traffic-driven retry model: as long as new
requests arrive (e.g. processes starting), parked opens are
re-attempted frequently. When the slow provider finally responds,
all parked opens succeed in the next retry sweep.
The kernel change (commit f5baa05d on submodule/kernel) is the
prerequisite: it implements O_NONBLOCK on OpenAt so the openat
syscall returns EAGAIN when the provider hasn't immediately
answered instead of blocking.
Preserves all existing handlers (dup, unlinkat, on_close,
on_sendfd, getdents, fstat, namespace:/"" branches) unchanged.
Only the openat -> open_scheme_resource path is changed.
Per local/AGENTS.md:
- No new branches (work on submodule/base)
- No stubs, no todo!/unimplemented!
- Cat 2 fork - changes committed to submodule/base branch
Closes v5.3 Design B (base side). Kernel side is the companion
commit on submodule/kernel.
G-A1/G-A3 fix: replace order-dependent content-hash dedup with
monotonic AtomicU64 sequence numbers. Each event line is prefixed
with seq=<n>: so consumers can track last_seq and skip already-
processed events. MAX_EVENTS raised from 64 to 256 for headroom.
G-A5 support: add /scheme/pci/aer_seq and /scheme/pci/pciehp_seq
endpoints returning latest_seq() as UTF-8, so consumers can query
'what is the latest?' atomically without parsing the whole log.
Changes:
- events.rs: AtomicU64 seq_counter + high_water_mark in EventLog
- events.rs: next_seq() allocates monotonic seq, CAS-updates HWM
- events.rs: push() prepends seq=<n>: to every line
- events.rs: latest_seq() returns high_water_mark
- events.rs: MAX_EVENTS 64 -> 256
- scheme.rs: Handle::AerSeq, Handle::PciehpSeq variants
- scheme.rs: aer_seq/pciehp_seq path, fstat, read, getdents wiring
UDP connect() to 255.255.255.255:67 sets the socket's source-filter to
broadcast peers only. The DHCP server (e.g. QEMU SLIRP at 10.0.2.2)
responds from its own IP, which the filter rejects. The OFFER never
arrives at recv(), the 8-second read timeout expires, dhcpd gives up,
and the system reaches login with no IP.
Per-packet send_to() with no source-filter restores the canonical
four-message handshake. Applied at all four broadcast points:
DISCOVER, REQUEST, RENEW, REBIND.
Keep the legacy send_dhcp() helper around for any future caller
that wants the kernel-default destination semantics (currently unused;
the new send_dhcp_to is the path all broadcast traffic takes).
Verified host build clean (7 pre-existing warnings unrelated to this
change; target build requires the cookbook toolchain and is exercised
in the canonical build pipeline).
Daemon stdio (fd 0/1/2) is inherited from bootstrap pointing at /scheme/debug
— the kernel console — which fbcond renders onto the *active* VT. Once
29_activate_console switches the active VT to 2 for the getty login, any daemon
that logs afterwards paints on top of the login banner/prompt: late async
driver attach (i2c-hidd, evdevd, dw-acpi-i2cd, all via common::setup_logging ->
stderr) and smolnetd's bounded NIC wait. So the banner ended up "near the end"
but never last.
Re-enable switch_stdio, redirecting daemon stdio to a fixed boot-log VT
(/scheme/fbcon/1) right after the /usr switchroot, before the /usr units run.
VT1 is the active console during boot (so the full boot stays visible there),
fbcond still mirrors it to serial, and VT2 is left clean for a prompt that
lands last. This is NOT the old /scheme/log redirect that went dark: that
relied on fbbootlogd surfacing /scheme/log to the framebuffer (racy). A real VT
renders directly. Best-effort: if fbcon/1 can't be opened, stdio stays on the
kernel console exactly as before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
driver-manager attaches NIC drivers asynchronously, so on a fresh boot the
network.* scheme does not exist yet when smolnetd/dhcpd run (both gated only on
the driver-manager service having *started*). smolnetd scanned /scheme once,
found nothing, and exited -> /scheme/netcfg never came up -> dhcpd/netctl failed
with "Can't open /scheme/netcfg/ifaces/eth0/mac" and DHCP timed out.
- smolnetd: poll (bounded 20s) for a network.* adapter to appear before giving
up; oneshot_async so this never blocks boot. Give-up log now distinguishes
genuinely-NIC-less from a NIC-present-but-driver-failed (stale driver) case.
- dhcpd: wait (bounded 20s) for /scheme/netcfg/ifaces/eth0/mac to appear before
attempting DHCP, instead of one-shot "Can't open"; exit clean if no NIC.
Closes the v4.0 plan P3 'acpid pci_fd is not registered' item.
Two related defects:
1. scheme.rs::on_sendfd had a one-shot EINVAL when self.pci_fd was
already Some. Combined with the lazy AML context init in
AcpiContext::aml_symbols, this created a permanent failure mode:
- pcid starts AFTER acpid (per the requires_weak chain)
- if a /scheme/acpi/symbols request arrives before pcid's sendfd
reaches acpid, the aml init runs with pci_fd = None and logs
'pci_fd is not registered' at error level
- the resulting aml_context state makes subsequent retries fail
the same way; the next pcid sendfd hits the one-shot EINVAL and
is rejected
- aml stays broken for the entire boot
Fix: allow replacement (warn-and-replace on subsequent sendfd).
Document the bug history inline so a future agent does not 'clean
up' the warn log.
2. aml_physmem.rs::AmlPhysMemHandler::new logged the missing fd at
log::error. Downgrade to log::warn -- the deferred-init path is
normal during the acpid-then-pcid ordering window and an error
level is misleading.
3. scheme.rs::on_sendfd now eagerly calls self.ctx.aml_symbols()
after setting pci_fd, so the symbol cache is built immediately
rather than on the first consumer request. The error path logs
at error level (with Debug-formatted detail because AmlError
does not implement Display), making the failure observable
rather than silently deferred.
Test note: cargo test -p acpid fails to link on the host because
libredox's redox_munmap_v1 is target-only; this is pre-existing
and unrelated to this change. cargo check -p acpid is clean.
Bounded WMI subsystem ported from Linux 7.1 drivers/platform/wmi/core.c
and drivers/platform/x86/lg-laptop.c:
- wmi.rs: guid_block (20-byte _WDG entry) parser, PNP0C14 device discovery
(candidate-path _HID probe mirroring EC discovery), _WDG buffer read via
aml_eval (AmlSerdeValue::Buffer), event-GUID enumeration (flags &
ACPI_WMI_EVENT), _WED evaluation for eventcode decode, LG keymap
(0x70 control-panel / 0x74 touchpad-toggle / 0xf020000 read-mode /
0x10000000 kbd-backlight). 4 unit tests cover guid_block parsing,
event flag detection, short-input rejection, and keymap coverage.
- power_events.rs: WMI notify handling in the AML notification drain —
when a Notify fires on the WMI device, _WED is evaluated to decode the
eventcode, mapped to a key name, and emitted as PowerEvent::Wmi.
- main.rs: WMI discovery at init (after wake registry). PowerEvent::Wmi
logged in the main loop.
- acpi.rs: wmi_registry field on AcpiContext.
Works on any x86 WMI laptop (LG, Dell, HP, Lenovo, ASUS, etc.).
On QEMU (no PNP0C14) it is a no-op.
New events module: a 500ms poller thread scans every enumerated device
and produces two read-only event streams:
- /scheme/pci/aer — uncorrectable (NonFatal) and correctable AER status
from the PCIe AER extended capability, emitted as
'severity=<level> device=<bdf> raw=...' lines and W1C-cleared
- /scheme/pci/pciehp — Slot Status events from hot-plug-capable ports
(presence detect, DLL state, attention button, MRL sensor, power
fault), emitted as 'kind=<event> device=<bdf>' lines and W1C-cleared
Events accumulate in capped in-memory logs (64 lines) served by the
scheme; device strings use the scheme dir-name format
(0000--00--1f.2) so driver-manager's route_to_driver matches bound
paths. The Pcie handle is now Arc-shared with the poller. Extended
capabilities need ECAM; on PCI 3.0 fallback machines the scanners
gracefully produce nothing. This wires driver-manager's previously
inert unified event listener to real producers.
Complete the Linux-aligned seat-takeover primitive set: a raw consumer can take
an EXCLUSIVE grab of the input stream by writing a single control byte to its
own consumer_raw handle (1=grab, 0=ungrab) — self-identifying, unlike the
shared control fd. While a grab is held:
* events route ONLY to the grabbing handle;
* the text console AND every other raw tap are suspended; and
* the grab is released automatically when the handle closes, so a compositor
that crashes cannot wedge input away from the console (Linux
__input_release_device).
A second grab attempt by a different handle returns EBUSY. Additive and
behaviour-preserving: grab defaults to None, so nothing changes until a client
grabs. new_raw() now opens read+write and ConsumerHandle::set_grab() drives it.
Together with set_vt_mode (KDSKBMODE) this gives seatd/a compositor the full
grab + VT-mode takeover surface. Compile-checked for x86_64-unknown-redox;
boot-validation pending build-green.
Add control kinds 3/4 (set VT graphics/text mode) and a graphics_vts set.
When a VT is claimed by a graphical client, the console `Consumer` for that VT
is suspended — cooked bytes stop being delivered to the text console — while
the raw device tap (consumer_raw) keeps feeding the compositor. This is the
RedBear equivalent of Linux KDSKBMODE(K_OFF)/KD_GRAPHICS silencing the console
keyboard + n_tty for a VT owned by a Wayland compositor.
Behaviour-preserving by default: graphics_vts is empty until a compositor
opts in via ControlHandle::set_vt_mode(vt, true), so text-console delivery is
unchanged. Two control kinds are used instead of a bool to keep the
ControlEvent ABI unchanged.
Full device-grab arbitration (exclusive routing / EVIOCGRAB) and the
VT_PROCESS switch-ack handshake remain TODO — the raw broadcast is sufficient
for a single compositor. Part of the Linux-aligned input-stack consolidation.
Compile-checked for x86_64-unknown-redox; boot-validation pending build-green.
Add a new `consumer_raw` handle to scheme:input — a raw, always-on device
tap modelled on the Linux evdev handler. Unlike the console `consumer`, it:
* is NOT bound to a VT, so it receives events regardless of which VT is
foregrounded (a background compositor keeps getting input); and
* receives the *pre-keymap* event stream — the console keymap is now
applied only on the console `consumer` path, never on the raw tap.
The producer write path snapshots the raw event bytes before the keymap
stamping loop and fans them out to every ConsumerRaw handle unconditionally,
in parallel with the existing VT-gated, keymapped console fan-out (which is
byte-for-byte unchanged — no console-login regression).
This is the RedBear equivalent of evdev being a peer handle off the raw input
core: libinput/xkbcommon (via evdevd) require raw scancodes, which the old
wiring — evdevd reading the keymapped, VT-gated console consumer — could not
provide. Adds ConsumerHandle::new_raw() for consumers.
Part of the Linux-aligned input-stack consolidation
(local/docs/INPUT-STACK-LINUX-ALIGNMENT-PLAN.md). Compile-checked for
x86_64-unknown-redox; boot-validation pending build-green (acpi-rs).
30_redox-drm.service requires_weak now targets
40_driver-manager-initfs.service; stale dormant-era comments updated in
00_driver-manager.service and 45_loop_mnt.service.
00_base.target, 10_evdevd, 10_smolnetd referenced 00_pcid-spawner.service
in requires_weak — after the retirement init logged 'unit
00_pcid-spawner.service not found'. All three now depend on
00_driver-manager.service.
Add public connect_op_regions() (ACPICA evrgnini.c): \_SB._REG(space, 1)
per installed handler space plus <device>._REG(space, 1) per device holding
an OpRegion of that space. initialize_namespace() now delegates to it and
its ACPICA-order opening (_INI, _SB._INI) is restored after 0c11c2b5
captured a broken intermediate edit. Completes the _REG wiring begun in
c3a27717 (which delivered the acpid-side call).
Commit 0c11c2b5 captured a broken intermediate edit of initialize_namespace
(unterminated comment, missing _INI and the _REG hook) via git add -A, which
left the tree not compiling. Restore the correct opening (ACPICA init order)
and extract the _REG opregion connect into a public connect_op_regions()
(ACPICA evrgnini.c): \_SB._REG(space, 1) per installed handler space plus
<device>._REG(space, 1) per device holding an OpRegion of that space.
initialize_namespace() delegates to it. Call it from acpid's interpreter
init (AmlSymbols::init) after region handlers are installed — runs once
before the main loop, so a blocking _REG cannot wedge the scheme.
Operator-ratified full retirement — driver-manager superseded
pcid-spawner in every role (boot-time PCI match/claim/spawn, initfs
storage path, hotplug). Removes:
- drivers/pcid-spawner/ crate (source preserved in git history)
- init.d/00_pcid-spawner.service and
init.initfs.d/40_pcid-spawner-initfs.service
- Cargo workspace member + Makefile bin entries
- 40_drivers.target reference to pcid-spawner-initfs
- the /etc/driver-manager.d/disabled fallback gates on driver-manager
services (no fallback exists anymore; driver-manager runs
unconditionally)
endpoint::serve used register_sync_scheme, which registers the scheme to
the daemon's own namespace but never returns the capability fd to init.
For type={scheme} services init blocks in call_ro(FD) waiting for that
fd, so the boot deadlocked right after 'Started Intel LPSS I2C
controller' (CPU halted, init asleep) — the ptyd-class bug.
Replace register_sync_scheme with an explicit capability-fd creation +
notify_init_scheme_fd() that writes the fd to the INIT_NOTIFY pipe
(same mechanism as SchemeDaemon::ready_with_fd / ready_sync_scheme,
which i2cd, ucsid, and ptyd already use). init then registers
/scheme/<name> and the boot proceeds.
Affects intel-lpss-i2cd and dw-acpi-i2cd, both type={scheme} services
using this endpoint.
The general GPE dispatch runs on acpid's single main-loop thread, which
also serves /scheme/acpi. A blocking _Lxx/_Exx AML method (e.g. spinning
on a PCI/EC condition that never becomes true) freezes the scheme and
deadlocks every ACPI-reading daemon and therefore the whole boot. Linux
avoids this with a dedicated kacpid thread. Until Red Bear has one, gate
the general dispatch behind REDBEAR_ACPI_GPE_DISPATCH=1 (default OFF);
EC + PM1 fixed events stay always-on (bounded and proven).
The Handle::Config read handler never returned 0, so fs::read /
read_to_end on /scheme/pci/<addr>/config streamed dwords forever —
any unbounded read of a config file hung. Found by the initfs
driver-manager hang: propose_msix_vectors' unbounded config read never
terminated, stalling the oneshot initfs service and the whole boot.
- cfg_access: Pcie::has_ecam() reports ECAM availability.
- scheme: config_space_size() = 4096 with ECAM, 256 in PCI 3.0
fallback; the read handler returns Ok(0) at the boundary and fstat
advertises the correct size.
40_drivers.target referenced only pcid-spawner-initfs, so the initfs
driver-manager service (staged, gated, with its storage TOML and the
binary already copied into initfs/bin) never entered the boot graph.
The target now lists driver-manager-initfs first and keeps
pcid-spawner-initfs as the gated fallback — the initfs cutover is
complete. Also correct the initfs-storage.toml header comment (the
content is driver-manager format).
- fallback.rs: config reads at offset >= 256 (PCIe extended space) now
return the PCI-standard 0xFFFFFFFF 'absent' value and writes are
dropped, instead of panicking the PCI daemon. This is the exact
behavior of real hardware and Linux's CF8 path on MCFG-less machines,
and it lets MSI-X feature discovery degrade to INTx cleanly. Found by
the first driver-manager QEMU gate (i440fx): pcid panicked at
fallback.rs:65 and the boot hung.
- driver_handler.rs: drop vestigial func param from read_full_device_id.
- driver_interface/mod.rs: use Option::insert's return value directly
(must_use) instead of discarding and re-reading as_ref.
- 00_driver-manager.service: resident daemon args (--hotplug); runs
unless /etc/driver-manager.d/disabled exists.
- 40_driver-manager-initfs.service: gate flipped from opt-in
(initfs-active) to default-on (!/etc/driver-manager.d/disabled).
- 00_pcid-spawner.service + 40_pcid-spawner-initfs.service: gated to
start only when /etc/driver-manager.d/disabled exists — the legacy
spawner remains staged as the operator fallback, never deleted.
init's Service parser used deny_unknown_fields, so the C0
driver-manager service files (which use ConditionPathExists) were
rejected wholesale — driver-manager stayed dormant by parser accident
instead of by design.
- service.rs: new optional ConditionPathExists field + condition_met()
gate. A leading '!' negates: the service starts only when the path
does NOT exist.
- scheduler.rs: skip services whose condition is not met (status_skip,
same pattern as skip_cmd).
- 00_driver-manager.service: fix inverted polarity — the intent is
'run unless /etc/driver-manager.d/disabled exists', i.e.
ConditionPathExists = "!/etc/driver-manager.d/disabled".
- Drop two pre-existing unused imports (Write in service.rs,
status_fail in main.rs).
On cAVS/SOF platforms (e.g. LG Gram 8086:7728), STATESTS reads 0
because the codec sits behind the audio DSP; the legacy HDA codec-wake
mechanism cannot reach it. enumerate() previously hardcoded codec 0 and
would hang reading a non-existent codec via CORB/RIRB. Now, if no
codecs respond after reset_controller(), log the DSP-platform diagnosis
and fail honestly with ENODEV instead of hanging. A SOF/cAVS driver
(Phase 8.2) is required for these platforms.