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.
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).
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.
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.
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).
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 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.
The suspend_to_ram, read_battery_status, and read_battery_info methods
were incorrectly placed inside 'impl Drop for PhysmapGuard'. They
reference fields (aml_symbols, fadt) that only exist on AcpiContext.
Move them to impl AcpiContext and fix field access (self.fadt.as_ref()
instead of self.fadt()).
Per local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md the base
fork was carrying only 38 of 100 patches in local/patches/base/.
The other 62 patches' content was silently missing from the fork
working tree, even though their .patch files were preserved.
This commit re-applies 41 patches that genuinely still apply
cleanly + 17 hunks that partially applied. Recovery covers:
- D-Bus initfs service wiring (P4-initfs-dbus-services)
- USB service wiring (P4-initfs-usb-drm-services)
- netcfg/dhcp/dhcpv6 driver fixes
- acpid shutdown/PM/quirk fixes
- inputd/ps2d hard-fail logging
- pcid driver interface refinements (server cmd channel)
- virtio-core for VirtualBox support
- ixgbed/rtl8139/rtl8168 net drivers
- ahcid NCQ + per-function interrupt coalescing
- logd persistent logging
- bootstrap procmgr race-condition fixes
- cargo version pin to +rb0.3.0 (synchronized release branch)
58 files changed, +1444/-318 lines.
Untracked mnt/ tree and *.orig / *.rej files cleaned up after
patch application (leftover from absolute-path patch headers).
Otherwise, an open call to /scheme/acpi/tables will result in nsmgr
blocking on an `openat(acpi_root, "tables")` call, which will never
occur if acpid is itself blocking on a `ForkNs` call to nsmgr before it
can serve any scheme requests.