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.
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.
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).
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.
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
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).
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.
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.
acpi-rs (vendored fork):
- _REG opregion connect (ACPICA evrgnini.c): run \_SB._REG(space, 1)
per installed handler after \_INI — firmware gates EC access behind
this. Device-level _REG for each device holding an OpRegion of an
installed space. Fills the last big TODO in initialize_namespace.
- RegionSpace: add From<RegionSpace> for u8 (region-space ID for _REG).
acpid:
- LPIT parser BUG FIX: GAS (Generic Address Structure) is 12 bytes
(4-byte header + 8-byte address); the parser previously read
entry_trigger/residency/latency/counter_frequency 4 bytes early and
required length>=48 instead of 56. Fixed offsets, added
entry_trigger_space_id, and mwait_hint()/best_mwait_hint() for the
FFH MWAIT hint. Tests rewritten with correct 56-byte layout.
- Thermal active cooling: /scheme/acpi/thermal/<zone>/active evaluates
_AC0.._AC9 and returns defined trip points (tenths of K).
- ACPI PM timer: pm_timer_read() from FADT pm_timer_block;
/scheme/acpi/pmtimer endpoint (3.579545 MHz).
- HW-reduced ACPI: Fadt::is_hardware_reduced() (FADT bit 20); skip
PM1/GPE setup on such platforms.
- General GPE dispatch: enabled_active_gpes() + \_GPE._Lxx/_Exx method
evaluation (evgpe.c acpi_ev_gpe_detect).
- GpeBlocks::enabled_active_gpes(): scans all GPE block status+enable
register pairs, returns every GPE with both bits set (evgpe detect
semantics — a GPE only fires the SCI when enabled AND active).
- handle_sci general GPE dispatch (acpi_ev_gpe_detect): every
enabled+active GPE that is not the EC GPE gets its \_GPE._L{gpe:02X}
(level, tried first) or \_GPE._E{gpe:02X} (edge) control method
evaluated; status cleared after. This is the ACPICA core GPE model
that drives lid, device wake, and all non-EC GPE events — previously
only the EC GPE and PM1 fixed events were dispatched.
- pm_timer_read(): reads the FADT pm_timer_block free-running counter
(3.579545 MHz, PM_TIMER_FREQUENCY_HZ). Exposed at
/scheme/acpi/pmtimer for precise sleep-path timing.
Add ThermalZone handle kind to the scheme: per-zone ACPI thermal
data at /scheme/acpi/thermal/<zone>/{temperature,passive,critical}.
Each path evaluates the corresponding AML method (_TMP, _PSV, _CRT)
and returns the raw integer value. ThermalFileKind enum dispatches
to the correct method. thermald can now read real thermal zone
temperatures and thresholds instead of relying on hardcoded values.
Ported from Linux drivers/acpi/thermal.c thermal_zone_device_ops.
- WakeRegistry::arm_wake_devices(): evaluates _DSW(1,0,Sx) per wake
device (falls back to _PSW(1)), enables wake GPEs in the GPE block.
Wired into enter_s2idle() — wake devices are now armed before
the kernel MWAIT loop. Ported from Linux acpi_enable_wakeup_devices.
- WakeRegistry::disarm_wake_devices(): evaluates _DSW(0,0,0) per
wake device (falls back to _PSW(0)), clears wake GPE status bits.
Wired into exit_s2idle() — wake devices are disarmed on resume.
Ported from Linux acpi_disable_wakeup_devices.
- SleepStates::discover(): enumerates _S0 through _S5 to find
available sleep states. s3_supported() and s2idle_only() helpers
identify the platform's sleep capability profile.
- AcpiContext.wake_registry: new field storing the populated
WakeRegistry for use by enter_s2idle/exit_s2idle.
An extended-capability walk (pci_types capabilities()) that follows a
chain running to/past the end of config space read at offset 4096,
which tripped the dword-offset assert in bus_addr_offset_in_dwords
("pcie offset larger than 4095") and aborted pcid with an Invalid
opcode fault / UNHANDLED EXCEPTION on boot — after switchroot to /usr,
so it blocked reaching login.
PCIe config space is exactly 4096 bytes (offsets 0..=4095). Fix at two
levels:
- ConfigRegionAccess::read/write (cfg_access/mod.rs): guard the single
access choke point — an out-of-range read returns the PCI "no
response" pattern (0xFFFFFFFF, which also terminates a capability
walk cleanly) and an out-of-range write is a no-op. This catches any
caller (capability walk, scheme reads, driver access).
- scheme.rs config read/write loop: clamp the loop bound with
saturating_add(...).min(4096) so a consumer requesting offset+len
past the boundary reads only the valid part instead of looping to
offset 4096.
Verified: boot no longer panics in pcid (progresses past the previous
crash point through switchroot and the /usr driver set).
Port useful ACPICA components into the existing Rust ACPI stack:
- LPIT (Low Power Idle Table) parser in new acpid/wake.rs.
Parses native C-state LPI entries (entry trigger, residency,
latency, residency counter, counter frequency). Computes total
residency and deepest LPI state. 4 unit tests cover parsing,
empty input, short input, and deepest-entry selection.
Reference: Linux include/acpi/actbl2.h struct acpi_lpit_native.
- _PRW wake device enumeration in acpid/wake.rs.
WakeRegistry::enumerate() evaluates _PRW on known wake-capable
device paths (LID, PWRB, SLPB, XHCI, HDAS, CNVW, I2C0/1, THC0/1).
Extracts GPE number and sleep state from the _PRW package.
Reference: Linux drivers/acpi/scan.c acpi_bus_get_wakeup_device_flags.
- FADT S0-idle detection: Fadt::supports_s0_idle() checks bit 21
(ACPI_FADT_LOW_POWER_S0) to detect platforms that support s2idle.
Reference: Linux include/acpi/actbl.h.
- acpid/main.rs: LPIT parse + wake enumeration + S0-idle detection
wired into init after AcpiContext::init() and before power events.
ACPICA assessment: do NOT port ACPICA as a C library. Port its
useful data structures and algorithms into the existing Rust stack
(acpi-rs vendored fork + acpid daemon). The AML interpreter already
has comprehensive opcode coverage (only DefLoad/DefLoadTable remain
unimplemented, both optional). The GPE/EC/fixed-event infrastructure
is already in acpid. LPIT + _PRW + S0-idle detection fill the
remaining gaps for Phase 9.1 s2idle completion.
The PCI IDE Programming Interface byte (PCI spec §3.1.1) encodes
per-channel mode: bit 0 = primary native, bit 2 = secondary native.
The previous code panicked on native-mode channels and also had a
bug checking bit 0 for both channels (secondary should check bit 2).
Native mode now reads I/O and control bases from PCI BARs (BAR0/1 for
primary, BAR2/3 for secondary) instead of hardcoded legacy ports.
Interrupt line comes from legacy_interrupt_line when available.
Phase 5 — Laptop ACPI events (LG Gram 16Z90TP compatibility plan):
- Vendor acpi-rs crate (rev 90cbe88) into base fork with a real
Opcode::Notify executor; upstream panicked on every Notify opcode,
which is the backbone of ACPI event flow on real laptop firmware.
Handler::handle_notify hook added; acpid + amlserde pointed at the
path dep so every consumer sees the same fork.
- gpe.rs: FADT-driven GPE block register map (status half + enable
half), write-1-to-clear status bits, read-modify-write enable
preserving every other GPE's firmware state. PM1 fixed-event bits
(PWRBTN/SLPBTN/RTC) per ACPI 6.4 §4.8.3.1.
- power_events.rs: init builds the GPE map, discovers the EC via ECDT
(ACPI 6.4 §5.2.16) with a _HID=PNP0C09 probe fallback over conven-
tional paths, enables the EC GPE + PM1 fixed events, discovers lid
and ACPI 4.0 fan devices. handle_sci dispatches PM1 → EC query loop
(bounded at 32) → AML notification drain, following Linux 7.1
evgpe.c / ec.c / button.c.
- notifications.rs: shared AmlNotifications queue (parking_lot::Mutex)
populated by the vendored acpi-rs handle_notify hook, drained by the
SCI handler and redbear-upower.
- ec.rs: sci_evt_set() and query() exposed on Ec for the SCI handler.
- scheme.rs: new handle kinds Lid, LidState, ButtonDir, Button,
Notifications, Fan, FanState, FanSpeed with proper dir/file
separation. Fan _FST on read, _FSL on write (percent clamped 0-100).
- main.rs: subscribes /scheme/irq/{sci_irq} (default 9) on the event
queue, dispatches SCI events through handle_sci. PowerButton incre-
ments edge counter and triggers the shutdown path; SleepButton incre-
ments counter and calls enter_s2idle.
- acpi.rs: AcpiContext gained gpe, ec_device, lid_device, lid_state,
power_button_events, sleep_button_events, fan_devices fields (all
RwLock-guarded). evaluate_acpi_method and enter_s2idle changed from
&mut self to &self (AML mutation goes through RwLock inner state).
- aml_physmem.rs: handle_notify impl pushes onto the shared queue.
Phase 4.4 — HID report descriptor parser (i2c-hidd):
- report_desc.rs: HID 1.11 §6.2.2 descriptor parser + decoder. Global-
state stack (Push/Pop), usage-min/max expansion, sign-extended
fields, contact reconstruction (id, tip, x, y). 3 unit tests.
- input.rs: forward_layout_report dispatches descriptor-driven reports
through forward_decoded: first touching contact → absolute
MouseEvent; two simultaneous contacts → vertical ScrollEvent (two-
finger scroll); buttons → ButtonEvent; keyboard-page reports fall
back to the existing boot-protocol path.
- hid.rs: stream_input_reports parses the descriptor once and uses
forward_layout_report when layouts are non-empty, otherwise keeps the
boot-protocol summary path.
Reference: Linux 7.1 drivers/acpi/{evgpeblk.c,evgpe.c,ec.c,button.c}
and drivers/hid/hid-core.c.
All affected crates compile for x86_64-unknown-redox; i2c-hidd 3/3
unit tests pass on host; acpid host-side tests still can't link
(pre-existing libredox limitation — must run via redoxer).
The pcid_interface crate now reads the two env vars emitted by
driver-manager's SpawnDecision committee:
- REDBEAR_DRIVER_PCI_IRQ_MODE: lets the spawned child know
whether to fall back to MSI / INTx instead of MSI-X
- REDBEAR_DRIVER_DISABLE_ACCEL: lets the spawned child suppress
hardware acceleration (matches PciQuirkFlags::DISABLE_ACCEL)
Both values are stored on PciFunctionHandle and exposed via
accessors (irq_mode_hint / disable_accel_hint) so the driver
daemons can read them at startup. 6 new unit tests cover the
env-var parsing.
Refs: local/docs/DRIVER-MANAGER-MIGRATION-PLAN.md § 5.2 D2.7.
Post-implementation review returned FAIL with two verified-CRITICAL
findings; all legitimate findings fixed in this round:
CRITICAL — engine wrote IC_CON/IC_TAR while enabled (DesignWare
databook forbids; Linux i2c_dw_xfer_init disables first). Engine
restructured: wait-idle -> disable -> program -> enable with
IC_ENABLE_STATUS polling on every transfer.
CRITICAL — Intel LPSS PCI bring-up skipped parent-device init.
intel-lpss-i2cd now claims functions via pcid_interface
(connect_by_path + enable_device), validates the real BAR size, and
performs intel_lpss_init_dev's sequence (reset-deassert + 64-bit
remap address at BAR0+0x200), ported from Linux drivers/mfd/intel-lpss.c.
MAJOR fixes:
- wait_for checks TX_ABRT before success predicates — a NACKed write
no longer reports success via post-abort idle state
- SCL timing corrected to Linux's formulas: HCNT uses sda_fall_ns,
round-to-nearest division (FS 100/200, SS 552/652 for bxt 133 MHz)
- stop=false rejected honestly instead of hanging on the idle wait
- recover(): ABORT-bit cycle + disable + state flush after any failed
transfer
- validate_request: segment/byte/address/10-bit limits before any MMIO
- i2cd + endpoint: exact-first adapter resolution; last-component
matching accepted only when unambiguous
- i2cd registration validates provider_scheme as a single safe
scheme-name component
- I2cTransferResponse gains typed status (I2cTransferStatus,
serde-defaulted, wire-compatible)
- endpoint::serve takes a Result-returning on_ready callback;
setrens failure is fatal (fail-closed namespace reduction)
- unexpected i2cd registration responses are fatal for that controller
Tests: dw-i2c 5 (timing vectors, validation, resolution),
intel-lpss-i2cd 1 (PCI ID table coverage), full workspace cargo check
clean, base cooks for x86_64-unknown-redox.
Refs: local/docs/LG-GRAM-16Z90TP-COMPATIBILITY-PLAN.md review-fix round
Five daemon-local hardening items on top of the AML-mutex fix. acpid is
single-threaded and serves the `acpi` scheme on the same thread that
evaluates AML, so any way it can stall or die stalls or kills every
consumer. These reduce or bound each such way (verified against
local/reference/linux-7.1 ACPICA):
- stall(): refuse >255us and warn >100us instead of an unbounded CPU
busy-spin on the serving thread (ACPICA exsystem.c:129-147). The spec
caps Stall at 100us; a long busy-spin here would delay scheme requests.
- AML mutex release(): a release of a not-currently-held mutex, or by a
non-owning thread, is now logged (ACPICA AE_NOT_ACQUIRED / owner
mismatch, exmutex.c:287,376) rather than silently decrementing depth.
- Static processor-method cache: _PSS/_PSD/_CST/_CPC are fixed after boot
but cpufreqd polls them; cache the first evaluation so later reads never
re-run the AML interpreter under the global lock. Removes acpid's
dominant recurring head-of-line source. Dynamic methods are not cached.
- Panic-free scheme path: every release_global_lock().expect(...) on the
AML-evaluation path is now log-and-continue, and a result.ok()?.unwrap()
that panicked on Ok(None) (absent method) is now `?`. A scheme-daemon
panic kills the `acpi` scheme and wedges every consumer.
- Observability: log AML evaluations >=50ms and mutex-acquire timeouts —
the early-warning signal before a stall becomes a wedge.
Context: the residual under-load boot wedge is NOT in acpid (a mutex-only
baseline wedges identically under load); it is head-of-line blocking in
the single-threaded initnsmgr. See
local/docs/INIT-NAMESPACE-MANAGER-SCALABILITY-PLAN.md and
local/docs/INITNSMGR-CONCURRENCY-DESIGN.md.
The I2C transfer chain was stubbed end-to-end:
- i2cd's transfer path returned 'not implemented yet' for every
transfer, and its wire format didn't match the only in-tree consumer
(i2c-hidd sends a bare I2cTransferRequest, i2cd expected
I2cControlRequest::Transfer).
- intel-lpss-i2cd / dw-acpi-i2cd / amd-mp2-i2cd registered an adapter
name and parked forever without initializing the controller or
executing a transfer.
- intel-lpss-i2cd matched only legacy ACPI HIDs, which do not exist on
Meteor Lake / Arrow Lake platforms (LPSS I2C binds by PCI ID there,
per Linux drivers/mfd/intel-lpss-pci.c).
Replace with real implementations:
- New shared crate drivers/i2c/designware (dw-i2c): DesignWare I2C
master engine ported from Linux 7.1 i2c-designware-master.c and
i2c-designware-common.c — IC_CON + SCL timing computed from ic_clk
with Linux's exact hcnt/lcnt formulas (bxt_i2c_info 133 MHz for
LPSS, 100 MHz for ACPI-designated blocks), SDA hold with RX-hold
workaround, polling transfer engine with RESTART/STOP sequencing,
TX_ABRT decode (Nack/arbitration-lost/abort/timeout), 7/10-bit
addressing, bounded timeouts. Also provides the shared
/scheme/<name> transfer endpoint with register-then-ready ordering.
- intel-lpss-i2cd: PCI discovery for ARL-H (0x7750/0x7751,
0x7778-0x777b) and MTL-P (0x7e50/0x7e51, 0x7e78-0x7e7b) with 32/64-bit
BAR0 decode, ACPI alias resolution via FixedMemory32 == BAR0 across
/scheme/acpi/resources, legacy ACPI-HID path kept, i2cd registration
with bounded retry, serves /scheme/i2c-lpss.
- dw-acpi-i2cd: converted from register-and-park to the shared engine,
serves /scheme/dw-acpi-i2c.
- i2cd: real transfer routing — adapter resolution by name/alias
(exact, normalized, last-component) and forwarding to the provider
daemon's /scheme/<provider>/transfer endpoint.
- i2c-interface: I2cAdapterInfo gains provider_scheme + aliases
(serde-default, wire-compatible).
Tests: dw-i2c 3, intel-lpss-i2cd 3; full workspace cargo check clean;
base cooks for x86_64-unknown-redox.
Refs: local/docs/LG-GRAM-16Z90TP-COMPATIBILITY-PLAN.md Phase 4
The AML mutex `acquire` handler had two defects that let a routine
`_ACQ` on ACPI processor P-state objects (`processor/CPUn/pss`, opened
by cpufreqd) deadlock acpid permanently:
1. Timeout units. ACPI `_ACQ` timeouts are milliseconds and 0xFFFF is
the spec's "wait forever" (ACPICA ACPI_WAIT_FOREVER,
actypes.h:459). The code multiplied the value by 1000, treating it
as seconds, so 0xFFFF became ~18 hours.
2. No ownership. ACPI mutexes are recursive: the owning thread may
re-acquire, each Acquire paired with a Release (ACPICA
acpi_ex_acquire_mutex_object, exmutex.c:140). The old code tracked
only a "held" set with no owner, so a nested acquire by the single
AML thread waited for a release only it could perform — a
self-deadlock.
Because acpid is single-threaded and the same thread serves the
`acpi` scheme socket, a stuck acquire stops acpid answering scheme
requests. The init namespace manager then blocks in the openat it was
proxying, and — since every restricted-namespace path resolution goes
through that single manager — the whole system's open path wedges
(observed: mini never reaches a usable brush login).
Fix: track (owner ThreadId, recursion depth) per mutex, treat a
nested acquire by the owner as a depth bump, interpret the timeout as
milliseconds, and bound any wait to MAX_MUTEX_WAIT (5s) so a
misbehaving AML method can never freeze the scheme-serving thread.
usbhidd already accumulated prior interfaces' endpoint counts (global
enumeration index, matching xhcid's PortState::get_endp_desc), but the
counter was never reset between candidate configurations — a HID device
whose usable config is not the first would get endpoint numbers offset
by the first config's endpoints. xhcid indexes within the selected
configuration only, so reset per config.
Implement the Linux 7.1 xhci.c:4650 xhci_set_usb2_hardware_lpm() enable
path, closing the P7-A gap on top of the P2-B substrate:
- usb/bos.rs: BosUsb2ExtDesc bmAttributes accessors (LPM support, BESL
support/validity, baseline/deep BESL fields) + 3 unit tests
- xhci/mod.rs:
- change_max_exit_latency(): MEL update via Evaluate Context (slot
output context copied, SLOT add flag, DWORD1 low 16 bits = MEL) —
Linux xhci.c:4520
- calculate_hird_besl(): HCS_PARAMS3 U2 latency + device BESL
attributes — Linux xhci.c:4594
- enable_usb2_hw_lpm(): BESL vs HIRD parameter selection, MEL
Evaluate Context, PORTHLPMC/PORTPMSC programming via the existing
Port::enable_lpm — Linux xhci.c:4686-4722
- BESL_ENCODING_US table, XHCI_L1_TIMEOUT_US=512, XHCI_DEFAULT_BESL=4
- xhci/scheme.rs (get_desc): attach-time gate chain — hw_lpm_support &&
per-port HLC (USB 2.0 protocol only) && device USB_LPM_SUPPORT in BOS
&& non-hub class && root-hub-direct (get_desc only runs for root-port
devices). LPM enable failure logs a warning and continues — LPM is
opportunistic; the device works in U0 regardless.
Verified: cargo check -Z build-std --target x86_64-unknown-redox -p
xhcid clean (no new warnings). Unit tests compile-verified; execution
requires redoxer like the crate's existing test suite. Runtime L1
entry validation requires LPM-capable hardware (QEMU's xHCI does not
advertise HLC) — recorded as hardware-validation debt in the USB plan.
Continuation of 92924224 — the same conditional-ack bug class found in
six more drivers: a foreign interrupt on a shared INTx line skipped the
write-back ack, leaving the kernel-masked IRQ line dead forever.
- sb16d, ac97d: ack before the not-ours continue
- rtl8139d, rtl8168d, ixgbed: ack unconditionally, tick only when ours
(the stale 'TODO: spurious interrupts' comments removed — the
spurious-interrupt confusion was this bug)
- ihdgd: ack unconditionally, handle_events/tick only when ours
Verified: cargo check -Z build-std --target x86_64-unknown-redox for
sb16d, ac97d, rtl8139d, rtl8168d, ixgbed, ihdgd — clean, no new
warnings. nvmed audited: not affected (per-queue MSI pattern, no
conditional ack).
Same bug class as the ahcid fix (ad40fffd): the kernel masks the IRQ
line when it delivers an interrupt and only re-arms it on the userspace
write-back (irq scheme write -> acknowledge() -> pic/ioapic_unmask).
When a driver acks only if the interrupt was its own, one foreign
interrupt on a shared INTx line leaves the line masked forever and all
later interrupts for the device are lost.
- e1000d: ack unconditionally; ICR is read-to-clear, so the ownership
check doubles as the device-level status clear
- ihdad: ack before the not-ours continue
- vboxd: ack unconditionally; device-side ack and event handling stay
conditional on host_events
- xhcid: ack unconditionally in the IRQ reactor; for INTx the ownership
check already clears IMAN.IP (RW1C) and device interrupts are masked
before re-arming, so no storm. MSI/MSI-X vectors are never shared, so
behavior there is unchanged (every delivery is ours by construction)
Verified: cargo check -Z build-std --target x86_64-unknown-redox for
e1000d, ihdad, vboxd, xhcid — clean, no new warnings.
fbcond blocked indefinitely in open_display_v2() while handing the console
over to the real graphics driver: boot stopped at "fbcond: Performing
handoff" and never reached the "Opened new display" that follows a
successful open.
fbcond is single-threaded and owns the console, so while it waited there it
stopped draining console writers. getty, login and the login shell all
blocked on their writes, which made the failure look like a slow or hung
shell -- the stall point just tracked whichever process next wrote to the
console. If the graphics driver was itself waiting on the console, the two
deadlocked outright and the console never came back.
The wait comes from the scheme-open handshake: inputd signals the handoff as
soon as the driver registers its path, but the driver may not be serving its
scheme yet, and O_NONBLOCK does not bound the open (it governs later reads).
Split the two halves in inputd's ConsumerHandle: display_path_v2() only does
an fpath on our own handle and returns promptly, while open_display_path_v2()
carries the blocking open. fbcond now resolves the path inline and hands the
open to a worker thread, then picks the result up from the event loop via
poll_pending_handoff(). Console output produced meanwhile is buffered in
pending_writes (already the behaviour while the display is unmapped) and
flushed once the new display is mapped.
The kernel masks an IRQ line when it delivers the interrupt to userspace
(trigger() -> pic_mask()/ioapic_mask()) and only re-enables it when the
driver writes the count back (kwrite -> acknowledge() -> pic_unmask()/
ioapic_unmask()). That write-back is therefore mandatory, not optional.
ahcid only performed it inside the `is > 0` branch. IRQ lines are shared
(on q35 the AHCI controller sits on IRQ 10 with other devices), so an
interrupt raised by a different device reaches the handler with the HBA
interrupt status register reading 0. In that case ahcid returned without
writing back, leaving the line masked permanently: every subsequent AHCI
completion was lost and all disk I/O blocked forever.
Move the write-back out of the branch so the line is re-armed whether or
not the interrupt turned out to be ours, and keep scheme.tick() gated on
`is > 0` since there is no completion to service otherwise. Device-level
status is still cleared before unmasking, so this cannot cause a storm.
The handoff is one-shot: inputd signals it the moment the real display
driver registers its path, but the driver's scheme may not serve the v2
open / capability query for a few more milliseconds. Without a retry, a
transient not-ready driver leaves the framebuffer permanently blank —
the login prompt is drawn to the framebuffer VT, so the console appears
dead even though boot continues on the serial mirror (observed as the
2026-07-20 '-vga std' boot freeze at 'Performing handoff').
Retry the reopen for up to 250 x 10ms; the driver becomes ready well
within this bound in practice. On success after retries, log the retry
count at info level. If the driver is still not ready after the bound,
emit a single explicit warning instead of per-attempt warn spam (the
per-attempt message in display.rs is demoted to debug). Serial mirror
remains unaffected in the failure case.
'Running IRQ reactor with IRQ file and event queue' and 'in polling
mode' were debug! since the file was created, so test-xhci-irq-qemu.sh
(which greps for the interrupt-driven line) could never see them at the
default info log level — the IRQ-driven path had zero runtime evidence.
They are one-time startup lines; promote to info so the interrupt
delivery mode is observable and the proof can detect it.