acpi-i2c-hid: implement wave 1 boot-path diagnostics and service wiring

This commit is contained in:
2026-04-22 21:31:19 +01:00
parent 0323d7b8a7
commit 78e5d99fb8
13 changed files with 725 additions and 33 deletions
@@ -151,6 +151,13 @@ surface that can matter during boot on modern bare metal.
- GPIO expanders used to expose reset, enable, interrupt, or wake lines for input devices
- platform-specific I2C controller companions that gate access to the actual `I2C-HID` device
Concrete implementation carriers in local Linux reference tree:
- `drivers/hid/intel-thc-hid/intel-quicki2c/*` for Intel THC QuickI2C-backed HID paths
(Lunar/Panther/Nova/Wildcat generations)
- `drivers/gpio/*` families used as ACPI `GpioInt`/`GpioIo` providers for input reset/wake rails
- `drivers/i2c/i2c-core-acpi.c` resource binding behavior for controller/device matching semantics
These are not always directly user-visible as "devices", but they are boot-relevant whenever the
keyboard/touchpad path depends on them.
@@ -191,3 +198,79 @@ For boot-to-login on modern laptops, the correct priority is:
4. introduce the minimal native I2C userspace substrate
5. implement Intel LPSS controller ownership
6. implement `i2c-hidd`
## Boot-Critical I2C Addendum (post-Phase D)
The remaining boot-critical order after initial `i2c-hidd` is:
1. Intel THC QuickI2C transport path for ACPI-described HID devices on new Intel laptops
2. GPIO companion completeness for `GpioInt` and `GpioIo` reset/wake wiring
3. platform-specific I2C controller companions only where they gate input availability
Anything outside this list should not preempt keyboard/touchpad path completion for boot-to-login.
### Concrete device classes to implement next (boot-first order)
| Priority | Device class | Linux carrier in tree | Red Bear status |
|---|---|---|---|
| P0 | Intel THC QuickI2C transport (`HID over THC`) | `drivers/hid/intel-thc-hid/intel-quicki2c/*` | detection/parking landed, transport still missing |
| P1 | GPIO companions for `GpioInt`/`GpioIo` (reset/wake rails) | `drivers/gpio/*`, ACPI resource flow | partially landed, board-specific gaps remain |
| P2 | Controller-companion ACPI methods (`_DSM/_DSD`) that gate input | `i2c-core-acpi.c`, QuickI2C ACPI helpers | partially landed, still platform-dependent |
| P3 | USB-C/UCSI I2C only on machines where input depends on it | `drivers/usb/typec/ucsi/*` and ACPI glue | partial: ACPI UCSI (`PNP0CA0`/`AMDI0042`) discovery + bounded I2C probe/policy surface landed; runtime UCSI transport/partner path still missing |
This order is strict for boot-to-login resilience on modern laptops.
Current in-tree staging note:
- initfs boot ownership for ACPI/PIC is now explicit: `40_pcid.service` -> `41_acpid.service` -> `40_hwd.service` -> `40_pcid-spawner-initfs.service`; `hwd` no longer spawns `acpid` or `pcid` ad hoc.
- `redbear-live-mini` now enables `00_i2c-hidd.service` in non-blocking mode (`oneshot_async`) instead of masking it with `cmd = "true"`.
- `redbear-live-mini` now carries non-blocking `00_i2c-gpio-expanderd.service` and `00_ucsid.service` overlays so the boot-minimal image keeps companion GPIO and UCSI topology diagnostics aligned with the boot-critical I2C path.
- `intel-thc-hidd` now performs ACPI companion resolution, `_DSM` capability reads, `PNP0C50` scan, THC-bound candidate diagnostics, BAR mapping, and registers a minimal `intel-thc-quicki2c` adapter into `i2cd` with transfer handling through THC I2C subIP (DesignWare-style path).
- `intel-thc-hidd` now also consumes bounded ACPI controller-companion methods `ICRS` and `ISUB` when present, using them to refine adapter speed/addressing diagnostics and to apply ACPI timing overrides for DW SCL high/low counters.
- `intel-thc-hidd` now emits compact `RB_THC_HIDD_SCHEMA` / `RB_THC_HIDD` marker lines with THC-bound PNP0C50 candidate counts and status reasons (`available` vs `not-available`) so boot logs can distinguish missing ACPI binding surfaces from transport/runtime faults.
- `intel-thc-hidd` now canonicalizes the primary `RB_THC_HIDD` status field and warns/coerces unknown values to `error`, matching the parser-robust status policy used in `hwd`/`i2c-hidd`.
- `RB_THC_HIDD` / `RB_THC_HIDD_FATAL` markers now include `generation=<n>` for explicit correlation with other boot-readiness streams.
- `RB_THC_HIDD` status semantics now explicitly include `not-ready` (ACPI symbols not ready) and `error` (ACPI symbol scan failure), so init ordering and enumeration faults are disambiguated in CI logs.
- `intel-thc-hidd` now emits `RB_THC_HIDD_FATAL status=error ...` markers on hard-stop failures (BAR map/size failures, unexpected DW component type, i2cd registration failure, provider-loop failure) so fatal transport bring-up exits are machine-classifiable.
- `hwd` now emits compact `RB_THC_QUICKI2C status=not-ready ...` and UCSI `RB_UCSI_* status=not-ready ...` markers even when ACPI symbol enumeration returns `WouldBlock`, preserving machine-readable readiness signals during early init ordering windows.
- `hwd` now reports THC companion `ICRS`/`ISUB` method readiness counts (`thc_quicki2c_ready`) during ACPI probe so missing controller-companion surfaces are visible before driver bring-up.
- `hwd` now also emits compact `RB_THC_QUICKI2C_SCHEMA` / `RB_THC_QUICKI2C` marker lines (`status=absent|available|not-ready`) for THC companion readiness scraping in CI/log pipelines.
- `RB_THC_QUICKI2C` now also includes `generation=<n>` for consistent correlation semantics with other marker streams.
- `hwd` now assigns marker generation per ACPI probe pass and threads it through UCSI/THC fallback markers as well, eliminating `generation=0` ambiguity on local fallback paths.
- schema markers now also carry generation (`RB_UCSI_SCHEMA`, `RB_THC_QUICKI2C_SCHEMA`, `RB_UCSID_SCHEMA`, `RB_I2C_HIDD_SCHEMA`, `RB_THC_HIDD_SCHEMA`) so schema and data lines share the same correlation key shape.
- `i2c-hidd` now consults THC companion `ICRS` when bound through `intel-thc-quicki2c`, and performs a bounded slave-address override if HID `_CRS` I2C address and companion-method address disagree.
- `i2c-hidd` now emits compact `RB_I2C_HIDD_SCHEMA` and `RB_I2C_HIDD_BLOCKER` markers so unresolved THC resource-source adapter matches are machine-readable in boot logs (`reason=thc_transport_adapter_unavailable`).
- `i2c-hidd` marker emitters now canonicalize status fields (`RB_I2C_HIDD_SNAPSHOT`, `RB_I2C_HIDD_BLOCKER`) and warn/coerce unknown values to `error` for parser-safe output under schema drift.
- `RB_I2C_HIDD_BLOCKER` now also includes `generation=<n>`, aligned to the current scan cycle so blocker and snapshot events are directly correlatable.
- `RB_I2C_HIDD_BLOCKER` status semantics now also include `not-ready` (`acpi_symbols_not_ready`) and `error` (`acpi_symbol_scan_error`) on the ACPI symbol scan path, aligning HID-consumer readiness reporting with THC producer markers.
- `i2c-hidd` scan-state handling now preserves that distinction in snapshots: ACPI `WouldBlock` yields `RB_I2C_HIDD_SNAPSHOT status=not-ready reason=acpi_symbols_not_ready` (not `not-available`), avoiding false “no devices” classification during early init ordering.
- `i2c-hidd` now emits `RB_I2C_HIDD_SNAPSHOT` per scan cycle (`status`, `reason`, `devices`, `adapters`, `started`, `probe_ok`, `probe_failed`) so boot logs expose HID bring-up progress and failure density, not only blocker edges.
- `RB_I2C_HIDD_SNAPSHOT` now also carries `generation=<n>`, allowing cycle-level correlation with other readiness marker streams.
- the same `RB_I2C_HIDD_SNAPSHOT` stream now includes `status=error reason=scan_failed` on scan-cycle exceptions, preserving explicit machine-readable failure state even when scan aborts early.
- The in-tree bridge now derives adapter speed profile from ACPI-bound devices and includes bounded one-shot controller recovery/reinit on non-addressing transfer failures.
- `ucsid` now exposes `/scheme/ucsi` summary/device records with policy-driven `input_critical` classification, per-node probe policy, bounded AMDI0042 I2C probe telemetry, and PNP0CA0 ACPI `_DSM` capability-mask diagnostics (Linux carrier aligned with `ucsi_acpi.c` function-mask semantics).
- `ucsid` now also supports a policy/env-gated bounded PNP0CA0 `_DSM` function-2 read-call probe (`REDBEAR_UCSI_DSM_READ_PROBE` / `probe_dsm_read`) to surface ACPI transport readiness without enabling full UCSI command transport.
- when that bounded read probe returns a buffer payload, `ucsid` now surfaces a minimal UCSI header snapshot (`version_bcd` at offset 0 and `cci` at offset 4) for early transport-readiness diagnostics.
- `hwd` now opportunistically consumes `/scheme/ucsi/summary` (when available) and logs UCSI transport-readiness snapshot counts/devices on the ACPI probe path.
- `hwd` now classifies UCSI summary availability as `available` / `not-ready` / `not-available` / `error`, making initfs service-order gaps distinguishable from summary parse/probe failures.
- `hwd` now emits a compact `RB_UCSI_SNAPSHOT ...` key-value marker line for CI/log scrapers in addition to the human-readable UCSI status logs.
- `ucsid` now emits compact `RB_UCSID_SUMMARY ...` and per-device `RB_UCSID_DEVICE ...` marker lines so readiness can be scraped directly from daemon logs without scheme reads.
- `hwd` now mirrors per-device compact markers as `RB_UCSI_DEVICE ...` when `/scheme/ucsi/summary` is available, so CI can consume transport-readiness from the `hwd` boot stream as well.
- `ucsid` now classifies `transport_blocker` for `input_critical` devices that are not transport-ready, and exports `transport_blocked_input_critical` in summary markers for boot-priority triage.
- `ucsid`/`hwd` compact markers now include `health=ok|degraded` plus dedicated `RB_UCSID_HEALTH` / `RB_UCSI_HEALTH` lines keyed by `transport_blocked_input_critical`.
- `RB_UCSID_SUMMARY` and mirrored `RB_UCSI_SUMMARY` now carry `generation=<n>` so CI can correlate `hwd` snapshots with a specific `ucsid` scan cycle and detect stale reads.
- generation is now assigned before each `ucsid` scan pass and included on per-device markers (`RB_UCSID_DEVICE` / mirrored `RB_UCSI_DEVICE`) so device-level lines are cycle-correlated as well.
- `RB_UCSI_SNAPSHOT` is now self-contained with `generation`, `health`, and `transport_blocked_input_critical` when summary is available, while preserving explicit `status=*` for not-ready/not-available/error cases.
- `hwd` now emits `RB_UCSI_SNAPSHOT status=absent ...` when no ACPI UCSI candidates are discovered, so CI can distinguish true surface absence from service readiness failures.
- `ucsid` compact markers now emit explicit status on every scan cycle (`status=available|absent|not-ready|error`) via `RB_UCSID_SUMMARY` / `RB_UCSID_HEALTH`.
- `ucsid` now also emits `RB_UCSID_SUMMARY` / `RB_UCSID_HEALTH` with `status=error` and `health=unknown` on scan-cycle failures, so CI can distinguish explicit UCSI scan faults from stale/missing marker streams.
- on scan failure, `ucsid` now also resets shared `/scheme/ucsi/summary` counters to a clean `status=error` state for that generation (instead of carrying stale counters from the previous successful cycle).
- `ucsid` ACPI-symbol `WouldBlock` no longer collapses into `status=absent`; it now emits explicit `status=not-ready` markers so early-init readiness is not misclassified as true UCSI surface absence.
- `/scheme/ucsi/summary` now carries explicit producer status (`available|absent|not-ready|error`) and `hwd` consumes that field when mirroring `RB_UCSI_*`, preventing false `status=available` interpretations from zeroed summary counters during not-ready/error cycles.
- `hwd` now normalizes UCSI summary status parsing (trims whitespace and accepts case variants) and warns before coercing unknown statuses to `error`, improving resilience to producer/schema drift.
- `hwd` now also cross-checks status against summary counters: contradictory payloads (`available` with zero devices, or `absent` with nonzero devices) are warned and coerced to safe marker output (`absent` / `error`) instead of being mirrored verbatim.
- for `status=not-ready|not-available|error`, `hwd` now warns if nonzero payload counters/devices are present before emitting fallback status markers, making producer-status drift explicit in logs.
- `ucsid` startup default summary state is now explicitly `status=not-ready` (instead of implicit empty/default status), so early pre-scan reads of `/scheme/ucsi/summary` remain unambiguous.
- mirrored `hwd` markers now carry explicit status on `RB_UCSI_SUMMARY` / `RB_UCSI_HEALTH` (`available|absent|not-ready|not-available|error`), keeping status semantics aligned across both producers and fallback paths.
- mirrored `RB_UCSI_DEVICE` now carries richer readiness context (`i2c_backed`, DSM read support/probe flags) to match `ucsid` diagnostics from a single boot-log stream.
- for `status=not-ready|not-available|error`, `hwd` now emits fallback `RB_UCSI_SUMMARY` / `RB_UCSI_HEALTH` lines (not just `RB_UCSI_SNAPSHOT`) so CI can rely on the same marker keys in every status path.
- Native THC DMA/report transport is still missing.
+5 -4
View File
@@ -24,7 +24,8 @@ What is still open:
- userspace AML bootstrap no longer depends solely on `RSDP_ADDR` on x86, but the explicit boot-path handoff contract is still underdocumented and non-BIOS paths remain unresolved,
- normal service ownership is still transitional: `hwd` and `acpid` live on the initfs boot path rather than under a stable long-lived rootfs service contract,
- AML readiness is still coupled to PCI registration timing,
- `hwd` still spawns `acpid` ad hoc and the non-ACPI `LegacyBackend` fallback is effectively a TODO no-op,
- initfs boot order now starts `pcid` and `acpid` explicitly before `hwd`, and `hwd` no longer spawns `acpid` ad hoc,
- the non-ACPI `LegacyBackend` fallback is still effectively a TODO no-op,
- failed `/scheme/acpi/register_pci` handoff now uses a bounded retry path before degrading, but the degraded contract is still not strong enough to call Wave 1 closed,
- the `\_S5` / shutdown path is not yet trustworthy enough to call robust,
- `/scheme/acpi/power` is still not a trustworthy runtime power surface,
@@ -122,9 +123,9 @@ bounded-hardware, or release-grade completeness.
- `acpid` startup still contains active panic-grade `expect` paths.
- userspace AML bootstrap now has an explicit handoff path plus x86 BIOS fallback, but the producer side of that contract is still underdocumented and non-BIOS fallback remains unresolved.
- service lifecycle is still transitional: `hwd` and `acpid` are primarily initfs-owned and `acpid` is still spawned ad hoc rather than by an explicit long-lived rootfs unit.
- service lifecycle is still transitional: `hwd` and `acpid` are primarily initfs-owned rather than by an explicit long-lived rootfs unit.
- `\_S5` derivation currently depends on AML readiness that is still gated on PCI registration.
- `hwd` owns an ad hoc `acpid` spawn path, while `LegacyBackend` fallback is still a TODO no-op rather than a meaningful degraded probe path.
- `hwd` no longer owns an ad hoc `acpid` spawn path; `LegacyBackend` fallback is still a TODO no-op rather than a meaningful degraded probe path.
- `pcid` can continue without ACPI integration after a bounded retry window, so AML readiness still transitions from transient-not-ready to durable degraded mode without a stronger recovery contract.
- post-PCI AML bootstrap failure is now surfaced as an explicit error instead of a quietly empty symbol surface, but that path still needs broader boot-path proof.
- `set_global_s_state()` is effectively `S5`-only.
@@ -198,7 +199,7 @@ These rules govern all work from this plan:
| Wave | Theme | Current status | Main blocker | Primary closure signal |
|---|---|---|---|---|
| Wave 0 | Contracts / truthfulness | partially complete | doc drift across adjacent ACPI-facing docs | one canonical vocabulary and ownership story across the repo |
| Wave 1 | Startup hardening / parser policy | partially complete | boot-path contract gaps (explicit `RSDP_ADDR` producer ownership, ad hoc `acpid` spawn) plus remaining panic-grade startup and fault paths | firmware-origin startup failures are bounded and typed and AML bootstrap preconditions are explicit |
| Wave 1 | Startup hardening / parser policy | partially complete | boot-path contract gaps (explicit `RSDP_ADDR` producer ownership and still-transitional initfs lifecycle) plus remaining panic-grade startup and fault paths | firmware-origin startup failures are bounded and typed and AML bootstrap preconditions are explicit |
| Wave 2 | AML ordering / shutdown / sleep scope | partially complete | shutdown/reboot result semantics and broader runtime proof still remain incomplete | deterministic `\_S5` derivation and bounded shutdown behavior |
| Wave 3 | Honest ACPI power surface | open | current power reporting is real but still provisional and under-validated | `/scheme/acpi/power` exposes only behavior that the runtime evidence can honestly support |
| Wave 4 | AML physmem / EC / runtime fault handling | partially complete | placeholder-like runtime error behavior remains in places | no correctness-critical fabricated runtime values |
@@ -195,19 +195,26 @@ Grounded in:
- `recipes/core/base/source/drivers/hwd/src/backend/acpi.rs`
- `recipes/core/base/source/drivers/hwd/src/main.rs`
- `recipes/core/base/source/drivers/acpid/src/acpi.rs`
- `recipes/core/base/source/init.initfs.d/40_pcid.service`
- `recipes/core/base/source/init.initfs.d/41_acpid.service`
- `recipes/core/base/source/init.initfs.d/40_hwd.service`
- `recipes/core/base/source/init.initfs.d/40_pcid-spawner-initfs.service`
Strict order:
1. kernel bootstrap / memory / early ACPI / IRQ / serio baseline
2. userspace bootstrap
3. `hwd` starts
4. `hwd` ACPI backend spawns `acpid`
5. `hwd` main spawns `pcid`
6. `acpid` waits for PCI registration before AML-symbol readiness
3. `pcid` starts in initfs (`40_pcid.service`)
4. `acpid` starts in initfs (`41_acpid.service`)
5. `hwd` starts (`40_hwd.service`) and probes only after `pcid` + `acpid`
6. `pcid-spawner` runs (`40_pcid-spawner-initfs.service`)
7. `acpid` waits for PCI registration before AML-symbol readiness
### Shared initfs target membership (not strict serialization)
Grounded in:
- `recipes/core/base/source/init.initfs.d/40_pcid.service`
- `recipes/core/base/source/init.initfs.d/40_hwd.service`
- `recipes/core/base/source/init.initfs.d/41_acpid.service`
- `recipes/core/base/source/init.initfs.d/40_pcid-spawner-initfs.service`
- `recipes/core/base/source/init.initfs.d/40_ps2d.service`
- `recipes/core/base/source/init.initfs.d/40_drivers.target`
@@ -216,7 +223,7 @@ Grounded in:
- `recipes/core/base/source/init.initfs.d/20_graphics.target`
Important nuance:
- `ps2d`, `hwd`, and `pcid-spawner-initfs` all participate in early initfs driver bring-up.
- `ps2d`, `pcid`, `acpid`, `hwd`, and `pcid-spawner-initfs` all participate in early initfs driver bring-up.
- They are grouped by `40_drivers.target`, but they are **not** one single strict serial chain.
## 3. What Linux material Red Bear should borrow into Rust
@@ -0,0 +1,333 @@
diff --git a/drivers/hwd/src/backend/acpi.rs b/drivers/hwd/src/backend/acpi.rs
index 3da41d63..5d1a9466 100644
--- a/drivers/hwd/src/backend/acpi.rs
+++ b/drivers/hwd/src/backend/acpi.rs
@@ -1,27 +1,36 @@
use amlserde::{AmlSerde, AmlSerdeValue};
-use std::{error::Error, fs, process::Command};
+use std::{error::Error, fs};
use super::Backend;
pub struct AcpiBackend {
- rxsdt: Vec<u8>,
+ _rxsdt: Vec<u8>,
}
impl Backend for AcpiBackend {
fn new() -> Result<Self, Box<dyn Error>> {
let rxsdt = fs::read("/scheme/kernel.acpi/rxsdt")?;
- // Spawn acpid
- //TODO: pass rxsdt data to acpid?
- #[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(Command::new("acpid"));
-
- Ok(Self { rxsdt })
+ Ok(Self { _rxsdt: rxsdt })
}
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
+ let mut boot_critical_input_candidates = 0usize;
+ let mut thc_candidates = 0usize;
+ let mut non_hid_i2c_candidates = 0usize;
+
// Read symbols from acpi scheme
- let entries = fs::read_dir("/scheme/acpi/symbols")?;
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
+ Ok(entries) => entries,
+ Err(err)
+ if err.kind() == std::io::ErrorKind::WouldBlock
+ || err.raw_os_error() == Some(11) =>
+ {
+ log::debug!("hwd: ACPI symbols are not ready yet");
+ return Ok(());
+ }
+ Err(err) => return Err(Box::new(err)),
+ };
// TODO: Reimplement with getdents?
let symbols_fd = libredox::Fd::open(
"/scheme/acpi/symbols",
@@ -100,12 +109,102 @@ impl Backend for AcpiBackend {
"PNP0C0F" => "PCI interrupt link",
"PNP0C50" => "I2C HID",
"PNP0F13" => "PS/2 port for PS/2-style mouse",
+ "80860F41" | "808622C1" => "DesignWare I2C controller",
+ "AMDI0010" | "AMDI0019" | "AMDI0510" => "AMD laptop I2C controller",
+ "INT33C2" | "INT33C3" | "INT3432" | "INT3433" | "INTC10EF" => {
+ "Intel LPSS/SerialIO I2C controller"
+ }
+ "INT34C5" | "INTC1055" => "Intel GPIO controller",
+ "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082" => {
+ "Intel THC companion (QuickI2C/QuickSPI path)"
+ }
+ _ if is_elan_touchpad_id(&id) => "ELAN touchpad (I2C/SMBus path)",
+ _ if is_cypress_touchpad_id(&id) => {
+ "Cypress/Trackpad (non-HID I2C path)"
+ }
+ _ if is_synaptics_rmi_id(&id) => "Synaptics RMI touchpad (I2C/SMBus path)",
_ => "?",
};
log::debug!("{}: {} ({})", name, id, what);
+ if is_boot_critical_i2c_surface(&id) {
+ boot_critical_input_candidates += 1;
+ log::info!("{}: {} is boot-critical for laptop input path", name, id);
+ }
+ if is_thc_companion(&id) {
+ thc_candidates += 1;
+ log::warn!(
+ "{}: {} indicates Intel THC path; DMA/report fast-path is not complete yet",
+ name,
+ id
+ );
+ }
+ if is_non_hid_i2c_input_id(&id) {
+ non_hid_i2c_candidates += 1;
+ }
}
}
}
+
+ if boot_critical_input_candidates == 0 {
+ log::warn!(
+ "hwd: no ACPI boot-critical I2C input candidates found; built-in laptop input may require additional controller/device support"
+ );
+ } else {
+ log::info!(
+ "hwd: ACPI input candidates: total={} thc={} non_hid_i2c={}",
+ boot_critical_input_candidates,
+ thc_candidates,
+ non_hid_i2c_candidates
+ );
+ }
+
Ok(())
}
}
+
+fn is_boot_critical_i2c_surface(id: &str) -> bool {
+ matches!(
+ id,
+ "PNP0C50"
+ | "ACPI0C50"
+ | "80860F41"
+ | "808622C1"
+ | "AMDI0010"
+ | "AMDI0019"
+ | "AMDI0510"
+ | "INT33C2"
+ | "INT33C3"
+ | "INT3432"
+ | "INT3433"
+ | "INTC10EF"
+ | "INT34C5"
+ | "INTC1055"
+ | "INTC1050"
+ | "INTC1051"
+ | "INTC1080"
+ | "INTC1081"
+ | "INTC1082"
+ ) || is_elan_touchpad_id(id)
+ || is_cypress_touchpad_id(id)
+ || is_synaptics_rmi_id(id)
+}
+
+fn is_thc_companion(id: &str) -> bool {
+ matches!(id, "INTC1050" | "INTC1051" | "INTC1080" | "INTC1081" | "INTC1082")
+}
+
+fn is_elan_touchpad_id(id: &str) -> bool {
+ id.starts_with("ELAN")
+}
+
+fn is_cypress_touchpad_id(id: &str) -> bool {
+ id.starts_with("CYAP")
+}
+
+fn is_synaptics_rmi_id(id: &str) -> bool {
+ id.starts_with("SYNA")
+}
+
+fn is_non_hid_i2c_input_id(id: &str) -> bool {
+ is_elan_touchpad_id(id) || is_cypress_touchpad_id(id) || is_synaptics_rmi_id(id)
+}
diff --git a/drivers/pcid-spawner/src/main.rs b/drivers/pcid-spawner/src/main.rs
index a968f4d4..4f0aa7f7 100644
--- a/drivers/pcid-spawner/src/main.rs
+++ b/drivers/pcid-spawner/src/main.rs
@@ -1,4 +1,5 @@
use std::fs;
+use std::env;
use std::process::Command;
use anyhow::{anyhow, Context, Result};
@@ -6,6 +7,38 @@ use anyhow::{anyhow, Context, Result};
use pcid_interface::config::Config;
use pcid_interface::PciFunctionHandle;
+fn strict_usb_boot() -> bool {
+ matches!(
+ env::var("REDBEAR_STRICT_USB_BOOT")
+ .ok()
+ .as_deref()
+ .map(str::to_ascii_lowercase)
+ .as_deref(),
+ Some("1" | "true" | "yes" | "on")
+ )
+}
+
+fn should_detach_in_initfs(initfs: bool, class: u8, subclass: u8, strict_usb_boot: bool) -> bool {
+ if !initfs {
+ return false;
+ }
+
+ // Keep storage controller drivers synchronous in initfs so rootfs discovery stays
+ // deterministic. Everything else is non-blocking to avoid wedging boot on optional
+ // hardware paths (GPU, HID companions, etc).
+ if class == 0x01 {
+ return false;
+ }
+
+ // When explicitly requested, keep USB host controller startup synchronous
+ // for rootfs-on-USB scenarios.
+ if strict_usb_boot && class == 0x0C && subclass == 0x03 {
+ return false;
+ }
+
+ true
+}
+
fn main() -> Result<()> {
let mut args = pico_args::Arguments::from_env();
let initfs = args.contains("--initfs");
@@ -30,6 +63,7 @@ fn main() -> Result<()> {
}
let config: Config = toml::from_str(&config_data)?;
+ let strict_usb_boot = strict_usb_boot();
for entry in fs::read_dir("/scheme/pci")? {
let entry = entry.context("failed to get entry")?;
@@ -55,10 +89,11 @@ fn main() -> Result<()> {
};
let full_device_id = handle.config().func.full_device_id;
+ let device_addr = handle.config().func.addr;
log::debug!(
"pcid-spawner enumerated: PCI {} {}",
- handle.config().func.addr,
+ device_addr,
full_device_id.display()
);
@@ -67,7 +102,7 @@ fn main() -> Result<()> {
.iter()
.find(|driver| driver.match_function(&full_device_id))
else {
- log::debug!("no driver for {}, continuing", handle.config().func.addr);
+ log::debug!("no driver for {}, continuing", device_addr);
continue;
};
@@ -85,16 +120,61 @@ fn main() -> Result<()> {
let mut command = Command::new(program);
command.args(args);
- log::info!("pcid-spawner: spawn {:?}", command);
-
- handle.enable_device();
+ log::info!(
+ "pcid-spawner: matched {} to driver {:?}",
+ device_addr,
+ driver.command
+ );
+ log::info!("pcid-spawner: enabling {} before spawn", device_addr);
+
+ if let Err(err) = handle.try_enable_device() {
+ log::error!(
+ "pcid-spawner: failed to enable {} before spawn: {}",
+ device_addr,
+ err
+ );
+ continue;
+ }
let channel_fd = handle.into_inner_fd();
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
+ log::info!("pcid-spawner: spawn {:?}", command);
#[allow(deprecated, reason = "we can't yet move this to init")]
- daemon::Daemon::spawn(command);
- syscall::close(channel_fd as usize).unwrap();
+ let spawn_result =
+ if should_detach_in_initfs(
+ initfs,
+ full_device_id.class,
+ full_device_id.subclass,
+ strict_usb_boot,
+ ) {
+ log::warn!(
+ "pcid-spawner: detached initfs spawn for {} to avoid blocking early boot",
+ device_addr
+ );
+ daemon::Daemon::spawn_detached(command)
+ } else {
+ daemon::Daemon::spawn(command)
+ };
+ if let Err(err) = spawn_result {
+ log::error!(
+ "pcid-spawner: spawn/readiness failed for {}: {}",
+ device_addr,
+ err
+ );
+ log::error!(
+ "pcid-spawner: {} remains enabled without a confirmed ready driver",
+ device_addr
+ );
+ }
+ if let Err(err) = syscall::close(channel_fd as usize) {
+ log::error!(
+ "pcid-spawner: failed to close channel fd {} for {}: {}",
+ channel_fd,
+ device_addr,
+ err
+ );
+ }
}
Ok(())
diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs
index 61cd9a78..a15e5f38 100644
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -12,6 +12,7 @@ use pci_types::{
};
use redox_scheme::scheme::register_sync_scheme;
use scheme_utils::Blocking;
+use syscall::{SendFdFlags, sendfd};
use crate::cfg_access::Pcie;
use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, PciRom};
@@ -262,14 +263,13 @@ fn daemon(daemon: daemon::Daemon) -> ! {
let access_fd = socket
.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0)
.expect("failed to issue this resource");
- let access_bytes = access_fd.to_ne_bytes();
- let _ = register_pci
- .call_wo(
- &access_bytes,
- syscall::CallFlags::WRITE | syscall::CallFlags::FD,
- &[],
- )
- .expect("failed to send pci_fd to acpid");
+ sendfd(
+ register_pci.raw(),
+ access_fd as usize,
+ SendFdFlags::empty().bits(),
+ 0,
+ )
+ .expect("failed to send pci_fd to acpid");
}
Err(err) => {
if err.errno() == libredox::errno::ENODEV {
@@ -13,4 +13,5 @@ cp "${COOKBOOK_SOURCE}/quirks.d/30-net.toml" "${COOKBOOK_STAGE}/etc/quirks.d/30-
cp "${COOKBOOK_SOURCE}/quirks.d/40-storage.toml" "${COOKBOOK_STAGE}/etc/quirks.d/40-storage.toml"
cp "${COOKBOOK_SOURCE}/quirks.d/50-system.toml" "${COOKBOOK_STAGE}/etc/quirks.d/50-system.toml"
cp "${COOKBOOK_SOURCE}/quirks.d/60-i2c-hid.toml" "${COOKBOOK_STAGE}/etc/quirks.d/60-i2c-hid.toml"
cp "${COOKBOOK_SOURCE}/quirks.d/70-ucsi.toml" "${COOKBOOK_STAGE}/etc/quirks.d/70-ucsi.toml"
"""
@@ -0,0 +1,38 @@
# ACPI UCSI policy overrides.
#
# ucsid discovers ACPI UCSI devices and tracks I2C-backed ports. This file
# provides platform-specific overrides for boot-policy classification and
# bounded I2C probing.
#
# Matching is exact. At least one match field must be present.
# Multiple matching rules are applied in file order; later rules override
# earlier values for the same key.
#
# Supported match keys:
# sys_vendor, board_vendor, board_name, board_version,
# product_name, product_version, bios_version,
# acpi_path, ucsi_id
#
# Supported action keys:
# input_critical mark this UCSI node as input-critical for boot diagnostics
# probe_i2c override bounded UCSI I2C probing for this node
# probe_dsm_read override bounded ACPI UCSI _DSM function-2 read probe
#
# Global probe default comes from REDBEAR_UCSI_I2C_PROBE.
# Global DSM-read probe default comes from REDBEAR_UCSI_DSM_READ_PROBE.
# Use per-platform rules here to force enable or disable probing.
#
# When `probe_dsm_read` is enabled and succeeds, ucsid records a bounded
# snapshot (`version_bcd`, `cci`) from returned UCSI header bytes when present.
# ucsid also emits per-device `transport_ready` and `transport_readiness_reason`
# so boot consumers can use a single readiness signal instead of recomputing
# policy/capability/probe outcomes.
#
# Example:
#
# [[ucsi_policy]]
# match.sys_vendor = "AMD"
# match.product_name = "Example Laptop 14"
# match.ucsi_id = "AMDI0042"
# input_critical = true
# probe_i2c = true
+1
View File
@@ -69,6 +69,7 @@ done
echo "==> Linking recipe patches from local/patches/..."
symlink "../../../local/patches/kernel/redox.patch" "recipes/core/kernel/redox.patch"
symlink "../../../local/patches/base/redox.patch" "recipes/core/base/redox.patch"
symlink "../../../local/patches/base/P2-boot-runtime-fixes.patch" "recipes/core/base/P2-boot-runtime-fixes.patch"
# ── 3. Custom recipe symlinks ──────────────────────────────────────
echo "==> Linking custom recipes from local/recipes/..."
+1
View File
@@ -215,6 +215,7 @@ echo ""
section "Ensuring recipe patch symlinks..."
symlink "../../../local/patches/kernel/redox.patch" "recipes/core/kernel/redox.patch"
symlink "../../../local/patches/base/redox.patch" "recipes/core/base/redox.patch"
symlink "../../../local/patches/base/P2-boot-runtime-fixes.patch" "recipes/core/base/P2-boot-runtime-fixes.patch"
if [ -d "recipes/core/installer" ]; then
symlink "../../../local/patches/installer/redox.patch" "recipes/core/installer/redox.patch"
else
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
ISO_PATH="${1:-$ROOT_DIR/build/x86_64/redbear-live-mini.iso}"
LOG_PATH="${2:-/tmp/redbear-live-mini-uefi.log}"
TIMEOUT_SECS="${TIMEOUT_SECS:-120}"
if [[ ! -f "$ISO_PATH" ]]; then
echo "error: ISO not found: $ISO_PATH" >&2
exit 1
fi
CODE_FD="/usr/share/edk2/x64/OVMF_CODE.4m.fd"
VARS_SRC="/usr/share/edk2/x64/OVMF_VARS.4m.fd"
VARS_FD="/tmp/rb-ovmf-vars-live-mini.fd"
if [[ ! -f "$CODE_FD" || ! -f "$VARS_SRC" ]]; then
echo "error: OVMF files not found under /usr/share/edk2/x64" >&2
exit 1
fi
cp -f "$VARS_SRC" "$VARS_FD"
ACCEL="tcg"
if [[ -r /dev/kvm && -w /dev/kvm ]]; then
ACCEL="kvm:tcg"
fi
echo "ISO: $ISO_PATH"
echo "LOG: $LOG_PATH"
echo "ACCEL: $ACCEL"
echo "TIMEOUT:${TIMEOUT_SECS}s"
timeout "${TIMEOUT_SECS}s" qemu-system-x86_64 \
-machine "q35,accel=${ACCEL}" \
-cpu max -smp 4 -m 4096 \
-nographic -serial mon:stdio \
-drive "if=pflash,format=raw,readonly=on,file=${CODE_FD}" \
-drive "if=pflash,format=raw,file=${VARS_FD}" \
-cdrom "$ISO_PATH" \
>"$LOG_PATH" 2>&1 || true
echo "---- markers ----"
grep -nE "RedBear OS starting|switchroot to /scheme/initfs|switchroot to /usr|pcid-spawner: matched 0000:00:01.0|panic|UNHANDLED EXCEPTION|emergency shell" "$LOG_PATH" | sed -n '1,200p' || true
echo "---- tail ----"
tail -n 80 "$LOG_PATH" | sed -e 's/\x1b\[[0-9;]*m//g'