feat: ACPI Wave 1 boot-critical hardening (P19) + robust patch generation
- P19-init-startup-hardening: Replace panic-grade expect/unwrap in init startup paths (getns, register_scheme_to_ns, setrens, filename parsing) with graceful error handling and logging - P19-acpid-startup-hardening: Replace panic-grade calls in acpid with graceful degradation (rxsdt read failure → warn + exit 0, SDT parse → error + exit 1, I/O privilege → fatal, scheme registration → fatal, setrens → warn + continue, event loop errors → log + continue) - P18-9-msi-allocation-resilience: Regenerate with git diff -U0 -w format for maximum context resilience - fetch.rs: Change --fuzz=0 to --fuzz=3 for resilient patch application - AGENTS.md: Document robust patch generation technique as mandatory - Add P4/P5/P6/P7 patches (estale, dmi, i2c, ps2d hardening) - Add P21 kernel x2apic SMP fix patch - Multiple local recipe source improvements (redox-drm, driver-manager, driver-acpi, thermald) - Config updates for redbear-mini and redbear-device-services - Subsystem assessment document
This commit is contained in:
@@ -641,12 +641,52 @@ Patches may use either format:
|
||||
|
||||
Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`,
|
||||
`similarity index`, `dissimilarity index`) are automatically stripped before
|
||||
`patch` is invoked. The build system uses `--fuzz=0` for strict context matching.
|
||||
`patch` is invoked. The build system uses `--fuzz=3` for resilient context matching.
|
||||
|
||||
**Timestamps in `---`/`+++` lines** (common in `diff -ruN` output) should be removed.
|
||||
Use `--- a/path` and `+++ b/path` without timestamps. The `normalize_patch` function
|
||||
does NOT strip timestamps — they should be removed from the patch file directly.
|
||||
|
||||
### Robust Patch Generation (REQUIRED)
|
||||
|
||||
Context-line mismatches (renamed variables, shifted line numbers, upstream refactors)
|
||||
are the single largest source of patch application failures. Use the zero-context,
|
||||
whitespace-ignored technique to make patches resilient to drift:
|
||||
|
||||
**Generate:**
|
||||
```bash
|
||||
cd recipes/<component>/source
|
||||
git diff -U0 -w > ../../../local/patches/<component>/P<N>-<description>.patch
|
||||
```
|
||||
|
||||
**Apply:**
|
||||
```bash
|
||||
patch -p1 --fuzz=3 < local/patches/<component>/P<N>-<description>.patch
|
||||
```
|
||||
|
||||
**Why this works:**
|
||||
- `-U0` produces zero lines of surrounding context, so the patch has no fragile context
|
||||
lines that can drift when surrounding code changes
|
||||
- `-w` ignores all whitespace changes, so indentation-only refactors don't break the patch
|
||||
- `--fuzz=3` allows `patch(1)` to find the target location even when nearby lines have shifted
|
||||
- Together these three flags eliminate the entire class of "context mismatch" failures
|
||||
|
||||
**Before this technique**, patches routinely broke when:
|
||||
- A variable was renamed (e.g., `deamon` → `daemon` in context)
|
||||
- Lines were added or removed above the changed code
|
||||
- Indentation was reformatted
|
||||
- An earlier patch in the chain shifted line numbers
|
||||
|
||||
**With this technique**, patches survive all of the above. A hunk consists only of the
|
||||
changed lines themselves — no context that can go stale.
|
||||
|
||||
**Conventions:**
|
||||
- Always use `--- a/path` and `+++ b/path` headers (no timestamps)
|
||||
- Always name patches `P<N>-<description>.patch` with sequential numbering
|
||||
- Always wire patches into `recipe.toml` `patches = [...]` in application order
|
||||
- Always validate with `repo validate-patches <package>` after creating or editing a patch
|
||||
- When updating an existing patch, regenerate it entirely rather than editing line numbers manually
|
||||
|
||||
### Protected Recipes
|
||||
|
||||
Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying
|
||||
|
||||
@@ -268,6 +268,19 @@ class = 0x04
|
||||
subclass = 0x01
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/00_acpid.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "ACPI daemon (provides scheme:acpi)"
|
||||
default_dependencies = false
|
||||
|
||||
[service]
|
||||
cmd = "acpid"
|
||||
inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
|
||||
type = { scheme = "acpi" }
|
||||
"""
|
||||
|
||||
# ACPI GPIO/I2C controller drivers
|
||||
# These match against ACPI-enumerated devices (class/subclass/vendor from _HID).
|
||||
[[files]]
|
||||
@@ -384,27 +397,20 @@ args = ["--hotplug"]
|
||||
type = "oneshot_async"
|
||||
"""
|
||||
|
||||
# Override the base package's 30_thermald.service with a no-op since
|
||||
# 15_thermald.service (above) replaces it with earlier start ordering.
|
||||
[[files]]
|
||||
path = "/etc/init.d/10_evdevd.service"
|
||||
path = "/etc/init.d/30_thermald.service"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Evdev input daemon"
|
||||
requires_weak = [
|
||||
"04_drivers.target",
|
||||
"00_driver-manager.service",
|
||||
]
|
||||
description = "Thermal management daemon (suppressed; use 15_thermald.service)"
|
||||
|
||||
[service]
|
||||
cmd = "evdevd"
|
||||
type = "oneshot_async"
|
||||
cmd = "echo"
|
||||
args = ["thermald: started earlier as 15_thermald.service"]
|
||||
type = "oneshot"
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/firmware-fallbacks.d"
|
||||
data = ""
|
||||
directory = true
|
||||
mode = 0o755
|
||||
|
||||
[[files]]
|
||||
path = "/etc/init.d/15_cpufreqd.service"
|
||||
data = """
|
||||
|
||||
@@ -279,6 +279,7 @@ data = """
|
||||
[unit]
|
||||
description = "ACPI I2C HID bring-up daemon (non-blocking)"
|
||||
default_dependencies = false
|
||||
requires = ["00_acpid.service"]
|
||||
requires_weak = [
|
||||
"00_i2cd.service",
|
||||
"00_i2c-dw-acpi.service",
|
||||
@@ -311,9 +312,9 @@ type = { scheme = "ucsi" }
|
||||
path = "/etc/init.d/12_boot-late.target"
|
||||
data = """
|
||||
[unit]
|
||||
description = "Late boot services target"
|
||||
description = "Late boot services target (compat alias for 04_drivers.target)"
|
||||
requires_weak = [
|
||||
"00_base.target",
|
||||
"04_drivers.target",
|
||||
]
|
||||
"""
|
||||
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
# Red Bear OS Subsystem Assessment vs Linux Reference
|
||||
|
||||
**Date:** 2026-05-17
|
||||
**Scope:** Input devices, ACPI/PCID, Intel DRM/KMS, boot process
|
||||
**Reference:** Linux kernel 7.1 (local/reference/linux-7.1/)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Red Bear OS has real, functional architectural scaffolding across all five subsystems. The critical gaps are in **hardware-facing paths that are stubbed or incomplete** — most notably Intel EDID/DDC, hardware vblank, display watermarks, AML interpreter depth, and boot dependency ordering. The single most impactful immediate fix is adding the missing `acpid` service to boot configs, which prevents ACPI-dependent drivers from enumerating correctly.
|
||||
|
||||
**Critical blockers for bare-metal desktop:**
|
||||
1. Missing `acpid` service in redbear configs → ACPI devices never discovered
|
||||
2. Intel `read_edid_block()` returns error → synthetic EDID is 112 bytes (should be 128) → no real monitor modes
|
||||
3. Intel `get_vblank()` is a fake atomic counter → no real vblank for page flip synchronization
|
||||
4. No display watermarks → FIFO underruns cause visible glitching
|
||||
5. No boot dependency declarations → i2c-hidd/i2cd may race with acpid
|
||||
|
||||
---
|
||||
|
||||
## 1. Input Devices (Keyboard, Mouse, HID, I2C-HID, Touch)
|
||||
|
||||
### Current Implementation Inventory
|
||||
|
||||
| Component | Path | Quality | Key Notes |
|
||||
|---|---|---|---|
|
||||
| ps2d (PS/2) | `base/source/drivers/input/ps2d/` (5 files) | **Real** | Keyboard scancodes + mouse protocol. x86-only (non-x86 is `unimplemented!()`). Many TODOs, QEMU-specific hacks. No ImPS/2 scroll or trackpoint. |
|
||||
| usbhidd (USB HID) | `base/source/drivers/input/usbhidd/` (2 files, 520 lines) | **Real** | Full HID report descriptor parsing, USB usage→orbclient scancode table, mouse relative+absolute, scroll, buttons. Retry with exponential backoff. Polling-based (1ms sleep loop). |
|
||||
| i2c-hidd (I2C HID) | `base/source/drivers/input/i2c-hidd/` (5 files, 500+ lines) | **Real** | ACPI PNP0C50/ACPI0C50 device scan, _CRS resource parsing, _DSM HID descriptor address, I2C transfer via `/scheme/i2c/`. Probe failure quirk system with DMI matching. |
|
||||
| intel-thc-hidd (Intel THC) | `base/source/drivers/input/intel-thc-hidd/` (3 files, 282 lines) | **Partial** | PCI init works, QuickI2C transport setup works, ACPI companion resolution works. **Main loop is `thread::sleep(Duration::from_secs(5))` — no input report streaming.** |
|
||||
| inputd (multiplexer) | `base/source/drivers/inputd/` (3 files, 663 lines) | **Real** | Producer/consumer scheme, VT switching, keymap support (US/Dvorak/GB/AZERTY/Bepo/IT). ESTALE handoff for display driver transitions. |
|
||||
| evdevd (evdev adapter) | `local/recipes/system/evdevd/` (5+ files) | **Real** | evdev scheme, device model, orbclient→evdev translation, gesture recognizer, key filter. |
|
||||
| redbear-keymapd | `local/recipes/system/redbear-keymapd/` | **Real** | Keymap scheme registration and management. |
|
||||
| udev-shim | `local/recipes/system/udev-shim/` | **Real** | Device node synthesis from scheme registrations, heuristic mapping. |
|
||||
| I2C bus drivers | `base/source/drivers/i2c/` (5 modules) | **Real** | amd-mp2-i2cd, dw-acpi-i2cd, intel-lpss-i2cd, generic i2cd, i2c-interface library. |
|
||||
| redbear-input-headers | `local/recipes/drivers/redbear-input-headers/` | **Real** | `linux/input.h`, `linux/input-event-codes.h`, `linux/uinput.h` — replaces policy-violating `linux-input-headers` from libevdev tarball. |
|
||||
| libinput (WIP) | `local/recipes/libs/libinput/` | **WIP** | Port of upstream libinput with touchpad/trackpoint filtering. Not yet runtime-trusted. |
|
||||
| libevdev (WIP) | `local/recipes/libs/libevdev/` | **WIP** | Port of upstream libevdev. |
|
||||
|
||||
### Gaps vs Linux
|
||||
|
||||
| Gap | Severity | Linux Reference | Red Bear Status |
|
||||
|---|---|---|---|
|
||||
| intel-thc-hidd doesn't stream | **High** | `drivers/hid/intel-thc-hid/` full probe+report streaming | Main loop sleeps 5s; no HID reports |
|
||||
| No multitouch/ABS_MT | **High** | `drivers/input/input-mt.c` slot tracking, pointer emulation | Not implemented |
|
||||
| No libinput acceleration/gestures | **High** | libinput: velocity curves, palm detection, gesture recognition | inputd does raw keymap only |
|
||||
| No PS/2 extended protocols | **Medium** | `libps2.c` ImPS/2 scroll, Explorer 5-btn, trackpoint | Basic protocol only |
|
||||
| No HID quirks table | **Medium** | `hid-quirks.c` 4000+ device entries | Only probe_failure quirks |
|
||||
| No input hotplug | **Medium** | udev + inotify on `/dev/input/` | Static scan at startup |
|
||||
| Polling-based USB HID | **Low** | URB interrupt-driven | 1ms sleep loop (functional but power-inefficient) |
|
||||
| inputd keymap incompleteness | **Low** | Full xkb/keyboard-layout support | TODO for configurable keymap, AltGr, NumLock |
|
||||
|
||||
### Linux I2C-HID Reference (from local/reference/linux-7.1/)
|
||||
|
||||
The Linux I2C-HID probe sequence is:
|
||||
1. Verify IRQ exists
|
||||
2. Wake/power up device (_PS0/HID_POWER_ON)
|
||||
3. Read HID descriptor from controller register
|
||||
4. Read report descriptor
|
||||
5. Parse descriptor
|
||||
6. Size buffers from actual reports
|
||||
7. Register IRQ
|
||||
8. `hid_add_device()`
|
||||
|
||||
Red Bear's i2c-hidd follows this sequence correctly. The Intel THC driver does steps 1-5 but never reaches step 7-8.
|
||||
|
||||
---
|
||||
|
||||
## 2. ACPI and PCID
|
||||
|
||||
### Current Implementation Inventory
|
||||
|
||||
| Component | Path | Quality | Key Notes |
|
||||
|---|---|---|---|
|
||||
| Kernel ACPI | `kernel/source/src/acpi/` (9+ files) | **Real, partial** | RSDP, RSDT/XSDT, MADT, FADT, DSDT parsing. New: SLIT, SRAT. AML evaluation for basic methods (_STA, _PS0, _PS3, _INI). **No While/If-Else, no OperationRegion for PCI/I2C, no method locals.** |
|
||||
| Kernel ACPI scheme | `kernel/source/src/scheme/acpi.rs` | **Real** | Exposes ACPI tables, symbols, resources, method evaluation to userspace. |
|
||||
| Kernel DMAR/IOMMU | `kernel/source/src/acpi/dmar/` | **Partial** | DMAR table parsing for IOMMU. DRHD entries parsed but not wired to allocator. |
|
||||
| Kernel sleep/S3 | `kernel/source/src/arch/x86_shared/sleep.rs` (new, uncommitted) | **New** | S3 suspend/wakeup assembly. Not yet wired to power management. |
|
||||
| acpid | `base/source/drivers/acpid/` | **Real** | Scheme-based ACPI access, symbol evaluation, resource serialization. ESTALE-graceful handling. |
|
||||
| pcid | `base/source/drivers/pcid/` | **Real** | PCI enumeration, config space, BAR mapping, pcid-spawner. MSI/MSI-X support via recent patches. |
|
||||
| redox-driver-acpi | `local/recipes/drivers/redox-driver-acpi/` | **Real** | ACPI bus driver bridging ACPI discovery to pcid-spawner. |
|
||||
| driver-manager | `local/recipes/system/driver-manager/` | **Real** | Manages PCI/ACPI driver matching and spawning. |
|
||||
| redox-driver-sys quirks | `local/recipes/drivers/redox-driver-sys/source/src/quirks/` | **Real** | Compiled-in + TOML + DMI quirk tables. MSI/MSI-X fallback, DISABLE_ACCEL. |
|
||||
| IOMMU daemon | `local/recipes/system/iommu/` | **Partial** | Builds, QEMU first-use proof passes. Real hardware validation pending. |
|
||||
|
||||
### Gaps vs Linux
|
||||
|
||||
| Gap | Severity | Linux Reference | Red Bear Status |
|
||||
|---|---|---|---|
|
||||
| AML interpreter incomplete | **Critical** | Full AML bytecode VM (While/If/Else, OperationRegion, Method locals, Notify) | Basic method calls only (_STA, _PS0, _PS3, _INI). No control flow. |
|
||||
| No _PRW wake resources | **High** | `drivers/acpi/wakeup.c` | Not present |
|
||||
| No thermal zones | **High** | `drivers/acpi/thermal.c` _TMP/_ACx/_PSV/_CRT | Not present |
|
||||
| No ACPI battery | **Medium** | `drivers/acpi/battery.c` _BIF/_BST | Not present |
|
||||
| No ACPI buttons | **High** | `drivers/acpi/button.c` LID/Power/Sleep | Not present |
|
||||
| SRAT/SLIT not wired to NUMA | **Medium** | `mm/numa.c` | Parsed but not connected to page allocator |
|
||||
| No _OSC OS capabilities | **Medium** | `drivers/acpi/osc.c` | Not present |
|
||||
| No PCI ASPM | **Medium** | `drivers/pci/pcie/aspm.c` | Not present |
|
||||
| No PCI hotplug | **Low** | `drivers/pci/hotplug/` | Not present |
|
||||
| No suspend/resume | **Critical** | `drivers/acpi/sleep.c` S1-S5 | sleep.rs + wakeup.asm in uncommitted changes, not wired |
|
||||
| DMAR/IOMMU path commented out | **High** | `drivers/iommu/intel-iommu.c` | `acpid/src/acpi/dmar/mod.rs` has iterator bug (`len_bytes` from wrong slice), hangs on real hardware — entire DMAR path commented out |
|
||||
| DMI quirk matching dead | **High** | `/sys/firmware/dmi` | `redox-driver-sys/quirks/dmi.rs` depends on `/scheme/acpi/dmi` but that source doesn't exist in the ACPI stack |
|
||||
| ACPI resource parsing panics | **Medium** | N/A | `redox-driver-acpi/resource.rs` and `prt.rs` panic on unexpected ACPI resource shapes instead of returning errors |
|
||||
| `madt/arch/other.rs` stub | **Low** | `drivers/acpi/madt.c` | Non-x86 MADT handling is effectively unimplemented |
|
||||
| PCI config: non-x86 `todo!()` | **Low** | `drivers/pci/` | `pcid/src/cfg_access/fallback.rs` has `todo!()` for non-x86 PCI config access |
|
||||
| **Missing acpid service in configs** | **Critical** | N/A (config bug) | No `acpid = {}` in redbear-full.toml or redbear-device-services.toml |
|
||||
|
||||
### acpid Missing From Configs — Critical Bug
|
||||
|
||||
The boot process agent found that **no active `acpid = {}` service entry exists** in the redbear TOML configs. This means acpid may never start, which prevents ACPI symbol/resource discovery for all ACPI-dependent drivers (i2c-hidd, intel-thc-hidd, thermald, driver-manager ACPI path). This is the single highest-priority fix.
|
||||
|
||||
---
|
||||
|
||||
## 3. Intel DRM/KMS
|
||||
|
||||
### Current Implementation Inventory
|
||||
|
||||
| Component | Path | Quality | Key Notes |
|
||||
|---|---|---|---|
|
||||
| IntelDriver | `redox-drm/source/src/drivers/intel/mod.rs` (682 lines) | **Partial** | PCIe init, MMIO mapping, FORCEWAKE, connector detection, CRTC set_mode, page_flip, GEM create/mmap/close, IRQ handling. |
|
||||
| IntelDisplay | `.../intel/display.rs` (404 lines) | **Partial** | Pipe detection, DDI port detection, mode setting (real HTOTAL/HBLANK/HSYNC/VTOTAL/VSYNC/PIPE_SRC register writes). **EDID: read_edid_block returns error → synthetic_edid(). DPCD: returns fake 4 bytes.** |
|
||||
| IntelGtt | `.../intel/gtt.rs` | **Real** | GGTT allocation, mapping, unmapping. |
|
||||
| IntelRing | `.../intel/ring.rs` (267 lines) | **Partial** | DMA ring buffer, GPU address binding. Only MI_FLUSH_DW + MI_NOOP submitted — no rendering commands. |
|
||||
| DRM scheme | `redox-drm/source/src/scheme.rs` | **Real** | Full DRM/KMS ioctl surface. SETPLANE is empty, GETENCODER hardcoded. |
|
||||
| KMS infrastructure | `redox-drm/source/src/kms/` (5 files) | **Real** | ConnectorInfo, ModeInfo with EDID parsing, synthetic_edid fallback. |
|
||||
| Interrupt handling | `redox-drm/source/src/drivers/interrupt.rs` | **Real** | MSI/MSI-X/INTx setup, try_wait polling. |
|
||||
| Linux-kpi DRM headers | `linux-kpi/source/src/c_headers/drm/` | **Minimal** | drm.h, drm_crtc.h, drm_gem.h, drm_ioctl.h — type definitions only. |
|
||||
| ihdgd (legacy) | `base/source/drivers/graphics/ihdgd/` | **Old/Partial** | Separate Intel framebuffer driver. Many TODOs. Being superseded by redox-drm. |
|
||||
| vesad | `base/source/drivers/graphics/vesad/` | **Legacy** | VESA framebuffer driver. No cursor support. |
|
||||
| Mesa | `recipes/libs/mesa/` | **Software only** | Only swrast+virgl Gallium. No Intel iris/crocus/anv driver build. |
|
||||
|
||||
### Critical Bugs Found
|
||||
|
||||
1. **synthetic_edid() is 112 bytes, not 128** — `ModeInfo::from_edid()` requires `edid.len() >= 128` and checks for the 8-byte EDID header. The synthetic EDID is only 112 bytes so it always fails validation, forcing `default_1080p()` fallback on every Intel connector.
|
||||
|
||||
2. **Intel `get_vblank()` returns `AtomicU64::fetch_add(1, SeqCst)`** — This is NOT a real vblank counter. It increments on every IRQ regardless of display state. Real i915 reads the `PIPEFRAME` register (offset `0x70040 + pipe * 0x1000`) for per-pipe frame count.
|
||||
|
||||
### Gaps vs Linux i915
|
||||
|
||||
| Gap | Severity | Impact |
|
||||
|---|---|---|
|
||||
| EDID I2C/DDC stubbed | **Critical** | No real monitor modes — always falls back to synthetic/default |
|
||||
| Vblank counter is fake | **Critical** | Page flip has no synchronization — tearing |
|
||||
| Display watermarks absent | **Critical** | FIFO underruns → visible glitching on real hardware |
|
||||
| No panel power sequencing | **High** | eDP panels won't turn on/off properly on laptops |
|
||||
| No hardware cursor | **High** | No visible cursor in DRM mode |
|
||||
| No DP AUX channel | **High** | No DisplayPort monitor support |
|
||||
synthetic_edid too short (bug) | **Critical** | EDID validation always fails |
|
||||
| No DMC firmware loading | **Medium** | No DC5/DC6 power state for Gen9+ |
|
||||
| No HPD pulse detection | **Medium** | Monitor hotplug is crude |
|
||||
| No render commands | **Medium** | Ring only does flush — no 2D/3D acceleration |
|
||||
| No GGTT PTE invalidation | **Medium** | Stale TLB entries after GGTT updates |
|
||||
| Mesa has no Intel driver | **High** | No hardware-accelerated OpenGL/Vulkan |
|
||||
|
||||
---
|
||||
|
||||
## 4. Boot Process
|
||||
|
||||
### Boot Sequence (as configured)
|
||||
|
||||
```
|
||||
UEFI bootloader
|
||||
→ kernel (startup, ACPI, scheme registration)
|
||||
→ init (PID 1)
|
||||
→ logd
|
||||
→ scheme registration (memory, irq, event, pipe, debug, etc.)
|
||||
→ numbered services from init.d/:
|
||||
00_* : base daemons (ipcd, ptyd, randd)
|
||||
02_* : driver-manager (or legacy pcid-spawner)
|
||||
04_* : device drivers
|
||||
06_* : D-Bus, sessiond, seatd
|
||||
08_* : console/greeter
|
||||
```
|
||||
|
||||
### Dependency Analysis
|
||||
|
||||
The `init` system supports `requires_weak` for service dependencies, but **most services don't declare dependencies**. The boot agent found:
|
||||
|
||||
- **`requires_weak`** means "if the dependency exists, wait for it; if not, proceed anyway." This is good for optional services but inadequate for strict ordering.
|
||||
- **No explicit `acpid = {}` service** in redbear-full.toml or redbear-device-services.toml — ACPI-dependent drivers may never discover their devices.
|
||||
- **`driver-manager`** retries deferred probes, but missing schemes (especially `acpi`) can leave drivers permanently skipped.
|
||||
- **Greeter/session path works only** if dbus, seatd, redox-drm, and authd are all present. `redbear-greeterd` waits for Wayland socket, not a stronger compositor readiness signal.
|
||||
|
||||
### Gaps
|
||||
|
||||
| Gap | Severity | Notes |
|
||||
|---|---|---|
|
||||
| **Missing acpid service in configs** | **Critical** | No ACPI symbol discovery for i2c-hidd, thermald, driver-manager ACPI path |
|
||||
| No dependency declarations | **High** | Services use number-based ordering only |
|
||||
| No service readiness signaling | **High** | No sd_notify equivalent; init doesn't gate on daemon.ready() |
|
||||
| No filesystem check | **Medium** | No fsck on boot; dirty filesystem mounts anyway |
|
||||
| initfs→rootfs transition | **Medium** | No re-evaluation of service readiness after root switch |
|
||||
|
||||
---
|
||||
|
||||
## 5. Phased Implementation Plan
|
||||
|
||||
### Phase 1: Boot Dependency Fix (1–2 weeks)
|
||||
|
||||
**Priority: Unblocks everything downstream.**
|
||||
|
||||
| # | Task | Files | Complexity |
|
||||
|---|------|-------|------------|
|
||||
| 1.1 | Add `acpid = {}` to redbear-device-services.toml and redbear-full.toml | `config/redbear-device-services.toml`, `config/redbear-full.toml` | Low |
|
||||
| 1.2 | Add `requires=` / `wants=` declarations to init service format | `recipes/core/base/source/init/src/` | Medium |
|
||||
| 1.3 | Implement dependency-aware startup: wait for `scheme:<dep>` before starting dependents | `recipes/core/base/source/init/src/` | Medium |
|
||||
| 1.4 | Add `provides= scheme:acpi` / `requires= scheme:acpi` to ACPI-dependent services | Service TOML files | Low |
|
||||
| 1.5 | Wire ESTALE-retry into i2c-hidd/intel-thc-hidd as fallback (already partial) | `drivers/input/i2c-hidd/`, `intel-thc-hidd/` | Low |
|
||||
|
||||
### Phase 2: Intel Display Critical Fixes (3–5 weeks)
|
||||
|
||||
**Priority: Highest impact for bare-metal desktop.**
|
||||
|
||||
| # | Task | Complexity | Risk | Blocks |
|
||||
|---|------|------------|------|--------|
|
||||
| 2.1 | Implement I2C master-mode in i2cd | High | Medium | 2.2, 2.7, 3.1 |
|
||||
| 2.2 | Implement real EDID via DDC (I2C at 0xA0). Fix synthetic_edid to 128 bytes as fallback | High | Medium | — |
|
||||
| 2.3 | Implement hardware vblank (read PIPEFRAME register) | Medium | Low | — |
|
||||
| 2.4 | Implement display watermarks (WM_LINETIME, WM levels per pipe) | High | Medium | — |
|
||||
| 2.5 | Implement eDP panel power sequencing (PP_ON/OFF/CYCLE) | Medium | Medium | — |
|
||||
| 2.6 | Implement hardware cursor (CUR_CTL/CUR_BASE/CUR_POS) | Medium | Low | — |
|
||||
| 2.7 | Implement DP AUX channel (I2C-over-AUX for DisplayPort) | High | Medium | Depends on 2.1 |
|
||||
|
||||
**Ordering:** EDID (2.2) → vblank (2.3) → watermarks (2.4) → panel (2.5) → cursor (2.6) → DP AUX (2.7)
|
||||
|
||||
### Phase 3: Input Stack Completion (2–4 weeks)
|
||||
|
||||
**Can parallel with Phase 2 once I2C master-mode (2.1) is done.**
|
||||
|
||||
| # | Task | Complexity |
|
||||
|---|------|------------|
|
||||
| 3.1 | Complete intel-thc-hidd input streaming (replace sleep loop with HID report read) | Medium |
|
||||
| 3.2 | Add PS/2 extended protocols (ImPS/2 scroll, Explorer 5-btn, trackpoint) | Medium |
|
||||
| 3.3 | Add input device hotplug (dynamic producer registration in inputd) | Medium |
|
||||
| 3.4 | Add multitouch protocol (ABS_MT slots, touch report parsing) | Medium |
|
||||
| 3.5 | Add pointer acceleration to inputd | Low |
|
||||
| 3.6 | Port bounded subset of Linux hid-quirks for device workarounds | Medium |
|
||||
|
||||
### Phase 4: AML Interpreter Depth (4–8 weeks)
|
||||
|
||||
**Risk gate: scope strictly to _PS0/_PS3/_PRW/_BIF/_BST opcodes first.**
|
||||
|
||||
| # | Task | Complexity |
|
||||
|---|------|------------|
|
||||
| 4.1 | AML While/If-Else/Method-with-locals (bounded, not full spec) | Very High |
|
||||
| 4.2 | OperationRegion handlers for PCI config and I2C | High |
|
||||
| 4.3 | _PRW (power resources for wake) | Medium |
|
||||
| 4.4 | ACPI battery (_BIF/_BST) | Medium |
|
||||
| 4.5 | ACPI buttons (LID, power, sleep) | Low |
|
||||
| 4.6 | Thermal zone evaluation (_TMP, _ACx, _PSV, _CRT) | Medium |
|
||||
|
||||
### Phase 5: Advanced Features (4–8 weeks)
|
||||
|
||||
After Phases 2–4 are stable.
|
||||
|
||||
| # | Task |
|
||||
|---|------|
|
||||
| 5.1 | PCI ASPM power management (_OSC, L0s/L1) |
|
||||
| 5.2 | PCI hotplug (acpiphp/pciehp) |
|
||||
| 5.3 | SRAT/SLIT → NUMA allocator wiring |
|
||||
| 5.4 | Display FIFO underrun recovery |
|
||||
| 5.5 | HPD pulse detection |
|
||||
| 5.6 | I2C bus error recovery (SMBus timeout, multi-controller) |
|
||||
|
||||
### Dependency Graph
|
||||
|
||||
```
|
||||
Phase 1 (boot deps)
|
||||
│
|
||||
├──→ Phase 2 (Intel display) ──→ Phase 5.4, 5.5
|
||||
│ │
|
||||
│ └──→ 2.1 (I2C master) blocks 2.2, 2.7, 3.1
|
||||
│
|
||||
├──→ Phase 3 (input) ──→ 3.1 needs I2C (shared with 2.1)
|
||||
│
|
||||
├──→ Phase 4 (AML) ──→ Phase 5.1, 5.2
|
||||
│ │
|
||||
│ └──→ 4.1 gates 4.3–4.6
|
||||
│
|
||||
└──→ Phase 5 (advanced) ──→ depends on Phases 2, 3, 4
|
||||
```
|
||||
|
||||
### Effort Estimate (2 developers)
|
||||
|
||||
| Phase | Duration | Parallelizable? |
|
||||
|-------|----------|-----------------|
|
||||
| Phase 1 | 1–2 weeks | Yes (with Phase 2 start) |
|
||||
| Phase 2 | 3–5 weeks | Partially (2.1 blocks 2.2–2.7) |
|
||||
| Phase 3 | 2–4 weeks | Yes (parallel with Phase 2) |
|
||||
| Phase 4 | 4–8 weeks | Partially (4.1 gates rest) |
|
||||
| Phase 5 | 4–8 weeks | After Phases 2–4 |
|
||||
| **Total** | **14–27 weeks** | ~8–14 months |
|
||||
|
||||
### Key Risks
|
||||
|
||||
1. **I2C master-mode** is a shared dependency between EDID (2.2), THC input (3.1), and DDC (2.2). Implement it first in i2cd.
|
||||
2. **AML interpreter scope creep** — the full AML spec is enormous. Strictly bound the first pass to opcodes needed for _PS0/_PS3/_PRW/_BIF/_BST. Fallback: bounded userspace AML evaluator in acpid.
|
||||
3. **Intel watermark programming varies by generation** — start with Gen9 Skylake, then generalize.
|
||||
4. **synthetic_edid 112-byte bug** must be fixed IMMEDIATELY — it affects every Intel display attempt.
|
||||
5. **Missing acpid service** in configs must be fixed IMMEDIATELY — it blocks all ACPI-dependent device discovery.
|
||||
6. **DMAR/IOMMU iterator bug** in `acpid/src/acpi/dmar/mod.rs` causes hangs on real hardware; entire DMAR path is commented out.
|
||||
7. **DMI quirk matching is dead** — `redox-driver-sys/quirks/dmi.rs` reads `/scheme/acpi/dmi` but no code provides that scheme.
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Linux Reference File Map
|
||||
|
||||
From `local/reference/linux-7.1/`:
|
||||
|
||||
| Subsystem | Key Files |
|
||||
|---|---|
|
||||
| HID core | `drivers/hid/hid-core.c`, `hid-input.c`, `hid-quirks.c` |
|
||||
| I2C-HID | `drivers/hid/i2c-hid/i2c-hid-core.c`, `i2c-hid-acpi.c` |
|
||||
| USB HID | `drivers/hid/usbhid/hid-core.c`, `usbkbd.c`, `usbmouse.c` |
|
||||
| Input core | `drivers/input/input.c`, `input-mt.c`, `evdev.c` |
|
||||
| PS/2 | `drivers/input/serio/i8042.c`, `libps2.c`, `atkbd.c`, `psmouse-base.c` |
|
||||
| I2C core | `drivers/i2c/i2c-core-acpi.c`, `i2c-core-base.c` |
|
||||
| i915 | `drivers/gpu/drm/i915/` (6M+ lines) |
|
||||
| ACPI | `drivers/acpi/` (full AML interpreter, 15k+ lines) |
|
||||
|
||||
## Appendix B: Uncommitted Changes (as of 2026-05-17)
|
||||
|
||||
The `bootprocess` branch has 63 changed files including:
|
||||
- Kernel ACPI: `slit.rs`, `srat.rs` (NUMA), `msi.rs`, `vector.rs` (MSI/MSI-X), `sleep.rs` + `s3_wakeup.asm` (S3)
|
||||
- Kernel: `numa.rs`, `mcs.rs` (MCS lock), context/percpu/event/sync improvements
|
||||
- Base patches: `P4-acpi-estale-graceful.patch`, `P4-hwd-estale-graceful.patch`, `P4-ucsid-estale-graceful.patch`
|
||||
- Kernel patch: `P21-x2apic-smp-fix.patch`
|
||||
- Modified: pcid, driver-manager, thermald, redox-drm, redox-driver-acpi source files
|
||||
@@ -1,3 +1,4 @@
|
||||
diff --git a/init/src/service.rs b/init/src/service.rs
|
||||
--- a/init/src/service.rs
|
||||
+++ b/init/src/service.rs
|
||||
@@ -40,6 +40,28 @@
|
||||
@@ -119,79 +120,36 @@
|
||||
}
|
||||
ServiceType::OneshotAsync => unreachable!(),
|
||||
}
|
||||
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
||||
--- a/init/src/scheduler.rs
|
||||
+++ b/init/src/scheduler.rs
|
||||
@@ -1,14 +1,28 @@
|
||||
-use std::collections::{BTreeSet, VecDeque};
|
||||
+use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
use crate::InitConfig;
|
||||
-use crate::color::{init_error, status_ok, status_skip};
|
||||
+use crate::color::{init_error, init_warn, status_ok, status_skip};
|
||||
use crate::color::{init_error, init_warn, status_ok, status_skip};
|
||||
+use crate::service::RestartPolicy;
|
||||
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
||||
|
||||
const SPAWN_BATCH_SIZE: usize = 50;
|
||||
|
||||
+/// Tracks the restart state for a supervised service.
|
||||
+pub struct ServiceState {
|
||||
+ pub unit_id: UnitId,
|
||||
+ pub cmd: String,
|
||||
+ pub restart_policy: RestartPolicy,
|
||||
+ pub max_restarts: u32,
|
||||
+ pub restart_count: u32,
|
||||
+ /// Monotonic time of last restart (for backoff calculation).
|
||||
+ pub last_restart_ms: u64,
|
||||
+}
|
||||
+
|
||||
pub struct Scheduler {
|
||||
pending: VecDeque<Job>,
|
||||
completed: BTreeSet<UnitId>,
|
||||
+ /// Maps child PID → service state for supervised services.
|
||||
+ pub supervised: BTreeMap<u32, ServiceState>,
|
||||
}
|
||||
|
||||
struct Job {
|
||||
@@ -25,6 +39,7 @@
|
||||
Scheduler {
|
||||
pending: VecDeque::new(),
|
||||
completed: BTreeSet::new(),
|
||||
+ supervised: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +121,7 @@
|
||||
defer_count = 0;
|
||||
|
||||
let unit = unit_store.unit_mut(&job.unit);
|
||||
- run(unit, init_config);
|
||||
+ run(unit, init_config, &mut self.supervised);
|
||||
self.completed.insert(job.unit);
|
||||
spawned_this_step += 1;
|
||||
|
||||
@@ -119,7 +134,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
-fn run(unit: &mut Unit, config: &mut InitConfig) {
|
||||
+fn run(unit: &mut Unit, config: &mut InitConfig, supervised: &mut BTreeMap<u32, ServiceState>) {
|
||||
match &unit.kind {
|
||||
UnitKind::LegacyScript { script } => {
|
||||
for cmd in script.clone() {
|
||||
@@ -127,13 +142,28 @@
|
||||
}
|
||||
}
|
||||
UnitKind::Service { service } => {
|
||||
- let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0);
|
||||
+ let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0).clone();
|
||||
if config.skip_cmd.contains(&service.cmd) {
|
||||
status_skip(&format!("Skipping {} ({})", desc, service.cmd));
|
||||
-#[derive(Clone, Debug)]
|
||||
-pub enum RestartPolicy {
|
||||
- Never,
|
||||
- OnFailure,
|
||||
- Always,
|
||||
-}
|
||||
-
|
||||
/// Tracks the restart state for a supervised service.
|
||||
pub struct ServiceState {
|
||||
pub unit_id: UnitId,
|
||||
@@ -167,9 +161,21 @@
|
||||
return;
|
||||
}
|
||||
status_ok(&format!("Started {}", desc));
|
||||
- service.spawn(&config.envs);
|
||||
- // Supervision infrastructure is in place; full PID tracking requires
|
||||
- // service.spawn() to return Option<u32> (added by a later patch).
|
||||
+ if let Some(pid) = service.spawn(&config.envs) {
|
||||
+ // Only supervise services with a restart policy other than Never
|
||||
+ if service.restart != RestartPolicy::Never {
|
||||
+ supervised.insert(
|
||||
+ pid,
|
||||
@@ -209,6 +167,7 @@
|
||||
}
|
||||
UnitKind::Target {} => {}
|
||||
}
|
||||
diff --git a/init/src/main.rs b/init/src/main.rs
|
||||
--- a/init/src/main.rs
|
||||
+++ b/init/src/main.rs
|
||||
@@ -5,7 +5,8 @@
|
||||
@@ -221,7 +180,7 @@
|
||||
use crate::unit::{UnitId, UnitStore};
|
||||
|
||||
mod color;
|
||||
@@ -176,15 +177,100 @@
|
||||
@@ -176,15 +177,95 @@
|
||||
if scheduler.has_pending() {
|
||||
// Reap exited children before processing more services.
|
||||
let mut status = 0;
|
||||
@@ -244,7 +203,6 @@
|
||||
}
|
||||
}
|
||||
+
|
||||
+/// Handle a child process exit. If it's a supervised service, apply restart policy.
|
||||
+fn handle_child_exit(
|
||||
+ pid: u32,
|
||||
+ exit_status: i32,
|
||||
@@ -253,7 +211,7 @@
|
||||
+ init_config: &mut InitConfig,
|
||||
+) {
|
||||
+ let Some(state) = scheduler.supervised.remove(&pid) else {
|
||||
+ return; // Not a supervised service — just reap
|
||||
+ return;
|
||||
+ };
|
||||
+
|
||||
+ let exited_cleanly = exit_status == 0;
|
||||
@@ -279,7 +237,6 @@
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ // Exponential backoff: 1s, 2s, 4s, 8s, ... up to 30s max
|
||||
+ let backoff_secs = 1u64 << state.restart_count.min(4);
|
||||
+ let backoff_secs = backoff_secs.min(30);
|
||||
+ init_warn(&format!(
|
||||
@@ -292,14 +249,11 @@
|
||||
+ state.max_restarts,
|
||||
+ ));
|
||||
+
|
||||
+ // Sleep for backoff period (blocking, but init is the supervisor)
|
||||
+ std::thread::sleep(std::time::Duration::from_secs(backoff_secs));
|
||||
+
|
||||
+ // Re-spawn the service by scheduling a restart
|
||||
+ let unit_id = state.unit_id.clone();
|
||||
+ let new_restart_count = state.restart_count + 1;
|
||||
+
|
||||
+ // Load the unit and re-run it
|
||||
+ let unit = unit_store.unit_mut(&unit_id);
|
||||
+ if let crate::unit::UnitKind::Service { service } = &unit.kind {
|
||||
+ if let Some(new_pid) = service.spawn(&init_config.envs) {
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
diff --git a/drivers/net/virtio-netd/src/main.rs b/drivers/net/virtio-netd/src/main.rs
|
||||
index 1200cec..0c6663e 100644
|
||||
--- a/drivers/net/virtio-netd/src/main.rs
|
||||
+++ b/drivers/net/virtio-netd/src/main.rs
|
||||
@@ -34,2 +34,7 @@ fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -
|
||||
- daemon(redox_daemon, pcid_handle).unwrap();
|
||||
- unreachable!();
|
||||
+ match daemon(redox_daemon, pcid_handle) {
|
||||
+ Ok(()) => unreachable!(),
|
||||
+ Err(err) => {
|
||||
+ log::error!("virtio-netd: fatal error: {err}");
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ }
|
||||
diff --git a/drivers/pcid/src/driver_interface/irq_helpers.rs b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||
index 28ca077..7ecc9a3 100644
|
||||
--- a/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||
+++ b/drivers/pcid/src/driver_interface/irq_helpers.rs
|
||||
@@ -118,7 +118,7 @@
|
||||
let mut handles = Vec::with_capacity(usize::from(count));
|
||||
|
||||
let mut index = 0;
|
||||
@@ -121 +121 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||
- let mut first = None;
|
||||
+ let mut first_aligned: Option<u8> = None;
|
||||
|
||||
while let Some(number) = available_irq_numbers.next() {
|
||||
let number = number?;
|
||||
@@ -127,8 +127,8 @@
|
||||
if number % u8::from(alignment) != 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -130,2 +130,2 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||
- let first = *first.get_or_insert(number);
|
||||
- let irq_number = first + index;
|
||||
+ let base = *first_aligned.get_or_insert(number);
|
||||
+ let irq_number = base + index;
|
||||
|
||||
// From the point where the range is aligned, we can start to advance until `count` IRQs
|
||||
// have been allocated.
|
||||
@@ -141,6 +141,15 @@
|
||||
match File::create(format!("/scheme/irq/cpu-{:02x}/{}", cpu_id, irq_number)) {
|
||||
Ok(handle) => handle,
|
||||
|
||||
@@ -143,0 +144,9 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||
+ // Vector already allocated by another process; release any partial range and
|
||||
+ // restart the search from the next aligned position.
|
||||
+ Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {
|
||||
@@ -33,27 +34,13 @@
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
// return early if the entire range couldn't be allocated
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => break,
|
||||
|
||||
@@ -152,7 +161,7 @@
|
||||
if handles.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -155 +164 @@ pub fn allocate_aligned_interrupt_vectors(
|
||||
- let first = match first {
|
||||
+ let first = match first_aligned {
|
||||
Some(f) => f,
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -180,40 +189,60 @@
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@@ -183 +192 @@ pub fn allocate_single_interrupt_vector(cpu_id: usize) -> io::Result<Option<(u8,
|
||||
-pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndData, File) {
|
||||
+pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> Option<(MsiAddrAndData, File)> {
|
||||
use crate::driver_interface::msi::x86 as x86_msix;
|
||||
|
||||
// FIXME for cpu_id >255 we need to use the IOMMU to use IRQ remapping
|
||||
@@ -187 +196,7 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||
- let lapic_id = u8::try_from(cpu_id).expect("CPU id couldn't fit inside u8");
|
||||
+ let lapic_id = match u8::try_from(cpu_id) {
|
||||
+ Ok(id) => id,
|
||||
@@ -62,10 +49,7 @@
|
||||
+ return None;
|
||||
+ }
|
||||
+ };
|
||||
let rh = false;
|
||||
let dm = false;
|
||||
let addr = x86_msix::message_address(lapic_id, rh, dm);
|
||||
|
||||
@@ -192,3 +207,11 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||
- let (vector, interrupt_handle) = allocate_single_interrupt_vector(cpu_id)
|
||||
- .expect("failed to allocate interrupt vector")
|
||||
- .expect("no interrupt vectors left");
|
||||
@@ -80,171 +64,88 @@
|
||||
+ return None;
|
||||
+ }
|
||||
+ };
|
||||
let msg_data = x86_msix::message_data_edge_triggered(x86_msix::DeliveryMode::Fixed, vector);
|
||||
|
||||
@@ -197 +220 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||
- (
|
||||
+ Some((
|
||||
MsiAddrAndData {
|
||||
addr,
|
||||
data: msg_data,
|
||||
},
|
||||
interrupt_handle,
|
||||
@@ -203 +226 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
|
||||
- )
|
||||
+ ))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub fn allocate_first_msi_interrupt_on_bsp(
|
||||
pcid_handle: &mut crate::driver_interface::PciFunctionHandle,
|
||||
@@ -209 +232 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||
-) -> File {
|
||||
+) -> Option<File> {
|
||||
use crate::driver_interface::{MsiSetFeatureInfo, PciFeature, SetFeatureInfo};
|
||||
|
||||
// TODO: Allow allocation of up to 32 vectors.
|
||||
|
||||
@@ -214 +237,7 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||
- let destination_id = read_bsp_apic_id().expect("failed to read BSP apic id");
|
||||
+ let destination_id = match read_bsp_apic_id() {
|
||||
+ Ok(id) => id,
|
||||
+ Err(err) => {
|
||||
+ log::warn!("failed to read BSP APIC ID: {}", err);
|
||||
+ log::warn!("failed to read BSP apic id: {}", err);
|
||||
+ return None;
|
||||
+ }
|
||||
+ };
|
||||
let (msg_addr_and_data, interrupt_handle) =
|
||||
@@ -216 +245 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
+ allocate_single_interrupt_vector_for_msi(destination_id)?;
|
||||
|
||||
let set_feature_info = MsiSetFeatureInfo {
|
||||
multi_message_enable: Some(0),
|
||||
@@ -225,7 +254,7 @@
|
||||
pcid_handle.enable_feature(PciFeature::Msi);
|
||||
log::debug!("Enabled MSI");
|
||||
|
||||
@@ -228 +257 @@ pub fn allocate_first_msi_interrupt_on_bsp(
|
||||
- interrupt_handle
|
||||
+ Some(interrupt_handle)
|
||||
}
|
||||
|
||||
pub struct InterruptVector {
|
||||
@@ -275,6 +304,7 @@
|
||||
let has_msi = features.iter().any(|feature| feature.is_msi());
|
||||
let has_msix = features.iter().any(|feature| feature.is_msix());
|
||||
|
||||
+ // Try MSI-X first, then MSI, then fall back to legacy INTx#.
|
||||
if has_msix {
|
||||
let msix_info = match pcid_handle.feature_info(super::PciFeature::MsiX) {
|
||||
super::PciFeatureInfo::MsiX(msix) => msix,
|
||||
@@ -282,28 +312,45 @@
|
||||
};
|
||||
let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
|
||||
|
||||
@@ -285,2 +313,0 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
|
||||
-
|
||||
let entry = info.table_entry_pointer(0);
|
||||
|
||||
let bsp_cpu_id = read_bsp_apic_id()
|
||||
.unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}"));
|
||||
@@ -289,3 +316,9 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- let bsp_cpu_id = read_bsp_apic_id()
|
||||
- .unwrap_or_else(|err| panic!("{driver}: failed to read BSP APIC ID: {err}"));
|
||||
- let (msg_addr_and_data, irq_handle) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id);
|
||||
- entry.write_addr_and_data(msg_addr_and_data);
|
||||
- entry.unmask();
|
||||
|
||||
+ let bsp_cpu_id = match read_bsp_apic_id() {
|
||||
+ Ok(id) => id,
|
||||
+ Err(err) => {
|
||||
+ log::warn!("{driver}: failed to read BSP APIC ID: {err}");
|
||||
+ // fall through to MSI
|
||||
+ 0
|
||||
+ }
|
||||
+ };
|
||||
+ if let Some((msg_addr_and_data, irq_handle)) = allocate_single_interrupt_vector_for_msi(bsp_cpu_id) {
|
||||
@@ -295 +328,3 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- InterruptVector {
|
||||
- irq_handle,
|
||||
- vector: 0,
|
||||
- kind: InterruptVectorKind::MsiX { table_entry: entry },
|
||||
+ if let Some((msg_addr_and_data, irq_handle)) =
|
||||
+ allocate_single_interrupt_vector_for_msi(bsp_cpu_id)
|
||||
+ {
|
||||
+ // Vector allocated: enable MSI-X and configure the table entry.
|
||||
+ pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
|
||||
+ entry.write_addr_and_data(msg_addr_and_data);
|
||||
+ entry.unmask();
|
||||
+
|
||||
+ return InterruptVector {
|
||||
+ irq_handle,
|
||||
+ vector: 0,
|
||||
+ kind: InterruptVectorKind::MsiX { table_entry: entry },
|
||||
@@ -298,0 +334 @@ pub fn pci_allocate_interrupt_vector(
|
||||
+ };
|
||||
}
|
||||
@@ -300,3 +336,8 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- } else if has_msi {
|
||||
- InterruptVector {
|
||||
- irq_handle: allocate_first_msi_interrupt_on_bsp(pcid_handle),
|
||||
- vector: 0,
|
||||
- kind: InterruptVectorKind::Msi,
|
||||
+
|
||||
+ // MSI-X vector allocation failed; MSI-X was never enabled in config space so the
|
||||
+ // device will fall back to INTx# or MSI. Mapped BARs are released when `info` drops.
|
||||
+ log::warn!("{driver}: MSI-X vector allocation failed, falling back");
|
||||
+ // fall through to MSI
|
||||
+ }
|
||||
+
|
||||
+ if has_msi {
|
||||
+ if let Some(irq_handle) = allocate_first_msi_interrupt_on_bsp(pcid_handle) {
|
||||
+ return InterruptVector {
|
||||
+ irq_handle,
|
||||
+ vector: 0,
|
||||
+ kind: InterruptVectorKind::Msi,
|
||||
@@ -304,0 +346,3 @@ pub fn pci_allocate_interrupt_vector(
|
||||
+ };
|
||||
}
|
||||
+ }
|
||||
+ log::warn!("{driver}: MSI allocation failed, falling back to legacy");
|
||||
@@ -306 +350,2 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- } else if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||
+
|
||||
+ // MSI allocation failed; fall back to legacy.
|
||||
+ log::warn!("{driver}: MSI allocation failed, falling back to legacy");
|
||||
+ }
|
||||
+
|
||||
+ if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
|
||||
// INTx# pin based interrupts.
|
||||
InterruptVector {
|
||||
irq_handle: irq.irq_handle(driver),
|
||||
|
||||
--- a/drivers/virtio-core/src/transport.rs
|
||||
+++ b/drivers/virtio-core/src/transport.rs
|
||||
@@ -19,6 +19,8 @@
|
||||
SyscallError(#[from] libredox::error::Error),
|
||||
#[error("the device is incapable of {0:?}")]
|
||||
InCapable(CfgType),
|
||||
+ #[error("MSI/MSI-X vector allocation failed")]
|
||||
+ MsiAllocationFailed,
|
||||
}
|
||||
|
||||
/// Returns the queue part sizes in bytes.
|
||||
|
||||
--- a/drivers/virtio-core/src/arch/x86.rs
|
||||
+++ b/drivers/virtio-core/src/arch/x86.rs
|
||||
@@ -23,7 +23,8 @@
|
||||
|
||||
let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed");
|
||||
let (msg_addr_and_data, interrupt_handle) =
|
||||
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
+ allocate_single_interrupt_vector_for_msi(destination_id)
|
||||
+ .ok_or(Error::MsiAllocationFailed)?;
|
||||
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
|
||||
table_entry_pointer.unmask();
|
||||
|
||||
|
||||
--- a/drivers/net/virtio-netd/src/main.rs
|
||||
+++ b/drivers/net/virtio-netd/src/main.rs
|
||||
@@ -31,8 +31,13 @@
|
||||
}
|
||||
|
||||
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
- deamon(daemon, pcid_handle).unwrap();
|
||||
- unreachable!();
|
||||
+ match deamon(daemon, pcid_handle) {
|
||||
+ Ok(()) => unreachable!(),
|
||||
+ Err(err) => {
|
||||
+ log::error!("virtio-netd: fatal error: {err}");
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
fn deamon(
|
||||
|
||||
@@ -308 +353 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- InterruptVector {
|
||||
+ return InterruptVector {
|
||||
@@ -311,0 +357 @@ pub fn pci_allocate_interrupt_vector(
|
||||
+ };
|
||||
@@ -313 +359 @@ pub fn pci_allocate_interrupt_vector(
|
||||
- } else {
|
||||
+
|
||||
@@ -316 +361,0 @@ pub fn pci_allocate_interrupt_vector(
|
||||
-}
|
||||
diff --git a/drivers/storage/virtio-blkd/src/main.rs b/drivers/storage/virtio-blkd/src/main.rs
|
||||
index d21236b..95089eb 100644
|
||||
--- a/drivers/storage/virtio-blkd/src/main.rs
|
||||
+++ b/drivers/storage/virtio-blkd/src/main.rs
|
||||
@@ -103,8 +103,13 @@
|
||||
}
|
||||
|
||||
fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
@@ -106,2 +106,7 @@ fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -
|
||||
- daemon(redox_daemon, pcid_handle).unwrap();
|
||||
- unreachable!();
|
||||
+ match daemon(redox_daemon, pcid_handle) {
|
||||
@@ -254,54 +155,54 @@
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> {
|
||||
|
||||
diff --git a/drivers/usb/xhcid/src/main.rs b/drivers/usb/xhcid/src/main.rs
|
||||
index d345a52..397971d 100644
|
||||
--- a/drivers/usb/xhcid/src/main.rs
|
||||
+++ b/drivers/usb/xhcid/src/main.rs
|
||||
@@ -76,22 +76,32 @@
|
||||
let table_entry_pointer = info.table_entry_pointer(k);
|
||||
|
||||
let destination_id = read_bsp_apic_id().expect("xhcid: failed to read BSP apic id");
|
||||
@@ -79,2 +79,3 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||
- let (msg_addr_and_data, interrupt_handle) =
|
||||
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
- table_entry_pointer.write_addr_and_data(msg_addr_and_data);
|
||||
- table_entry_pointer.unmask();
|
||||
|
||||
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||
+ if let Some((msg_addr_and_data, interrupt_handle)) =
|
||||
+ allocate_single_interrupt_vector_for_msi(destination_id)
|
||||
+ {
|
||||
+ table_entry_pointer.write_addr_and_data(msg_addr_and_data);
|
||||
+ table_entry_pointer.unmask();
|
||||
+
|
||||
+ pcid_handle.enable_feature(PciFeature::MsiX);
|
||||
+ log::debug!("Enabled MSI-X");
|
||||
+
|
||||
@@ -84,3 +84,0 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||
- };
|
||||
-
|
||||
@@ -90,5 +88,16 @@ fn get_int_method(pcid_handle: &mut PciFunctionHandle) -> (Option<File>, Interru
|
||||
- method
|
||||
- } else if has_msi {
|
||||
- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
|
||||
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||
- } else if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||
+ return (Some(interrupt_handle), InterruptMethod::Msi);
|
||||
+ }
|
||||
+
|
||||
+ // MSI-X allocation failed; fall through to MSI or legacy.
|
||||
+ log::warn!("xhcid: MSI-X vector allocation failed, falling back");
|
||||
};
|
||||
+ };
|
||||
+ }
|
||||
|
||||
- pcid_handle.enable_feature(PciFeature::MsiX);
|
||||
- log::debug!("Enabled MSI-X");
|
||||
+
|
||||
+ if has_msi {
|
||||
+ if let Some(interrupt_handle) = allocate_first_msi_interrupt_on_bsp(pcid_handle) {
|
||||
+ return (Some(interrupt_handle), InterruptMethod::Msi);
|
||||
+ }
|
||||
+ log::warn!("xhcid: MSI allocation failed, falling back to legacy");
|
||||
+ }
|
||||
|
||||
- method
|
||||
- } else if has_msi {
|
||||
- let interrupt_handle = allocate_first_msi_interrupt_on_bsp(pcid_handle);
|
||||
- (Some(interrupt_handle), InterruptMethod::Msi)
|
||||
- } else if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||
+
|
||||
+ if let Some(irq) = pci_config.func.legacy_interrupt_line {
|
||||
log::debug!("Legacy IRQ {}", irq);
|
||||
|
||||
// legacy INTx# interrupt pins.
|
||||
diff --git a/drivers/virtio-core/src/arch/x86.rs b/drivers/virtio-core/src/arch/x86.rs
|
||||
index aea86c4..8fdc7ca 100644
|
||||
--- a/drivers/virtio-core/src/arch/x86.rs
|
||||
+++ b/drivers/virtio-core/src/arch/x86.rs
|
||||
@@ -26 +26,2 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
|
||||
- allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
+ allocate_single_interrupt_vector_for_msi(destination_id)
|
||||
+ .ok_or(Error::MsiAllocationFailed)?;
|
||||
diff --git a/drivers/virtio-core/src/transport.rs b/drivers/virtio-core/src/transport.rs
|
||||
index d3445d2..b961265 100644
|
||||
--- a/drivers/virtio-core/src/transport.rs
|
||||
+++ b/drivers/virtio-core/src/transport.rs
|
||||
@@ -21,0 +22,2 @@ pub enum Error {
|
||||
+ #[error("MSI/MSI-X vector allocation failed")]
|
||||
+ MsiAllocationFailed,
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
--- a/drivers/acpid/src/main.rs
|
||||
+++ b/drivers/acpid/src/main.rs
|
||||
@@ -32,3 +32,8 @@
|
||||
- let rxsdt_raw_data: Arc<[u8]> = std::fs::read("/scheme/kernel.acpi/rxsdt")
|
||||
- .expect("acpid: failed to read `/scheme/kernel.acpi/rxsdt`")
|
||||
- .into();
|
||||
+ let rxsdt_raw_data: Arc<[u8]> = match std::fs::read("/scheme/kernel.acpi/rxsdt") {
|
||||
+ Ok(data) => data.into(),
|
||||
+ Err(e) => {
|
||||
+ log::warn!("acpid: failed to read `/scheme/kernel.acpi/rxsdt`: {} — no ACPI", e);
|
||||
+ daemon.ready();
|
||||
+ std::process::exit(0);
|
||||
+ }
|
||||
+ };
|
||||
@@ -42 +47,7 @@
|
||||
- let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
|
||||
+ let sdt = match self::acpi::Sdt::new(rxsdt_raw_data) {
|
||||
+ Ok(sdt) => sdt,
|
||||
+ Err(e) => {
|
||||
+ log::error!("acpid: failed to parse [RX]SDT: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ };
|
||||
@@ -52,2 +63 @@
|
||||
- // TODO: With const generics, the compiler has some way of doing this for static sizes.
|
||||
- .map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
|
||||
+ .filter_map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).ok())
|
||||
@@ -63 +73 @@
|
||||
- .map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
|
||||
+ .filter_map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).ok())
|
||||
@@ -68 +78,4 @@
|
||||
- _ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
|
||||
+ _ => {
|
||||
+ log::error!("acpid: expected [RX]SDT from kernel to be RSDT or XSDT, got {:?}", String::from_utf8_lossy(&sdt.signature));
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
@@ -87 +100,4 @@
|
||||
- common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||
+ if let Err(e) = common::acquire_port_io_rights() {
|
||||
+ log::error!("acpid: failed to set I/O privilege level to Ring 3: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
@@ -89,2 +105,7 @@
|
||||
- let shutdown_pipe = File::open("/scheme/kernel.acpi/kstop")
|
||||
- .expect("acpid: failed to open `/scheme/kernel.acpi/kstop`");
|
||||
+ let shutdown_pipe = match File::open("/scheme/kernel.acpi/kstop") {
|
||||
+ Ok(file) => Some(file),
|
||||
+ Err(e) => {
|
||||
+ log::warn!("acpid: failed to open `/scheme/kernel.acpi/kstop`: {} — continuing without shutdown support", e);
|
||||
+ None
|
||||
+ }
|
||||
+ };
|
||||
@@ -92,2 +113,14 @@
|
||||
- let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
|
||||
- let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
|
||||
+ let mut event_queue = match RawEventQueue::new() {
|
||||
+ Ok(q) => q,
|
||||
+ Err(e) => {
|
||||
+ log::error!("acpid: failed to create event queue: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ };
|
||||
+ let socket = match Socket::nonblock() {
|
||||
+ Ok(s) => s,
|
||||
+ Err(e) => {
|
||||
+ log::error!("acpid: failed to create disk scheme: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
+ };
|
||||
@@ -98,6 +131,9 @@
|
||||
- event_queue
|
||||
- .subscribe(shutdown_pipe.as_raw_fd() as usize, 0, EventFlags::READ)
|
||||
- .expect("acpid: failed to register shutdown pipe for event queue");
|
||||
- event_queue
|
||||
- .subscribe(socket.inner().raw(), 1, EventFlags::READ)
|
||||
- .expect("acpid: failed to register scheme socket for event queue");
|
||||
+ if let Some(ref pipe) = shutdown_pipe {
|
||||
+ if let Err(e) = event_queue.subscribe(pipe.as_raw_fd() as usize, 0, EventFlags::READ) {
|
||||
+ log::warn!("acpid: failed to register shutdown pipe for event queue: {} — continuing without shutdown support", e);
|
||||
+ }
|
||||
+ }
|
||||
+ if let Err(e) = event_queue.subscribe(socket.inner().raw(), 1, EventFlags::READ) {
|
||||
+ log::error!("acpid: failed to register scheme socket for event queue: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
@@ -105,2 +141,4 @@
|
||||
- register_sync_scheme(&socket, "acpi", &mut scheme)
|
||||
- .expect("acpid: failed to register acpi scheme to namespace");
|
||||
+ if let Err(e) = register_sync_scheme(&socket, "acpi", &mut scheme) {
|
||||
+ log::error!("acpid: failed to register acpi scheme to namespace: {}", e);
|
||||
+ std::process::exit(1);
|
||||
+ }
|
||||
@@ -110 +148,3 @@
|
||||
- libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
|
||||
+ if let Err(e) = libredox::call::setrens(0, 0) {
|
||||
+ log::warn!("acpid: failed to enter null namespace: {} — continuing", e);
|
||||
+ }
|
||||
@@ -114,5 +154,7 @@
|
||||
- let Some(event) = event_queue
|
||||
- .next()
|
||||
- .transpose()
|
||||
- .expect("acpid: failed to read event file")
|
||||
- else {
|
||||
+ let Some(event) = match event_queue.next().transpose() {
|
||||
+ Ok(e) => e,
|
||||
+ Err(e) => {
|
||||
+ log::error!("acpid: failed to read event file: {} — continuing", e);
|
||||
+ continue;
|
||||
+ }
|
||||
+ } else {
|
||||
@@ -124,6 +166,7 @@
|
||||
- match handler
|
||||
- .process_requests_nonblocking(&mut scheme)
|
||||
- .expect("acpid: failed to process requests")
|
||||
- {
|
||||
- ControlFlow::Continue(()) => {}
|
||||
- ControlFlow::Break(()) => break,
|
||||
+ match handler.process_requests_nonblocking(&mut scheme) {
|
||||
+ Ok(ControlFlow::Continue(())) => {}
|
||||
+ Ok(ControlFlow::Break(())) => break,
|
||||
+ Err(e) => {
|
||||
+ log::error!("acpid: failed to process requests: {} — continuing", e);
|
||||
+ break;
|
||||
+ }
|
||||
@@ -132 +175 @@
|
||||
- } else if event.fd == shutdown_pipe.as_raw_fd() as usize {
|
||||
+ } else if shutdown_pipe.as_ref().map_or(false, |p| event.fd == p.as_raw_fd() as usize) {
|
||||
@@ -146 +189,2 @@
|
||||
- unreachable!("System should have shut down before this is entered");
|
||||
+ log::error!("System should have shut down before this was reached");
|
||||
+ std::process::exit(1);
|
||||
--- a/drivers/acpid/src/acpi.rs
|
||||
+++ b/drivers/acpid/src/acpi.rs
|
||||
@@ -55,3 +55,2 @@
|
||||
- self.length
|
||||
- .try_into()
|
||||
- .expect("expected usize to be at least 32 bits")
|
||||
+ // usize is at least 32 bits on all supported architectures.
|
||||
+ self.length as usize
|
||||
@@ -95,0 +95,3 @@
|
||||
+
|
||||
+ #[error("bad alignment")]
|
||||
+ BadAlignment,
|
||||
@@ -139,3 +141,4 @@
|
||||
- Err(plain::Error::BadAlignment) => panic!(
|
||||
- "plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]!"
|
||||
- ),
|
||||
+ Err(plain::Error::BadAlignment) => {
|
||||
+ log::error!("plain::from_bytes failed due to alignment, but SdtHeader is #[repr(packed)]");
|
||||
+ return Err(InvalidSdtError::BadAlignment);
|
||||
+ }
|
||||
@@ -171 +174,3 @@
|
||||
- assert!(pages.len() >= mem::size_of::<SdtHeader>());
|
||||
+ if pages.len() < mem::size_of::<SdtHeader>() {
|
||||
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
|
||||
+ }
|
||||
@@ -174,2 +179,5 @@
|
||||
- let sdt = plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()])
|
||||
- .expect("either alignment is wrong, or the length is too short, both of which are already checked for");
|
||||
+ let sdt = match plain::from_bytes::<SdtHeader>(&sdt_mem[..mem::size_of::<SdtHeader>()]) {
|
||||
+ Ok(header) => header,
|
||||
+ Err(plain::Error::TooShort) => return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize)),
|
||||
+ Err(plain::Error::BadAlignment) => return Err(TablePhysLoadError::Validity(InvalidSdtError::BadAlignment)),
|
||||
+ };
|
||||
@@ -200 +208,4 @@
|
||||
- assert_eq!(left, 0);
|
||||
+ if left != 0 {
|
||||
+ log::error!("SDT physical load left {} bytes remaining after loop", left);
|
||||
+ return Err(TablePhysLoadError::Validity(InvalidSdtError::InvalidSize));
|
||||
+ }
|
||||
@@ -213,2 +224,2 @@
|
||||
- plain::from_bytes::<SdtHeader>(&self.0)
|
||||
- .expect("expected already validated Sdt to be able to get its header")
|
||||
+ // SAFETY: Sdt::new validated the slice length and SdtHeader is #[repr(packed)].
|
||||
+ unsafe { &*(self.0.as_ptr() as *const SdtHeader) }
|
||||
@@ -417,3 +428,3 @@
|
||||
- interpreter
|
||||
- .release_global_lock()
|
||||
- .expect("Failed to release GIL!"); //TODO: check if this should panic
|
||||
+ if let Err(e) = interpreter.release_global_lock() {
|
||||
+ log::error!("Failed to release AML global lock: {:?}", e);
|
||||
+ }
|
||||
@@ -435,4 +446,8 @@
|
||||
- .map(|physaddr| {
|
||||
- let physaddr: usize = physaddr
|
||||
- .try_into()
|
||||
- .expect("expected ACPI addresses to be compatible with the current word size");
|
||||
+ .filter_map(|physaddr| {
|
||||
+ let physaddr: usize = match physaddr.try_into() {
|
||||
+ Ok(addr) => addr,
|
||||
+ Err(e) => {
|
||||
+ log::error!("expected ACPI addresses to be compatible with the current word size: {}", e);
|
||||
+ return None;
|
||||
+ }
|
||||
+ };
|
||||
@@ -442 +457,7 @@
|
||||
- Sdt::load_from_physical(physaddr).expect("failed to load physical SDT")
|
||||
+ match Sdt::load_from_physical(physaddr) {
|
||||
+ Ok(sdt) => Some(sdt),
|
||||
+ Err(error) => {
|
||||
+ log::error!("failed to load physical SDT at {:#x}: {}", physaddr, error);
|
||||
+ None
|
||||
+ }
|
||||
+ }
|
||||
@@ -838,3 +859,4 @@
|
||||
- Err(plain::Error::BadAlignment) => unreachable!(
|
||||
- "plain::from_bytes reported bad alignment, but FadtAcpi2Struct is #[repr(packed)]"
|
||||
- ),
|
||||
+ Err(plain::Error::BadAlignment) => {
|
||||
+ log::error!("plain::from_bytes reported bad alignment for FadtAcpi2Struct, but it is #[repr(packed)]");
|
||||
+ None
|
||||
+ }
|
||||
@@ -849,2 +871,2 @@
|
||||
- plain::from_bytes::<FadtStruct>(&self.0 .0)
|
||||
- .expect("expected FADT struct to already be validated in Deref impl")
|
||||
+ // SAFETY: Fadt::new validated the slice length and FadtStruct is #[repr(packed)].
|
||||
+ unsafe { &*(self.0 .0.as_ptr() as *const FadtStruct) }
|
||||
@@ -863,3 +885,7 @@
|
||||
- let fadt_sdt = context
|
||||
- .take_single_sdt(*b"FACP")
|
||||
- .expect("expected ACPI to always have a FADT");
|
||||
+ let fadt_sdt = match context.take_single_sdt(*b"FACP") {
|
||||
+ Some(sdt) => sdt,
|
||||
+ None => {
|
||||
+ log::error!("expected ACPI to always have a FADT");
|
||||
+ return;
|
||||
+ }
|
||||
+ };
|
||||
@@ -876,4 +902,2 @@
|
||||
- Some(fadt2) => usize::try_from(fadt2.x_dsdt).unwrap_or_else(|_| {
|
||||
- usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize")
|
||||
- }),
|
||||
- None => usize::try_from(fadt.dsdt).expect("expected any given u32 to fit within usize"),
|
||||
+ Some(fadt2) => fadt2.x_dsdt as usize,
|
||||
+ None => fadt.dsdt as usize,
|
||||
@@ -0,0 +1,34 @@
|
||||
--- a/init/src/main.rs
|
||||
+++ b/init/src/main.rs
|
||||
@@ -167 +167,8 @@
|
||||
- UnitId(entry.file_name().unwrap().to_str().unwrap().to_owned()),
|
||||
+ let file_name = match entry.file_name().to_str() {
|
||||
+ Some(name) => name.to_owned(),
|
||||
+ None => {
|
||||
+ init_warn("skipping non-UTF8 service file name");
|
||||
+ continue;
|
||||
+ }
|
||||
+ };
|
||||
+ UnitId(file_name)
|
||||
@@ -174 +181,3 @@
|
||||
- libredox::call::setrens(0, 0).expect("init: failed to enter null namespace");
|
||||
+ if let Err(err) = libredox::call::setrens(0, 0) {
|
||||
+ init_error(&format!("init: failed to enter null namespace: {}", err));
|
||||
+ }
|
||||
--- a/init/src/service.rs
|
||||
+++ b/init/src/service.rs
|
||||
@@ -178,3 +178,11 @@
|
||||
- let current_namespace_fd = libredox::call::getns().expect("TODO");
|
||||
- libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
|
||||
- .expect("TODO");
|
||||
+ let current_namespace_fd = match libredox::call::getns() {
|
||||
+ Ok(fd) => fd,
|
||||
+ Err(err) => {
|
||||
+ init_error(&format!("failed to get namespace for {:?}: {}", command, err));
|
||||
+ return Some(child.id());
|
||||
+ }
|
||||
+ };
|
||||
+ if let Err(err) = libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd) {
|
||||
+ init_error(&format!("failed to register scheme {:?} for {:?}: {}", scheme, command, err));
|
||||
+ return Some(child.id());
|
||||
+ }
|
||||
@@ -0,0 +1,126 @@
|
||||
--- a/drivers/gpio/intel-gpiod/src/main.rs
|
||||
+++ b/drivers/gpio/intel-gpiod/src/main.rs
|
||||
@@ -130,6 +130,12 @@
|
||||
log::debug!("intel-gpiod: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-gpiod: ACPI symbols unavailable ({}), running with no GPIO controllers", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
|
||||
--- a/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||
+++ b/drivers/i2c/dw-acpi-i2cd/src/main.rs
|
||||
@@ -117,6 +117,12 @@
|
||||
log::debug!("dw-acpi-i2cd: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("dw-acpi-i2cd: ACPI symbols unavailable ({}), running with no I2C controllers", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
|
||||
--- a/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||
+++ b/drivers/i2c/intel-lpss-i2cd/src/main.rs
|
||||
@@ -117,6 +117,12 @@
|
||||
log::debug!("intel-lpss-i2cd: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-lpss-i2cd: ACPI symbols unavailable ({}), running with no I2C controllers", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
|
||||
--- a/drivers/gpio/i2c-gpio-expanderd/src/main.rs
|
||||
+++ b/drivers/gpio/i2c-gpio-expanderd/src/main.rs
|
||||
@@ -121,6 +121,12 @@
|
||||
log::debug!("i2c-gpio-expanderd: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("i2c-gpio-expanderd: ACPI symbols unavailable ({}), running with no GPIO expanders", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
|
||||
--- a/drivers/input/i2c-hidd/src/acpi.rs
|
||||
+++ b/drivers/input/i2c-hidd/src/acpi.rs
|
||||
@@ -32,6 +32,12 @@
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == ErrorKind::NotFound => {
|
||||
+ log::info!("i2c-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
|
||||
--- a/drivers/input/intel-thc-hidd/src/main.rs
|
||||
+++ b/drivers/input/intel-thc-hidd/src/main.rs
|
||||
@@ -95,8 +95,20 @@
|
||||
}
|
||||
|
||||
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
|
||||
- let entries =
|
||||
- fs::read_dir("/scheme/acpi/symbols").context("failed to read /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!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), skipping companion resolution", err);
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
+ };
|
||||
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
|
||||
|
||||
for entry in entries {
|
||||
@@ -136,8 +148,18 @@
|
||||
}
|
||||
|
||||
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
|
||||
- let entries =
|
||||
- fs::read_dir("/scheme/acpi/symbols").context("failed to read /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!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
+ };
|
||||
let mut devices = BTreeSet::new();
|
||||
|
||||
for entry in entries {
|
||||
@@ -0,0 +1,24 @@
|
||||
--- a/drivers/hwd/src/backend/acpi.rs
|
||||
+++ b/drivers/hwd/src/backend/acpi.rs
|
||||
@@ -16,7 +16,20 @@
|
||||
|
||||
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
// 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(());
|
||||
+ }
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("hwd: ACPI symbols unavailable ({}), running with no ACPI devices", err);
|
||||
+ return Ok(());
|
||||
+ }
|
||||
+ Err(err) => return Err(err.into()),
|
||||
+ };
|
||||
// TODO: Reimplement with getdents?
|
||||
let symbols_fd = libredox::Fd::open(
|
||||
"/scheme/acpi/symbols",
|
||||
@@ -0,0 +1,15 @@
|
||||
--- a/drivers/usb/ucsid/src/main.rs
|
||||
+++ b/drivers/usb/ucsid/src/main.rs
|
||||
@@ -397,6 +397,12 @@
|
||||
log::debug!("ucsid: ACPI symbols are not ready yet");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("ucsid: ACPI symbols unavailable ({}), running with no UCSI devices", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
diff --git a/drivers/acpid/src/dmi.rs b/drivers/acpid/src/dmi.rs
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/drivers/acpid/src/dmi.rs
|
||||
@@ -0,0 +1,350 @@
|
||||
+use std::fmt;
|
||||
+use syscall::PAGE_SIZE;
|
||||
+
|
||||
+use crate::acpi::PhysmapGuard;
|
||||
+
|
||||
+#[derive(Clone, Debug, Default)]
|
||||
+pub struct DmiStrings {
|
||||
+ pub sys_vendor: Option<String>,
|
||||
+ pub board_vendor: Option<String>,
|
||||
+ pub board_name: Option<String>,
|
||||
+ pub board_version: Option<String>,
|
||||
+ pub product_name: Option<String>,
|
||||
+ pub product_version: Option<String>,
|
||||
+ pub bios_version: Option<String>,
|
||||
+}
|
||||
+
|
||||
+impl DmiStrings {
|
||||
+ pub fn to_text(&self) -> String {
|
||||
+ let mut text = String::new();
|
||||
+ if let Some(ref v) = self.sys_vendor {
|
||||
+ text.push_str("sys_vendor=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.board_vendor {
|
||||
+ text.push_str("board_vendor=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.board_name {
|
||||
+ text.push_str("board_name=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.board_version {
|
||||
+ text.push_str("board_version=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.product_name {
|
||||
+ text.push_str("product_name=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.product_version {
|
||||
+ text.push_str("product_version=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ if let Some(ref v) = self.bios_version {
|
||||
+ text.push_str("bios_version=");
|
||||
+ text.push_str(v);
|
||||
+ text.push('\n');
|
||||
+ }
|
||||
+ text
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+#[derive(Debug)]
|
||||
+pub enum DmiError {
|
||||
+ InvalidData,
|
||||
+ Io(std::io::Error),
|
||||
+}
|
||||
+
|
||||
+impl fmt::Display for DmiError {
|
||||
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
+ match self {
|
||||
+ Self::InvalidData => write!(f, "invalid SMBIOS data"),
|
||||
+ Self::Io(e) => write!(f, "I/O error: {}", e),
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+impl From<std::io::Error> for DmiError {
|
||||
+ fn from(e: std::io::Error) -> Self {
|
||||
+ DmiError::Io(e)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/// Scan physical memory for SMBIOS entry point and parse DMI strings.
|
||||
+pub fn read_smbios_dmi() -> Result<DmiStrings, DmiError> {
|
||||
+ // SMBIOS entry point is in the BIOS ROM area 0xF0000-0xFFFFF
|
||||
+ let scan_start = 0xF_0000usize;
|
||||
+ let scan_size = 0x10_0000usize - scan_start;
|
||||
+ let start_page = scan_start / PAGE_SIZE * PAGE_SIZE;
|
||||
+ let start_offset = scan_start % PAGE_SIZE;
|
||||
+ let page_count = (start_offset + scan_size).div_ceil(PAGE_SIZE);
|
||||
+
|
||||
+ let pages = PhysmapGuard::map(start_page, page_count)?;
|
||||
+ let bios_region = &pages[start_offset..start_offset + scan_size];
|
||||
+
|
||||
+ // Search for 64-bit entry point first (_SM3_), then 32-bit (_SM_)
|
||||
+ // Entry points must be 16-byte aligned
|
||||
+ let mut entry_point_addr = None;
|
||||
+ let mut is_64bit = false;
|
||||
+
|
||||
+ for offset in (0..bios_region.len().saturating_sub(5)).step_by(16) {
|
||||
+ if bios_region[offset..].starts_with(b"_SM3_") {
|
||||
+ entry_point_addr = Some(scan_start + offset);
|
||||
+ is_64bit = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if entry_point_addr.is_none() {
|
||||
+ for offset in (0..bios_region.len().saturating_sub(4)).step_by(16) {
|
||||
+ if bios_region[offset..].starts_with(b"_SM_") {
|
||||
+ // Verify intermediate anchor "_DMI_" at offset 0x10 from anchor start
|
||||
+ let dmi_offset = offset + 0x10;
|
||||
+ if dmi_offset + 5 <= bios_region.len()
|
||||
+ && bios_region[dmi_offset..dmi_offset + 5].starts_with(b"_DMI_")
|
||||
+ {
|
||||
+ entry_point_addr = Some(scan_start + offset);
|
||||
+ is_64bit = false;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ let entry_addr = match entry_point_addr {
|
||||
+ Some(addr) => addr,
|
||||
+ None => {
|
||||
+ log::warn!("SMBIOS entry point not found in 0xF0000-0xFFFFF");
|
||||
+ return Ok(DmiStrings::default());
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ log::info!(
|
||||
+ "Found SMBIOS {} entry point at {:#x}",
|
||||
+ if is_64bit { "3.0" } else { "2.x" },
|
||||
+ entry_addr
|
||||
+ );
|
||||
+
|
||||
+ if is_64bit {
|
||||
+ parse_smbios_64(entry_addr)
|
||||
+ } else {
|
||||
+ parse_smbios_32(entry_addr)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+fn parse_smbios_32(entry_addr: usize) -> Result<DmiStrings, DmiError> {
|
||||
+ // 32-bit entry point is at least 0x1F bytes
|
||||
+ let page = entry_addr / PAGE_SIZE * PAGE_SIZE;
|
||||
+ let offset = entry_addr % PAGE_SIZE;
|
||||
+ let pages = PhysmapGuard::map(page, 1)?;
|
||||
+
|
||||
+ if pages.len() < offset + 0x1F {
|
||||
+ log::warn!("SMBIOS 32-bit entry point truncated");
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let ep = &pages[offset..];
|
||||
+ let ep_len = ep[0x05] as usize;
|
||||
+ if ep_len < 0x1F || pages.len() < offset + ep_len {
|
||||
+ log::warn!("SMBIOS 32-bit entry point length invalid: {}", ep_len);
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let checksum = ep[..ep_len].iter().fold(0u8, |sum, &b| sum.wrapping_add(b));
|
||||
+ if checksum != 0 {
|
||||
+ log::warn!("SMBIOS 32-bit entry point checksum failed");
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let table_len = u16::from_le_bytes([ep[0x16], ep[0x17]]) as usize;
|
||||
+ let table_addr = u32::from_le_bytes([ep[0x18], ep[0x19], ep[0x1A], ep[0x1B]]) as usize;
|
||||
+
|
||||
+ log::info!("SMBIOS 32-bit: table at {:#x}, len {}", table_addr, table_len);
|
||||
+
|
||||
+ parse_smbios_structures(table_addr, table_len)
|
||||
+}
|
||||
+
|
||||
+fn parse_smbios_64(entry_addr: usize) -> Result<DmiStrings, DmiError> {
|
||||
+ // 64-bit entry point is at least 0x18 bytes
|
||||
+ let page = entry_addr / PAGE_SIZE * PAGE_SIZE;
|
||||
+ let offset = entry_addr % PAGE_SIZE;
|
||||
+ let pages = PhysmapGuard::map(page, 1)?;
|
||||
+
|
||||
+ if pages.len() < offset + 0x18 {
|
||||
+ log::warn!("SMBIOS 64-bit entry point truncated");
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let ep = &pages[offset..];
|
||||
+ let ep_len = ep[0x06] as usize;
|
||||
+ if ep_len < 0x18 || pages.len() < offset + ep_len {
|
||||
+ log::warn!("SMBIOS 64-bit entry point length invalid: {}", ep_len);
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let checksum = ep[..ep_len].iter().fold(0u8, |sum, &b| sum.wrapping_add(b));
|
||||
+ if checksum != 0 {
|
||||
+ log::warn!("SMBIOS 64-bit entry point checksum failed");
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let table_max_size =
|
||||
+ u32::from_le_bytes([ep[0x0C], ep[0x0D], ep[0x0E], ep[0x0F]]) as usize;
|
||||
+ let table_addr = u64::from_le_bytes([
|
||||
+ ep[0x10], ep[0x11], ep[0x12], ep[0x13], ep[0x14], ep[0x15], ep[0x16], ep[0x17],
|
||||
+ ]) as usize;
|
||||
+
|
||||
+ log::info!(
|
||||
+ "SMBIOS 64-bit: table at {:#x}, max size {}",
|
||||
+ table_addr,
|
||||
+ table_max_size
|
||||
+ );
|
||||
+
|
||||
+ parse_smbios_structures(table_addr, table_max_size)
|
||||
+}
|
||||
+
|
||||
+fn parse_smbios_structures(table_addr: usize, table_len: usize) -> Result<DmiStrings, DmiError> {
|
||||
+ if table_addr == 0 || table_len == 0 {
|
||||
+ log::warn!("SMBIOS structure table address or length is zero");
|
||||
+ return Ok(DmiStrings::default());
|
||||
+ }
|
||||
+
|
||||
+ // Map the structure table. It may span multiple pages.
|
||||
+ let start_page = table_addr / PAGE_SIZE * PAGE_SIZE;
|
||||
+ let start_offset = table_addr % PAGE_SIZE;
|
||||
+ let total_needed = start_offset + table_len;
|
||||
+ let page_count = total_needed.div_ceil(PAGE_SIZE);
|
||||
+
|
||||
+ let pages = PhysmapGuard::map(start_page, page_count)?;
|
||||
+ if pages.len() < total_needed {
|
||||
+ log::warn!("SMBIOS structure table mapping truncated");
|
||||
+ return Err(DmiError::InvalidData);
|
||||
+ }
|
||||
+
|
||||
+ let table = &pages[start_offset..start_offset + table_len];
|
||||
+
|
||||
+ let mut dmi = DmiStrings::default();
|
||||
+ let mut pos = 0;
|
||||
+
|
||||
+ while pos + 4 <= table.len() {
|
||||
+ let stype = table[pos];
|
||||
+ let slen = table[pos + 1] as usize;
|
||||
+ let _handle = u16::from_le_bytes([table[pos + 2], table[pos + 3]]);
|
||||
+
|
||||
+ if slen < 4 {
|
||||
+ log::warn!(
|
||||
+ "Malformed SMBIOS structure at offset {}, type {}, len {}",
|
||||
+ pos,
|
||||
+ stype,
|
||||
+ slen
|
||||
+ );
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ if pos + slen > table.len() {
|
||||
+ log::warn!(
|
||||
+ "SMBIOS structure at offset {} extends past table end", pos
|
||||
+ );
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ // Parse strings after the formatted section
|
||||
+ let strings_start = pos + slen;
|
||||
+ let mut strings = Vec::new();
|
||||
+ let mut s = strings_start;
|
||||
+
|
||||
+ while s < table.len() {
|
||||
+ // Find null terminator
|
||||
+ let mut end = s;
|
||||
+ while end < table.len() && table[end] != 0 {
|
||||
+ end += 1;
|
||||
+ }
|
||||
+
|
||||
+ if end >= table.len() {
|
||||
+ log::warn!("Unterminated SMBIOS strings at offset {}", s);
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ let string = std::str::from_utf8(&table[s..end]).unwrap_or("").to_string();
|
||||
+ strings.push(string);
|
||||
+
|
||||
+ s = end + 1;
|
||||
+
|
||||
+ // Double null terminates the string set
|
||||
+ if s < table.len() && table[s] == 0 {
|
||||
+ s += 1;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ match stype {
|
||||
+ 0 => {
|
||||
+ // BIOS Information
|
||||
+ if slen > 5 {
|
||||
+ let idx = table[pos + 5] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.bios_version = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ 1 => {
|
||||
+ // System Information
|
||||
+ if slen > 4 {
|
||||
+ let idx = table[pos + 4] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.sys_vendor = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ if slen > 5 {
|
||||
+ let idx = table[pos + 5] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.product_name = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ if slen > 6 {
|
||||
+ let idx = table[pos + 6] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.product_version = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ 2 => {
|
||||
+ // Base Board Information
|
||||
+ if slen > 4 {
|
||||
+ let idx = table[pos + 4] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.board_vendor = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ if slen > 5 {
|
||||
+ let idx = table[pos + 5] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.board_name = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ if slen > 6 {
|
||||
+ let idx = table[pos + 6] as usize;
|
||||
+ if idx > 0 && idx <= strings.len() {
|
||||
+ dmi.board_version = Some(strings[idx - 1].clone());
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ 127 => {
|
||||
+ // End-of-table marker
|
||||
+ break;
|
||||
+ }
|
||||
+ _ => {}
|
||||
+ }
|
||||
+
|
||||
+ pos = s;
|
||||
+ }
|
||||
+
|
||||
+ Ok(dmi)
|
||||
+}
|
||||
|
||||
diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs
|
||||
--- a/drivers/acpid/src/acpi.rs
|
||||
+++ b/drivers/acpid/src/acpi.rs
|
||||
@@ -95,12 +95,12 @@
|
||||
BadChecksum,
|
||||
}
|
||||
|
||||
-struct PhysmapGuard {
|
||||
+pub struct PhysmapGuard {
|
||||
virt: *const u8,
|
||||
size: usize,
|
||||
}
|
||||
impl PhysmapGuard {
|
||||
- fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
|
||||
+ pub fn map(page: usize, page_count: usize) -> std::io::Result<Self> {
|
||||
let size = page_count * PAGE_SIZE;
|
||||
let virt = unsafe {
|
||||
common::physmap(page, size, common::Prot::RO, common::MemoryType::default())
|
||||
@@ -245,26 +245,55 @@
|
||||
symbol_cache: FxHashMap<String, String>,
|
||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||
}
|
||||
|
||||
impl AmlSymbols {
|
||||
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
|
||||
+ pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>, pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>) -> Self {
|
||||
Self {
|
||||
aml_context: None,
|
||||
symbol_cache: FxHashMap::default(),
|
||||
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
||||
aml_region_handlers,
|
||||
+ pci_fd,
|
||||
}
|
||||
}
|
||||
|
||||
- pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
||||
+ pub fn init(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
if self.aml_context.is_some() {
|
||||
return Err("AML interpreter already initialized".into());
|
||||
}
|
||||
let format_err = |err| format!("{:?}", err);
|
||||
- let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
||||
+ let handler = AmlPhysMemHandler::new(Arc::clone(&self.pci_fd), Arc::clone(&self.page_cache));
|
||||
//TODO: use these parsed tables for the rest of acpid
|
||||
- let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||
+ let rsdp_address = match std::env::var("RSDP_ADDR") {
|
||||
+ Ok(addr) => usize::from_str_radix(&addr, 16)?,
|
||||
+ Err(_) => {
|
||||
+ // RSDP_ADDR not provided — probe BIOS area (0xE0000–0xFFFFF) for RSDP signature
|
||||
+ log::info!("RSDP_ADDR not set, probing BIOS area for RSDP...");
|
||||
+ let mut found = None;
|
||||
+ for page_base in (0xE_0000..0x10_0000).step_by(16) {
|
||||
+ let mapped = unsafe {
|
||||
+ common::physmap(
|
||||
+ page_base,
|
||||
+ 16,
|
||||
+ common::Prot::RW,
|
||||
+ common::MemoryType::default(),
|
||||
+ )
|
||||
+ };
|
||||
+ if let Ok(virt) = mapped {
|
||||
+ let sig = unsafe { std::slice::from_raw_parts(virt as *const u8, 8) };
|
||||
+ if sig == b"RSD PTR " {
|
||||
+ log::info!("found RSDP at physical {:#x}", page_base);
|
||||
+ found = Some(page_base);
|
||||
+ break;
|
||||
+ }
|
||||
+ let _ = unsafe { libredox::call::munmap(virt as *mut (), 16) };
|
||||
+ }
|
||||
+ }
|
||||
+ found.ok_or("RSDP not found in BIOS area (0xE0000-0xFFFFF)")?
|
||||
+ }
|
||||
+ };
|
||||
let tables =
|
||||
unsafe { AcpiTables::from_rsdp(handler.clone(), rsdp_address).map_err(format_err)? };
|
||||
let platform = AcpiPlatform::new(tables, handler).map_err(format_err)?;
|
||||
@@ -278,10 +307,9 @@
|
||||
|
||||
pub fn aml_context_mut(
|
||||
&mut self,
|
||||
- pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<&mut Interpreter<AmlPhysMemHandler>, AmlEvalError> {
|
||||
if self.aml_context.is_none() {
|
||||
- match self.init(pci_fd) {
|
||||
+ match self.init() {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
log::error!("failed to initialize AML context: {}", err);
|
||||
@@ -305,8 +333,8 @@
|
||||
None
|
||||
}
|
||||
|
||||
- pub fn build_cache(&mut self, pci_fd: Option<&libredox::Fd>) {
|
||||
- let Ok(aml_context) = self.aml_context_mut(pci_fd) else {
|
||||
+ pub fn build_cache(&mut self) {
|
||||
+ let Ok(aml_context) = self.aml_context_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -382,6 +410,8 @@
|
||||
|
||||
aml_symbols: RwLock<AmlSymbols>,
|
||||
|
||||
+ pub pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||
+
|
||||
// TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1
|
||||
// states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll
|
||||
// generate an index only for those.
|
||||
@@ -397,7 +427,7 @@
|
||||
args: Vec<AmlSerdeValue>,
|
||||
) -> Result<AmlSerdeValue, AmlEvalError> {
|
||||
let mut symbols = self.aml_symbols.write();
|
||||
- let interpreter = symbols.aml_context_mut(None)?;
|
||||
+ let interpreter = symbols.aml_context_mut()?;
|
||||
interpreter.acquire_global_lock(16)?;
|
||||
|
||||
let args = args
|
||||
@@ -440,13 +470,17 @@
|
||||
})
|
||||
.collect::<Vec<Sdt>>();
|
||||
|
||||
+ let pci_fd = Arc::new(parking_lot::RwLock::new(None));
|
||||
+
|
||||
let mut this = Self {
|
||||
tables,
|
||||
dsdt: None,
|
||||
fadt: None,
|
||||
|
||||
// Temporary values
|
||||
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
|
||||
+ aml_symbols: RwLock::new(AmlSymbols::new(ec, Arc::clone(&pci_fd))),
|
||||
+
|
||||
+ pci_fd,
|
||||
|
||||
next_ctx: RwLock::new(0),
|
||||
|
||||
@@ -526,7 +560,7 @@
|
||||
}
|
||||
|
||||
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
|
||||
- if let Ok(aml_symbols) = self.aml_symbols(None) {
|
||||
+ if let Ok(aml_symbols) = self.aml_symbols() {
|
||||
aml_symbols.lookup(symbol)
|
||||
} else {
|
||||
None
|
||||
@@ -535,7 +569,6 @@
|
||||
|
||||
pub fn aml_symbols(
|
||||
&self,
|
||||
- pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
|
||||
// return the cached value if it exists
|
||||
let symbols = self.aml_symbols.read();
|
||||
@@ -550,7 +583,7 @@
|
||||
|
||||
let mut aml_symbols = self.aml_symbols.write();
|
||||
|
||||
- aml_symbols.build_cache(pci_fd);
|
||||
+ aml_symbols.build_cache();
|
||||
|
||||
// return the cached value
|
||||
Ok(RwLockWriteGuard::downgrade(aml_symbols))
|
||||
|
||||
diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs
|
||||
--- a/drivers/acpid/src/main.rs
|
||||
+++ b/drivers/acpid/src/main.rs
|
||||
@@ -14,6 +14,7 @@
|
||||
mod aml_physmem;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
mod ec;
|
||||
+mod dmi;
|
||||
|
||||
mod scheme;
|
||||
|
||||
@@ -73,6 +74,14 @@
|
||||
];
|
||||
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
||||
|
||||
+ let dmi_strings = match self::dmi::read_smbios_dmi() {
|
||||
+ Ok(strings) => Some(strings),
|
||||
+ Err(e) => {
|
||||
+ log::warn!("Failed to read SMBIOS DMI: {}", e);
|
||||
+ None
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
// TODO: I/O permission bitmap?
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||
@@ -83,7 +92,7 @@
|
||||
let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
|
||||
let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
|
||||
|
||||
- let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
|
||||
+ let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket, dmi_strings);
|
||||
let mut handler = Blocking::new(&socket, 16);
|
||||
|
||||
event_queue
|
||||
|
||||
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
|
||||
--- a/drivers/acpid/src/scheme.rs
|
||||
+++ b/drivers/acpid/src/scheme.rs
|
||||
@@ -22,12 +22,13 @@
|
||||
use syscall::{EOVERFLOW, EPERM};
|
||||
|
||||
use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
|
||||
+use crate::dmi::DmiStrings;
|
||||
|
||||
pub struct AcpiScheme<'acpi, 'sock> {
|
||||
ctx: &'acpi AcpiContext,
|
||||
handles: HandleMap<Handle<'acpi>>,
|
||||
- pci_fd: Option<Fd>,
|
||||
socket: &'sock Socket,
|
||||
+ dmi_text: Option<String>,
|
||||
}
|
||||
|
||||
struct Handle<'a> {
|
||||
@@ -43,6 +44,7 @@
|
||||
Symbol { name: String, description: String },
|
||||
SchemeRoot,
|
||||
RegisterPci,
|
||||
+ Dmi(String),
|
||||
}
|
||||
|
||||
impl HandleKind<'_> {
|
||||
@@ -55,6 +57,7 @@
|
||||
Self::Symbol { .. } => false,
|
||||
Self::SchemeRoot => false,
|
||||
Self::RegisterPci => false,
|
||||
+ Self::Dmi(_) => false,
|
||||
}
|
||||
}
|
||||
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
||||
@@ -65,6 +68,7 @@
|
||||
.ok_or(Error::new(EBADFD))?
|
||||
.length(),
|
||||
Self::Symbol { description, .. } => description.len(),
|
||||
+ Self::Dmi(text) => text.len(),
|
||||
// Directories
|
||||
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
||||
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
|
||||
@@ -73,12 +77,12 @@
|
||||
}
|
||||
|
||||
impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
|
||||
- pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket) -> Self {
|
||||
+ pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket, dmi: Option<DmiStrings>) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
handles: HandleMap::new(),
|
||||
- pci_fd: None,
|
||||
socket,
|
||||
+ dmi_text: dmi.map(|d| d.to_text()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,6 +200,7 @@
|
||||
match &*components {
|
||||
[""] => HandleKind::TopLevel,
|
||||
["register_pci"] => HandleKind::RegisterPci,
|
||||
+ ["dmi"] => HandleKind::Dmi(self.dmi_text.clone().unwrap_or_default()),
|
||||
["tables"] => HandleKind::Tables,
|
||||
|
||||
["tables", table] => {
|
||||
@@ -204,7 +209,7 @@
|
||||
}
|
||||
|
||||
["symbols"] => {
|
||||
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
||||
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
|
||||
HandleKind::Symbols(aml_symbols)
|
||||
} else {
|
||||
return Err(Error::new(EIO));
|
||||
@@ -309,6 +314,7 @@
|
||||
.ok_or(Error::new(EBADFD))?
|
||||
.as_slice(),
|
||||
HandleKind::Symbol { description, .. } => description.as_bytes(),
|
||||
+ HandleKind::Dmi(ref text) => text.as_bytes(),
|
||||
_ => return Err(Error::new(EINVAL)),
|
||||
};
|
||||
|
||||
@@ -332,9 +338,13 @@
|
||||
|
||||
match &handle.kind {
|
||||
HandleKind::TopLevel => {
|
||||
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
|
||||
+ const TOPLEVEL_ENTRIES: &[(DirentKind, &str)] = &[
|
||||
+ (DirentKind::Regular, "dmi"),
|
||||
+ (DirentKind::Directory, "tables"),
|
||||
+ (DirentKind::Directory, "symbols"),
|
||||
+ ];
|
||||
|
||||
- for (idx, name) in TOPLEVEL_ENTRIES
|
||||
+ for (idx, (kind, name)) in TOPLEVEL_ENTRIES
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(opaque_offset as usize)
|
||||
@@ -343,7 +353,7 @@
|
||||
inode: 0,
|
||||
next_opaque_id: idx as u64 + 1,
|
||||
name,
|
||||
- kind: DirentKind::Directory,
|
||||
+ kind: *kind,
|
||||
})?;
|
||||
}
|
||||
}
|
||||
@@ -470,10 +480,12 @@
|
||||
}
|
||||
let new_fd = libredox::Fd::new(new_fd);
|
||||
|
||||
- if self.pci_fd.is_some() {
|
||||
- return Err(Error::new(EINVAL));
|
||||
- } else {
|
||||
- self.pci_fd = Some(new_fd);
|
||||
+ {
|
||||
+ let mut pci_fd = self.ctx.pci_fd.write();
|
||||
+ if pci_fd.is_some() {
|
||||
+ return Err(Error::new(EINVAL));
|
||||
+ }
|
||||
+ *pci_fd = Some(new_fd);
|
||||
}
|
||||
|
||||
Ok(num_fds)
|
||||
@@ -0,0 +1,408 @@
|
||||
diff --git a/drivers/input/i2c-hidd/src/main.rs b/drivers/input/i2c-hidd/src/main.rs
|
||||
new file mode 100644
|
||||
index 00000000..88270e37
|
||||
--- /dev/null
|
||||
+++ b/drivers/input/i2c-hidd/src/main.rs
|
||||
@@ -0,0 +1,114 @@
|
||||
+use std::process;
|
||||
+use std::thread;
|
||||
+use std::time::Duration;
|
||||
+
|
||||
+use anyhow::{Context, Result};
|
||||
+
|
||||
+mod acpi;
|
||||
+mod hid;
|
||||
+mod input;
|
||||
+mod quirks;
|
||||
+
|
||||
+use acpi::{
|
||||
+ hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
||||
+ scan_acpi_i2c_hid_devices,
|
||||
+};
|
||||
+use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
|
||||
+use input::InputForwarder;
|
||||
+use quirks::match_probe_failure_quirk;
|
||||
+
|
||||
+fn main() {
|
||||
+ daemon::Daemon::new(daemon);
|
||||
+}
|
||||
+
|
||||
+fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
+ common::setup_logging(
|
||||
+ "input",
|
||||
+ "i2c-hid",
|
||||
+ "i2c-hidd",
|
||||
+ common::output_level(),
|
||||
+ common::file_level(),
|
||||
+ );
|
||||
+
|
||||
+ if let Err(err) = run(daemon) {
|
||||
+ log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
|
||||
+ process::exit(1);
|
||||
+ }
|
||||
+
|
||||
+ process::exit(0);
|
||||
+}
|
||||
+
|
||||
+fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||
+ log::info!("RB_I2C_HIDD_SCHEMA version=1");
|
||||
+
|
||||
+ let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
|
||||
+ if devices.is_empty() {
|
||||
+ log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
|
||||
+ }
|
||||
+
|
||||
+ let mut workers = Vec::new();
|
||||
+ for device in devices {
|
||||
+ log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
|
||||
+ workers.push(thread::spawn(move || {
|
||||
+ if let Err(err) = bind_device(&device) {
|
||||
+ log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
|
||||
+ }
|
||||
+ }));
|
||||
+ }
|
||||
+
|
||||
+ daemon.ready();
|
||||
+
|
||||
+ if workers.is_empty() {
|
||||
+ loop {
|
||||
+ thread::sleep(Duration::from_secs(5));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for worker in workers {
|
||||
+ let _ = worker.join();
|
||||
+ }
|
||||
+ Ok(())
|
||||
+}
|
||||
+
|
||||
+pub fn bind_device(device_path: &str) -> Result<()> {
|
||||
+ prepare_acpi_device(device_path)
|
||||
+ .with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
|
||||
+
|
||||
+ let resources = read_decoded_resources(device_path)
|
||||
+ .with_context(|| format!("failed to decode _CRS for {device_path}"))?;
|
||||
+ log::info!(
|
||||
+ "RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
|
||||
+ device_path,
|
||||
+ resources.i2c.adapter,
|
||||
+ resources.i2c.address,
|
||||
+ resources.irq,
|
||||
+ resources.gpio_int.len(),
|
||||
+ resources.gpio_io.len()
|
||||
+ );
|
||||
+
|
||||
+ let hid_desc_addr = hid_descriptor_address(device_path)
|
||||
+ .with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
|
||||
+ let adapter = I2cAdapterClient::new(resources.i2c.clone());
|
||||
+ let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
|
||||
+ .with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
|
||||
+ let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
|
||||
+ .with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
|
||||
+ let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
|
||||
+
|
||||
+ match stream_input_reports(
|
||||
+ &adapter,
|
||||
+ resources.i2c.address,
|
||||
+ &hid_desc,
|
||||
+ &report_desc,
|
||||
+ &mut forwarder,
|
||||
+ ) {
|
||||
+ Ok(()) => Ok(()),
|
||||
+ Err(err) => {
|
||||
+ let quirk =
|
||||
+ match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
|
||||
+ recover_acpi_device(device_path, &resources, quirk.as_ref())
|
||||
+ .with_context(|| format!("failed ACPI recovery for {device_path}"))?;
|
||||
+ Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/drivers/input/intel-thc-hidd/src/main.rs b/drivers/input/intel-thc-hidd/src/main.rs
|
||||
new file mode 100644
|
||||
index 00000000..c5cda29e
|
||||
--- /dev/null
|
||||
+++ b/drivers/input/intel-thc-hidd/src/main.rs
|
||||
@@ -0,0 +1,282 @@
|
||||
+use std::collections::BTreeSet;
|
||||
+use std::fs::{self, OpenOptions};
|
||||
+use std::io::Read;
|
||||
+use std::process;
|
||||
+use std::thread;
|
||||
+use std::time::Duration;
|
||||
+
|
||||
+use acpi_resource::ResourceDescriptor;
|
||||
+use amlserde::{AmlSerde, AmlSerdeValue};
|
||||
+use anyhow::{bail, Context, Result};
|
||||
+use libredox::flag::{O_CLOEXEC, O_RDWR};
|
||||
+use pcid_interface::PciFunctionHandle;
|
||||
+
|
||||
+mod quicki2c;
|
||||
+mod thc;
|
||||
+
|
||||
+use quicki2c::QuickI2cTransport;
|
||||
+use thc::{ThcController, SUPPORTED_PCI_IDS};
|
||||
+
|
||||
+fn main() {
|
||||
+ pcid_interface::pci_daemon(daemon);
|
||||
+}
|
||||
+
|
||||
+fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
+ common::setup_logging(
|
||||
+ "input",
|
||||
+ "intel-thc",
|
||||
+ "intel-thc-hidd",
|
||||
+ common::output_level(),
|
||||
+ common::file_level(),
|
||||
+ );
|
||||
+
|
||||
+ if let Err(err) = run(daemon, &mut pcid_handle) {
|
||||
+ log::error!("RB_THC_HIDD_FATAL error={err:#}");
|
||||
+ process::exit(1);
|
||||
+ }
|
||||
+
|
||||
+ process::exit(0);
|
||||
+}
|
||||
+
|
||||
+fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
|
||||
+ log::info!("RB_THC_HIDD_SCHEMA version=1");
|
||||
+
|
||||
+ let pci_config = pcid_handle.config();
|
||||
+ let id = (
|
||||
+ pci_config.func.full_device_id.vendor_id,
|
||||
+ pci_config.func.full_device_id.device_id,
|
||||
+ );
|
||||
+ if !SUPPORTED_PCI_IDS.contains(&id) {
|
||||
+ bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
|
||||
+ }
|
||||
+
|
||||
+ pcid_handle.enable_device();
|
||||
+ let bar = unsafe { pcid_handle.map_bar(0) };
|
||||
+ let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
|
||||
+ .context("failed to create THC controller")?;
|
||||
+
|
||||
+ let companion = resolve_acpi_companion(&pci_config.func.addr)
|
||||
+ .context("failed to resolve ACPI companion for THC device")?;
|
||||
+ let override_address = companion
|
||||
+ .as_deref()
|
||||
+ .map(companion_slave_address_override)
|
||||
+ .transpose()
|
||||
+ .context("failed to evaluate THC slave-address override")?
|
||||
+ .flatten();
|
||||
+ let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
|
||||
+ .context("failed to scan PNP0C50 devices for THC controller")?;
|
||||
+
|
||||
+ let effective_address = override_address.unwrap_or(0x0015);
|
||||
+ let transport = QuickI2cTransport::new(controller, effective_address);
|
||||
+ transport.prime_controller();
|
||||
+ transport.emulate_transfer(&[]);
|
||||
+ log::debug!("RB_THC_HIDD status={:#x}", transport.status());
|
||||
+
|
||||
+ match transport.register_with_i2cd(companion.as_deref(), override_address) {
|
||||
+ Ok(()) => {}
|
||||
+ Err(err) => {
|
||||
+ log::warn!("RB_THC_HIDD registration error={err:#}");
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ log::info!(
|
||||
+ "RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
|
||||
+ pci_config.func.name(),
|
||||
+ companion,
|
||||
+ override_address,
|
||||
+ hid_devices.len()
|
||||
+ );
|
||||
+
|
||||
+ daemon.ready();
|
||||
+
|
||||
+ loop {
|
||||
+ thread::sleep(Duration::from_secs(5));
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
|
||||
+ 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!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
|
||||
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), skipping companion resolution", err);
|
||||
+ return Ok(None);
|
||||
+ }
|
||||
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
+ };
|
||||
+ let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
|
||||
+
|
||||
+ for entry in entries {
|
||||
+ let entry = entry.context("failed to enumerate ACPI symbol entry")?;
|
||||
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
+ continue;
|
||||
+ };
|
||||
+ if !file_name.ends_with("._ADR") {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ let symbol = read_aml_symbol(&file_name)?;
|
||||
+ if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ let device = symbol
|
||||
+ .name
|
||||
+ .strip_suffix("._ADR")
|
||||
+ .unwrap_or(&symbol.name)
|
||||
+ .trim_start_matches('\\')
|
||||
+ .replace('/', ".");
|
||||
+ return Ok(Some(device));
|
||||
+ }
|
||||
+
|
||||
+ Ok(None)
|
||||
+}
|
||||
+
|
||||
+fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
|
||||
+ let icrs = evaluate_integer_method(path, "ICRS").ok();
|
||||
+ let isub = evaluate_integer_method(path, "ISUB").ok();
|
||||
+ Ok(icrs
|
||||
+ .or(isub)
|
||||
+ .map(|value| u16::try_from(value))
|
||||
+ .transpose()
|
||||
+ .context("THC ACPI override out of range")?)
|
||||
+}
|
||||
+
|
||||
+fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
|
||||
+ 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!("intel-thc-hidd: ACPI symbols are not ready yet");
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
|
||||
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
|
||||
+ return Ok(Vec::new());
|
||||
+ }
|
||||
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
+ };
|
||||
+ let mut devices = BTreeSet::new();
|
||||
+
|
||||
+ for entry in entries {
|
||||
+ let entry = entry.context("failed to enumerate ACPI HID entry")?;
|
||||
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
+ continue;
|
||||
+ };
|
||||
+ if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ let symbol = read_aml_symbol(&file_name)?;
|
||||
+ let is_hid = matches!(
|
||||
+ decode_hardware_id(&symbol.value).as_deref(),
|
||||
+ Some("PNP0C50" | "ACPI0C50")
|
||||
+ );
|
||||
+ if !is_hid {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ let device = symbol
|
||||
+ .name
|
||||
+ .strip_suffix("._HID")
|
||||
+ .or_else(|| symbol.name.strip_suffix("._CID"))
|
||||
+ .unwrap_or(&symbol.name)
|
||||
+ .trim_start_matches('\\')
|
||||
+ .replace('/', ".");
|
||||
+ if let Some(companion) = companion {
|
||||
+ if !is_bound_to_companion(&device, companion)? {
|
||||
+ continue;
|
||||
+ }
|
||||
+ }
|
||||
+ devices.insert(device);
|
||||
+ }
|
||||
+
|
||||
+ Ok(devices.into_iter().collect())
|
||||
+}
|
||||
+
|
||||
+fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
|
||||
+ let resource_path = format!("/scheme/acpi/resources/{device}");
|
||||
+ let serialized = match fs::read_to_string(&resource_path) {
|
||||
+ Ok(serialized) => serialized,
|
||||
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
||||
+ Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
|
||||
+ };
|
||||
+
|
||||
+ let resources: Vec<ResourceDescriptor> =
|
||||
+ ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
|
||||
+ Ok(resources.into_iter().any(|resource| match resource {
|
||||
+ ResourceDescriptor::I2cSerialBus(bus) => bus
|
||||
+ .resource_source
|
||||
+ .as_ref()
|
||||
+ .map(|source| source.source == companion)
|
||||
+ .unwrap_or(false),
|
||||
+ _ => false,
|
||||
+ }))
|
||||
+}
|
||||
+
|
||||
+fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
|
||||
+ let symbol_name = format!("{}.{}", normalize_device_path(path), method);
|
||||
+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
|
||||
+ let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
|
||||
+ .with_context(|| format!("failed to open {symbol_path}"))?;
|
||||
+
|
||||
+ let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
|
||||
+ .context("failed to serialize ACPI call arguments")?
|
||||
+ .into_bytes();
|
||||
+ payload.resize(payload.len() + 2048, 0);
|
||||
+ let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
|
||||
+ .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
|
||||
+ let response = std::str::from_utf8(&payload[..used])
|
||||
+ .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
|
||||
+ match ron::from_str::<AmlSerdeValue>(response)
|
||||
+ .with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
|
||||
+ {
|
||||
+ AmlSerdeValue::Integer(value) => Ok(value),
|
||||
+ other => bail!("{}.{} returned non-integer value {other:?}", path, method),
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
|
||||
+ let path = format!("/scheme/acpi/symbols/{file_name}");
|
||||
+ let mut file = OpenOptions::new()
|
||||
+ .read(true)
|
||||
+ .open(&path)
|
||||
+ .with_context(|| format!("failed to open {path}"))?;
|
||||
+ let mut ron_text = String::new();
|
||||
+ file.read_to_string(&mut ron_text)
|
||||
+ .with_context(|| format!("failed to read {path}"))?;
|
||||
+ ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
|
||||
+}
|
||||
+
|
||||
+fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
|
||||
+ match value {
|
||||
+ AmlSerdeValue::String(value) => Some(value.clone()),
|
||||
+ AmlSerdeValue::Integer(integer) => {
|
||||
+ let vendor = integer & 0xFFFF;
|
||||
+ let device = (integer >> 16) & 0xFFFF;
|
||||
+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
|
||||
+ let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
|
||||
+ let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
|
||||
+ let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
|
||||
+ let device_1 = (device >> 4) & 0xF;
|
||||
+ let device_2 = (device >> 0) & 0xF;
|
||||
+ let device_3 = (device >> 12) & 0xF;
|
||||
+ let device_4 = (device >> 8) & 0xF;
|
||||
+ Some(format!(
|
||||
+ "{}{}{}{:01X}{:01X}{:01X}{:01X}",
|
||||
+ vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
|
||||
+ ))
|
||||
+ }
|
||||
+ _ => None,
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+fn normalize_device_path(path: &str) -> String {
|
||||
+ path.trim_start_matches('\\')
|
||||
+ .trim_matches('/')
|
||||
+ .replace('/', ".")
|
||||
+}
|
||||
@@ -0,0 +1,240 @@
|
||||
diff --git a/init/src/scheduler.rs b/init/src/scheduler.rs
|
||||
--- a/init/src/scheduler.rs
|
||||
+++ b/init/src/scheduler.rs
|
||||
@@ -1,14 +1,34 @@
|
||||
-use std::collections::{BTreeSet, VecDeque};
|
||||
+use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
|
||||
use crate::InitConfig;
|
||||
-use crate::color::{init_error, status_ok, status_skip};
|
||||
+use crate::color::{init_error, init_warn, status_ok, status_skip};
|
||||
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
||||
|
||||
const SPAWN_BATCH_SIZE: usize = 50;
|
||||
|
||||
+#[derive(Clone, Debug)]
|
||||
+pub enum RestartPolicy {
|
||||
+ Never,
|
||||
+ OnFailure,
|
||||
+ Always,
|
||||
+}
|
||||
+
|
||||
+/// Tracks the restart state for a supervised service.
|
||||
+pub struct ServiceState {
|
||||
+ pub unit_id: UnitId,
|
||||
+ pub cmd: String,
|
||||
+ pub restart_policy: RestartPolicy,
|
||||
+ pub max_restarts: u32,
|
||||
+ pub restart_count: u32,
|
||||
+ /// Monotonic time of last restart (for backoff calculation).
|
||||
+ pub last_restart_ms: u64,
|
||||
+}
|
||||
+
|
||||
pub struct Scheduler {
|
||||
pending: VecDeque<Job>,
|
||||
completed: BTreeSet<UnitId>,
|
||||
+ /// Maps child PID → service state for supervised services.
|
||||
+ pub supervised: BTreeMap<u32, ServiceState>,
|
||||
}
|
||||
|
||||
struct Job {
|
||||
@@ -25,6 +45,7 @@
|
||||
Scheduler {
|
||||
pending: VecDeque::new(),
|
||||
completed: BTreeSet::new(),
|
||||
+ supervised: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,25 +96,38 @@
|
||||
|
||||
match job.kind {
|
||||
JobKind::Start => {
|
||||
- let deps_ok = {
|
||||
+ let (deps_ok, hard_deps_met) = {
|
||||
let unit = unit_store.unit(&job.unit);
|
||||
- let mut ok = true;
|
||||
- for dep in &unit.info.requires_weak {
|
||||
+ let mut hard_deps_met = true;
|
||||
+ for dep in &unit.info.requires {
|
||||
if self.completed.contains(dep) {
|
||||
continue;
|
||||
}
|
||||
if !unit_store.has_unit(dep) {
|
||||
- continue;
|
||||
- }
|
||||
- let in_pending = self.pending.iter().any(|pj| &pj.unit == dep);
|
||||
- if in_pending {
|
||||
- ok = false;
|
||||
+ init_error(&format!(
|
||||
+ "{}: hard dependency '{}' not found, skipping",
|
||||
+ job.unit.0, dep.0
|
||||
+ ));
|
||||
+ hard_deps_met = false;
|
||||
break;
|
||||
}
|
||||
+ hard_deps_met = false;
|
||||
+ break;
|
||||
}
|
||||
- ok
|
||||
+ let weak_ok = unit.info.requires_weak.iter().all(|dep| {
|
||||
+ self.completed.contains(dep)
|
||||
+ || !unit_store.has_unit(dep)
|
||||
+ || self.pending.iter().any(|pj| &pj.unit == dep)
|
||||
+ });
|
||||
+ (weak_ok, hard_deps_met)
|
||||
};
|
||||
|
||||
+ if !hard_deps_met {
|
||||
+ init_warn(&format!("{}: hard dependency not met, skipping", job.unit.0));
|
||||
+ self.completed.insert(job.unit);
|
||||
+ continue 'a;
|
||||
+ }
|
||||
+
|
||||
if !deps_ok {
|
||||
defer_count += 1;
|
||||
self.pending.push_back(job);
|
||||
@@ -106,7 +140,7 @@
|
||||
defer_count = 0;
|
||||
|
||||
let unit = unit_store.unit_mut(&job.unit);
|
||||
- run(unit, init_config);
|
||||
+ run(unit, init_config, &mut self.supervised);
|
||||
self.completed.insert(job.unit);
|
||||
spawned_this_step += 1;
|
||||
|
||||
@@ -119,7 +153,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
-fn run(unit: &mut Unit, config: &mut InitConfig) {
|
||||
+fn run(unit: &mut Unit, config: &mut InitConfig, supervised: &mut BTreeMap<u32, ServiceState>) {
|
||||
match &unit.kind {
|
||||
UnitKind::LegacyScript { script } => {
|
||||
for cmd in script.clone() {
|
||||
@@ -127,13 +161,15 @@
|
||||
}
|
||||
}
|
||||
UnitKind::Service { service } => {
|
||||
- let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0);
|
||||
+ let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0).clone();
|
||||
if config.skip_cmd.contains(&service.cmd) {
|
||||
status_skip(&format!("Skipping {} ({})", desc, service.cmd));
|
||||
return;
|
||||
}
|
||||
status_ok(&format!("Started {}", desc));
|
||||
service.spawn(&config.envs);
|
||||
+ // Supervision infrastructure is in place; full PID tracking requires
|
||||
+ // service.spawn() to return Option<u32> (added by a later patch).
|
||||
}
|
||||
UnitKind::Target {} => {}
|
||||
}
|
||||
|
||||
diff --git a/init/src/script.rs b/init/src/script.rs
|
||||
--- a/init/src/script.rs
|
||||
+++ b/init/src/script.rs
|
||||
@@ -12,12 +12,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
-pub struct Script(pub Vec<Command>, pub Vec<UnitId>);
|
||||
+pub struct Script(pub Vec<Command>, pub Vec<UnitId>, pub Vec<UnitId>);
|
||||
|
||||
impl Script {
|
||||
pub fn from_str(config: &str, errors: &mut Vec<String>) -> io::Result<Script> {
|
||||
let mut cmds = vec![];
|
||||
let mut requires_weak = vec![];
|
||||
+ let mut requires = vec![];
|
||||
|
||||
for line_raw in config.lines() {
|
||||
let line = line_raw.trim();
|
||||
@@ -27,14 +28,14 @@
|
||||
|
||||
let args = line.split(' ').map(subst_env);
|
||||
|
||||
- match Command::parse(args, &mut requires_weak) {
|
||||
+ match Command::parse(args, &mut requires_weak, &mut requires) {
|
||||
Ok(None) => {}
|
||||
Ok(Some(cmd)) => cmds.push(cmd),
|
||||
Err(err) => errors.push(err),
|
||||
}
|
||||
}
|
||||
|
||||
- Ok(Script(cmds, requires_weak))
|
||||
+ Ok(Script(cmds, requires_weak, requires))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +55,17 @@
|
||||
fn parse(
|
||||
mut args: impl Iterator<Item = String>,
|
||||
requires_weak: &mut Vec<UnitId>,
|
||||
+ requires: &mut Vec<UnitId>,
|
||||
) -> Result<Option<Command>, String> {
|
||||
let Some(cmd) = args.next() else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match cmd.as_str() {
|
||||
+ "requires" => {
|
||||
+ requires.extend(args.map(UnitId));
|
||||
+ Ok(None)
|
||||
+ }
|
||||
"requires_weak" => {
|
||||
requires_weak.extend(args.map(UnitId));
|
||||
Ok(None)
|
||||
|
||||
diff --git a/init/src/unit.rs b/init/src/unit.rs
|
||||
--- a/init/src/unit.rs
|
||||
+++ b/init/src/unit.rs
|
||||
@@ -1,4 +1,4 @@
|
||||
-use std::collections::BTreeMap;
|
||||
+use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
@@ -76,7 +76,9 @@
|
||||
|
||||
pub fn load_units(&mut self, root_unit: UnitId, errors: &mut Vec<String>) -> Vec<UnitId> {
|
||||
let mut loaded_units = vec![];
|
||||
- let mut pending_units = vec![root_unit];
|
||||
+ let mut pending_units = vec![root_unit.clone()];
|
||||
+ let mut seen = BTreeSet::new();
|
||||
+ seen.insert(root_unit);
|
||||
|
||||
while let Some(unit_id) = pending_units.pop() {
|
||||
if self.units.contains_key(&unit_id) {
|
||||
@@ -85,7 +87,16 @@
|
||||
let unit = self.load_single_unit(unit_id, errors);
|
||||
if let Some(unit) = unit {
|
||||
loaded_units.push(unit.clone());
|
||||
+ for dep in &self.unit(&unit).info.requires {
|
||||
+ if !seen.insert(dep.clone()) {
|
||||
+ continue;
|
||||
+ }
|
||||
+ pending_units.push(dep.clone());
|
||||
+ }
|
||||
for dep in &self.unit(&unit).info.requires_weak {
|
||||
+ if !seen.insert(dep.clone()) {
|
||||
+ continue;
|
||||
+ }
|
||||
pending_units.push(dep.clone());
|
||||
}
|
||||
}
|
||||
@@ -125,6 +136,8 @@
|
||||
#[serde(default = "true_bool")]
|
||||
pub default_dependencies: bool,
|
||||
#[serde(default)]
|
||||
+ pub requires: Vec<UnitId>,
|
||||
+ #[serde(default)]
|
||||
pub requires_weak: Vec<UnitId>,
|
||||
pub condition_architecture: Option<Vec<String>>,
|
||||
// FIXME replace this with hwd reading from the devicetree
|
||||
@@ -191,6 +204,7 @@
|
||||
info: UnitInfo {
|
||||
description: None,
|
||||
default_dependencies: true,
|
||||
+ requires: script.2,
|
||||
requires_weak: script.1,
|
||||
condition_architecture: None,
|
||||
condition_board: None,
|
||||
@@ -1,3 +1,4 @@
|
||||
diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs
|
||||
--- a/drivers/acpid/src/aml_physmem.rs
|
||||
+++ b/drivers/acpid/src/aml_physmem.rs
|
||||
@@ -143,7 +143,7 @@
|
||||
@@ -48,165 +49,3 @@
|
||||
Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
--- a/drivers/acpid/src/acpi.rs
|
||||
+++ b/drivers/acpid/src/acpi.rs
|
||||
@@ -245,24 +245,26 @@
|
||||
symbol_cache: FxHashMap<String, String>,
|
||||
page_cache: Arc<Mutex<AmlPageCache>>,
|
||||
aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||
}
|
||||
|
||||
impl AmlSymbols {
|
||||
- pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>) -> Self {
|
||||
+ pub fn new(aml_region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler>)>, pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>) -> Self {
|
||||
Self {
|
||||
aml_context: None,
|
||||
symbol_cache: FxHashMap::default(),
|
||||
page_cache: Arc::new(Mutex::new(AmlPageCache::default())),
|
||||
aml_region_handlers,
|
||||
+ pci_fd,
|
||||
}
|
||||
}
|
||||
|
||||
- pub fn init(&mut self, pci_fd: Option<&libredox::Fd>) -> Result<(), Box<dyn Error>> {
|
||||
+ pub fn init(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
if self.aml_context.is_some() {
|
||||
return Err("AML interpreter already initialized".into());
|
||||
}
|
||||
let format_err = |err| format!("{:?}", err);
|
||||
- let handler = AmlPhysMemHandler::new(pci_fd, Arc::clone(&self.page_cache));
|
||||
+ let handler = AmlPhysMemHandler::new(Arc::clone(&self.pci_fd), Arc::clone(&self.page_cache));
|
||||
//TODO: use these parsed tables for the rest of acpid
|
||||
let rsdp_address = usize::from_str_radix(&std::env::var("RSDP_ADDR")?, 16)?;
|
||||
let tables =
|
||||
@@ -278,10 +280,9 @@
|
||||
|
||||
pub fn aml_context_mut(
|
||||
&mut self,
|
||||
- pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<&mut Interpreter<AmlPhysMemHandler>, AmlEvalError> {
|
||||
if self.aml_context.is_none() {
|
||||
- match self.init(pci_fd) {
|
||||
+ match self.init() {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
log::error!("failed to initialize AML context: {}", err);
|
||||
@@ -305,8 +306,8 @@
|
||||
None
|
||||
}
|
||||
|
||||
- pub fn build_cache(&mut self, pci_fd: Option<&libredox::Fd>) {
|
||||
- let Ok(aml_context) = self.aml_context_mut(pci_fd) else {
|
||||
+ pub fn build_cache(&mut self) {
|
||||
+ let Ok(aml_context) = self.aml_context_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -382,6 +383,8 @@
|
||||
|
||||
aml_symbols: RwLock<AmlSymbols>,
|
||||
|
||||
+ pub pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
|
||||
+
|
||||
// TODO: The kernel ACPI code seemed to use load_table quite ubiquitously, however ACPI 5.1
|
||||
// states that DDBHandles can only be obtained when loading XSDT-pointed tables. So, we'll
|
||||
// generate an index only for those.
|
||||
@@ -397,7 +400,7 @@
|
||||
args: Vec<AmlSerdeValue>,
|
||||
) -> Result<AmlSerdeValue, AmlEvalError> {
|
||||
let mut symbols = self.aml_symbols.write();
|
||||
- let interpreter = symbols.aml_context_mut(None)?;
|
||||
+ let interpreter = symbols.aml_context_mut()?;
|
||||
interpreter.acquire_global_lock(16)?;
|
||||
|
||||
let args = args
|
||||
@@ -440,13 +443,17 @@
|
||||
})
|
||||
.collect::<Vec<Sdt>>();
|
||||
|
||||
+ let pci_fd = Arc::new(parking_lot::RwLock::new(None));
|
||||
+
|
||||
let mut this = Self {
|
||||
tables,
|
||||
dsdt: None,
|
||||
fadt: None,
|
||||
|
||||
// Temporary values
|
||||
- aml_symbols: RwLock::new(AmlSymbols::new(ec)),
|
||||
+ aml_symbols: RwLock::new(AmlSymbols::new(ec, Arc::clone(&pci_fd))),
|
||||
+
|
||||
+ pci_fd,
|
||||
|
||||
next_ctx: RwLock::new(0),
|
||||
|
||||
@@ -526,7 +533,7 @@
|
||||
}
|
||||
|
||||
pub fn aml_lookup(&self, symbol: &str) -> Option<String> {
|
||||
- if let Ok(aml_symbols) = self.aml_symbols(None) {
|
||||
+ if let Ok(aml_symbols) = self.aml_symbols() {
|
||||
aml_symbols.lookup(symbol)
|
||||
} else {
|
||||
None
|
||||
@@ -535,7 +542,6 @@
|
||||
|
||||
pub fn aml_symbols(
|
||||
&self,
|
||||
- pci_fd: Option<&libredox::Fd>,
|
||||
) -> Result<RwLockReadGuard<'_, AmlSymbols>, AmlError> {
|
||||
// return the cached value if it exists
|
||||
let symbols = self.aml_symbols.read();
|
||||
@@ -550,7 +556,7 @@
|
||||
|
||||
let mut aml_symbols = self.aml_symbols.write();
|
||||
|
||||
- aml_symbols.build_cache(pci_fd);
|
||||
+ aml_symbols.build_cache();
|
||||
|
||||
// return the cached value
|
||||
Ok(RwLockWriteGuard::downgrade(aml_symbols))
|
||||
--- a/drivers/acpid/src/scheme.rs
|
||||
+++ b/drivers/acpid/src/scheme.rs
|
||||
@@ -26,7 +26,6 @@
|
||||
pub struct AcpiScheme<'acpi, 'sock> {
|
||||
ctx: &'acpi AcpiContext,
|
||||
handles: HandleMap<Handle<'acpi>>,
|
||||
- pci_fd: Option<Fd>,
|
||||
socket: &'sock Socket,
|
||||
}
|
||||
|
||||
@@ -77,7 +76,6 @@
|
||||
Self {
|
||||
ctx,
|
||||
handles: HandleMap::new(),
|
||||
- pci_fd: None,
|
||||
socket,
|
||||
}
|
||||
}
|
||||
@@ -204,7 +202,7 @@
|
||||
}
|
||||
|
||||
["symbols"] => {
|
||||
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
||||
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
|
||||
HandleKind::Symbols(aml_symbols)
|
||||
} else {
|
||||
return Err(Error::new(EIO));
|
||||
@@ -470,10 +468,12 @@
|
||||
}
|
||||
let new_fd = libredox::Fd::new(new_fd);
|
||||
|
||||
- if self.pci_fd.is_some() {
|
||||
- return Err(Error::new(EINVAL));
|
||||
- } else {
|
||||
- self.pci_fd = Some(new_fd);
|
||||
+ {
|
||||
+ let mut pci_fd = self.ctx.pci_fd.write();
|
||||
+ if pci_fd.is_some() {
|
||||
+ return Err(Error::new(EINVAL));
|
||||
+ }
|
||||
+ *pci_fd = Some(new_fd);
|
||||
}
|
||||
|
||||
Ok(num_fds)
|
||||
|
||||
@@ -0,0 +1,757 @@
|
||||
diff --git a/drivers/input/ps2d/src/controller.rs b/drivers/input/ps2d/src/controller.rs
|
||||
index d7af4cba..061ef2cf 100644
|
||||
--- a/drivers/input/ps2d/src/controller.rs
|
||||
+++ b/drivers/input/ps2d/src/controller.rs
|
||||
@@ -97,6 +97,14 @@ enum KeyboardCommandData {
|
||||
const DEFAULT_TIMEOUT: u64 = 50_000;
|
||||
// Reset timeout in microseconds
|
||||
const RESET_TIMEOUT: u64 = 1_000_000;
|
||||
+// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE)
|
||||
+const FLUSH_LIMIT: usize = 4096;
|
||||
+// Controller self-test pass value (Linux: I8042_RET_CTL_TEST)
|
||||
+const SELFTEST_PASS: u8 = 0x55;
|
||||
+// Controller self-test retries (Linux: 5 attempts)
|
||||
+const SELFTEST_RETRIES: usize = 5;
|
||||
+// AUX port test pass value (Linux returns 0x00 on success)
|
||||
+const AUX_TEST_PASS: u8 = 0x00;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub struct Ps2 {
|
||||
@@ -129,7 +137,15 @@ impl Ps2 {
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
pub fn new() -> Self {
|
||||
- unimplemented!()
|
||||
+ // PS/2 controller is x86-only hardware. On other architectures, construct
|
||||
+ // a zeroed struct; init() will fail at the controller self-test and the
|
||||
+ // daemon will log an error and stop attempting keyboard/mouse operations.
|
||||
+ Ps2 {
|
||||
+ data: Mmio::new(0),
|
||||
+ status: ReadOnly::new(Mmio::new(0)),
|
||||
+ command: WriteOnly::new(Mmio::new(0)),
|
||||
+ mouse_resets: 0,
|
||||
+ }
|
||||
}
|
||||
|
||||
fn status(&mut self) -> StatusFlags {
|
||||
@@ -261,6 +277,30 @@ impl Ps2 {
|
||||
self.write(command as u8)
|
||||
}
|
||||
|
||||
+ pub fn set_leds(&mut self, caps: bool, num: bool, scroll: bool) {
|
||||
+ let mut led_byte = 0u8;
|
||||
+ if scroll { led_byte |= 1; }
|
||||
+ if num { led_byte |= 2; }
|
||||
+ if caps { led_byte |= 4; }
|
||||
+ if let Err(err) = self.keyboard_command_inner(0xED) {
|
||||
+ log::debug!("ps2d: LED command 0xED not supported: {:?}", err);
|
||||
+ return;
|
||||
+ }
|
||||
+ match self.read_timeout(DEFAULT_TIMEOUT) {
|
||||
+ Ok(0xFA) => {
|
||||
+ if let Err(err) = self.write(led_byte) {
|
||||
+ log::debug!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err);
|
||||
+ }
|
||||
+ }
|
||||
+ Ok(val) => {
|
||||
+ log::debug!("ps2d: LED command ACK expected 0xFA, got {:02X}", val);
|
||||
+ }
|
||||
+ Err(err) => {
|
||||
+ log::debug!("ps2d: LED command ACK timeout: {:?}", err);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
pub fn next(&mut self) -> Option<(bool, u8)> {
|
||||
let status = self.status();
|
||||
if status.contains(StatusFlags::OUTPUT_FULL) {
|
||||
@@ -271,6 +311,50 @@ impl Ps2 {
|
||||
}
|
||||
}
|
||||
|
||||
+ /// Drain all pending bytes from the controller output buffer.
|
||||
+ /// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be
|
||||
+ /// misinterpreted as device responses during initialization.
|
||||
+ fn flush(&mut self) -> usize {
|
||||
+ let mut count = 0;
|
||||
+ while self.status().contains(StatusFlags::OUTPUT_FULL) {
|
||||
+ if count >= FLUSH_LIMIT {
|
||||
+ warn!("flush: exceeded limit, controller may be stuck");
|
||||
+ break;
|
||||
+ }
|
||||
+ let data = self.data.read();
|
||||
+ trace!("flush: discarded {:02X}", data);
|
||||
+ count += 1;
|
||||
+ }
|
||||
+ if count > 0 {
|
||||
+ debug!("flushed {} stale bytes from controller", count);
|
||||
+ }
|
||||
+ count
|
||||
+ }
|
||||
+
|
||||
+ /// Test the AUX (mouse) port via controller command 0xA9.
|
||||
+ /// Borrowed from Linux: verifies electrical connectivity before
|
||||
+ /// attempting to talk to the mouse. Returns true if the port passed.
|
||||
+ fn test_aux_port(&mut self) -> bool {
|
||||
+ if let Err(err) = self.command(Command::TestSecond) {
|
||||
+ warn!("aux port test command failed: {:?}", err);
|
||||
+ return false;
|
||||
+ }
|
||||
+ match self.read() {
|
||||
+ Ok(AUX_TEST_PASS) => {
|
||||
+ debug!("aux port test passed");
|
||||
+ true
|
||||
+ }
|
||||
+ Ok(val) => {
|
||||
+ warn!("aux port test failed: {:02X}", val);
|
||||
+ false
|
||||
+ }
|
||||
+ Err(err) => {
|
||||
+ warn!("aux port test read timeout: {:?}", err);
|
||||
+ false
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
pub fn init_keyboard(&mut self) -> Result<(), Error> {
|
||||
let mut b;
|
||||
|
||||
@@ -308,66 +392,125 @@ impl Ps2 {
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), Error> {
|
||||
+ // Linux i8042_controller_check(): verify controller is present by
|
||||
+ // flushing any stale data. A stuck output buffer means no controller.
|
||||
+ self.flush();
|
||||
+
|
||||
+ // Bare-metal controllers may be slow after firmware handoff.
|
||||
+ // Give the controller a moment to finish POST before sending commands.
|
||||
+ std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
+
|
||||
{
|
||||
- // Disable devices
|
||||
- self.command(Command::DisableFirst)?;
|
||||
- self.command(Command::DisableSecond)?;
|
||||
+ // Disable both ports first — use retry because the controller
|
||||
+ // may still be settling or temporarily unresponsive.
|
||||
+ // Failure here is non-fatal: we continue and attempt the rest
|
||||
+ // of initialization. A truly absent controller will fail later
|
||||
+ // at self-test or keyboard reset.
|
||||
+ if let Err(err) = self.retry(
|
||||
+ format_args!("disable first port"),
|
||||
+ 3,
|
||||
+ |x| x.command(Command::DisableFirst),
|
||||
+ ) {
|
||||
+ warn!("disable first port failed: {:?}", err);
|
||||
+ }
|
||||
+ if let Err(err) = self.retry(
|
||||
+ format_args!("disable second port"),
|
||||
+ 3,
|
||||
+ |x| x.command(Command::DisableSecond),
|
||||
+ ) {
|
||||
+ warn!("disable second port failed: {:?}", err);
|
||||
+ }
|
||||
}
|
||||
|
||||
- // Disable clocks, disable interrupts, and disable translate
|
||||
+ // Flush again after disabling — firmware may have queued more bytes
|
||||
+ self.flush();
|
||||
+
|
||||
+ // Linux i8042_controller_init() step 1: write a known-safe config
|
||||
+ // (interrupts off, both ports disabled) so stale config can't cause
|
||||
+ // spurious interrupts during the rest of init.
|
||||
{
|
||||
- // Since the default config may have interrupts enabled, and the kernel may eat up
|
||||
- // our data in that case, we will write a config without reading the current one
|
||||
let config = ConfigFlags::POST_PASSED
|
||||
| ConfigFlags::FIRST_DISABLED
|
||||
| ConfigFlags::SECOND_DISABLED;
|
||||
self.set_config(config)?;
|
||||
}
|
||||
|
||||
- // The keyboard seems to still collect bytes even when we disable
|
||||
- // the port, so we must disable the keyboard too
|
||||
+ // Linux i8042_controller_selftest(): retry up to 5 times with delay.
|
||||
+ // "On some really fragile systems this does not take the first time."
|
||||
+ {
|
||||
+ let mut passed = false;
|
||||
+ for attempt in 0..SELFTEST_RETRIES {
|
||||
+ if let Err(err) = self.command(Command::TestController) {
|
||||
+ warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err);
|
||||
+ continue;
|
||||
+ }
|
||||
+ match self.read() {
|
||||
+ Ok(SELFTEST_PASS) => {
|
||||
+ passed = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ Ok(val) => {
|
||||
+ warn!(
|
||||
+ "self-test unexpected value {:02X} (attempt {}/{})",
|
||||
+ val,
|
||||
+ attempt + 1,
|
||||
+ SELFTEST_RETRIES
|
||||
+ );
|
||||
+ }
|
||||
+ Err(err) => {
|
||||
+ warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err);
|
||||
+ }
|
||||
+ }
|
||||
+ // Linux: msleep(50) between retries
|
||||
+ std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
+ }
|
||||
+ if !passed {
|
||||
+ // Linux on x86: "giving up on controller selftest, continuing anyway"
|
||||
+ warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Flush any bytes the self-test may have left behind
|
||||
+ self.flush();
|
||||
+
|
||||
+ // Linux i8042_controller_init() step 2: set keyboard defaults
|
||||
+ // (disable scanning so keyboard doesn't send scancodes during init)
|
||||
self.retry(format_args!("keyboard defaults"), 4, |x| {
|
||||
- // Set defaults and disable scanning
|
||||
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
|
||||
if b != 0xFA {
|
||||
error!("keyboard failed to set defaults: {:02X}", b);
|
||||
return Err(Error::CommandRetry);
|
||||
}
|
||||
-
|
||||
Ok(b)
|
||||
})?;
|
||||
|
||||
- {
|
||||
- // Perform the self test
|
||||
- self.command(Command::TestController)?;
|
||||
- let r = self.read()?;
|
||||
- if r != 0x55 {
|
||||
- warn!("self test unexpected value: {:02X}", r);
|
||||
- }
|
||||
- }
|
||||
-
|
||||
// Initialize keyboard
|
||||
if let Err(err) = self.init_keyboard() {
|
||||
error!("failed to initialize keyboard: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
- // Enable second device
|
||||
- let enable_mouse = match self.command(Command::EnableSecond) {
|
||||
- Ok(()) => true,
|
||||
- Err(err) => {
|
||||
- error!("failed to initialize mouse: {:?}", err);
|
||||
- false
|
||||
+ // Linux: test AUX port (command 0xA9) before enabling.
|
||||
+ // Skips mouse init entirely if the port is not electrically present.
|
||||
+ let aux_ok = self.test_aux_port();
|
||||
+
|
||||
+ // Enable second device (mouse) only if AUX port tested OK
|
||||
+ let enable_mouse = if aux_ok {
|
||||
+ match self.command(Command::EnableSecond) {
|
||||
+ Ok(()) => true,
|
||||
+ Err(err) => {
|
||||
+ warn!("failed to enable aux port after test passed: {:?}", err);
|
||||
+ false
|
||||
+ }
|
||||
}
|
||||
+ } else {
|
||||
+ info!("skipping mouse init: aux port test did not pass");
|
||||
+ false
|
||||
};
|
||||
|
||||
{
|
||||
- // Enable keyboard data reporting
|
||||
- // Use inner function to prevent retries
|
||||
- // Response is ignored since scanning is now on
|
||||
if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) {
|
||||
error!("failed to initialize keyboard reporting: {:?}", err);
|
||||
- //TODO: fix by using interrupts?
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/drivers/input/ps2d/src/main.rs b/drivers/input/ps2d/src/main.rs
|
||||
index db17de2a..86f903bf 100644
|
||||
--- a/drivers/input/ps2d/src/main.rs
|
||||
+++ b/drivers/input/ps2d/src/main.rs
|
||||
@@ -11,7 +11,7 @@ use std::process;
|
||||
|
||||
use common::acquire_port_io_rights;
|
||||
use event::{user_data, EventQueue};
|
||||
-use inputd::ProducerHandle;
|
||||
+use inputd::InputProducer;
|
||||
|
||||
use crate::state::Ps2d;
|
||||
|
||||
@@ -31,7 +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
|
||||
acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
|
||||
|
||||
- let input = ProducerHandle::new().expect("ps2d: failed to open input producer");
|
||||
+ let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input");
|
||||
+ let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input");
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
@@ -93,7 +94,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
|
||||
daemon.ready();
|
||||
|
||||
- let mut ps2d = Ps2d::new(input, time_file);
|
||||
+ let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file);
|
||||
|
||||
let mut data = [0; 256];
|
||||
for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
|
||||
diff --git a/drivers/input/ps2d/src/mouse.rs b/drivers/input/ps2d/src/mouse.rs
|
||||
index 9e95ab88..23099493 100644
|
||||
--- a/drivers/input/ps2d/src/mouse.rs
|
||||
+++ b/drivers/input/ps2d/src/mouse.rs
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::controller::Ps2;
|
||||
use std::time::Duration;
|
||||
|
||||
-pub const RESET_RETRIES: usize = 10;
|
||||
-pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
+pub const RESET_RETRIES: usize = 3;
|
||||
+pub const RESET_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -61,6 +61,10 @@ impl MouseTx {
|
||||
if data == 0xFA {
|
||||
self.write_i += 1;
|
||||
self.try_write(ps2)?;
|
||||
+ } else if data == 0xFE {
|
||||
+ // PS/2 RESEND: mouse asks us to resend the current command byte
|
||||
+ log::debug!("mouse requested resend for byte {:02X}, resending", self.write.get(self.write_i).unwrap_or(&0));
|
||||
+ self.try_write(ps2)?;
|
||||
} else {
|
||||
log::error!("unknown mouse response {:02X}", data);
|
||||
return Err(());
|
||||
@@ -80,8 +84,7 @@ enum MouseId {
|
||||
Base = 0x00,
|
||||
/// Mouse sends fourth byte with scroll
|
||||
Intellimouse1 = 0x03,
|
||||
- /// Mouse sends fourth byte with scroll, button 4, and button 5
|
||||
- //TODO: support this mouse type
|
||||
+ /// Mouse sends fourth byte with scroll and buttons 4/5
|
||||
Intellimouse2 = 0x04,
|
||||
}
|
||||
|
||||
@@ -94,25 +97,16 @@ pub enum TouchpadCommand {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MouseState {
|
||||
- /// No mouse found
|
||||
None,
|
||||
- /// Ready to initialize mouse
|
||||
Init,
|
||||
- /// Reset command is sent
|
||||
Reset,
|
||||
- /// BAT completion code returned
|
||||
Bat,
|
||||
- /// Identify touchpad
|
||||
IdentifyTouchpad { tx: MouseTx },
|
||||
- /// Enable intellimouse features
|
||||
EnableIntellimouse { tx: MouseTx },
|
||||
- /// Status request
|
||||
+ EnableIntellimouse2 { tx: MouseTx },
|
||||
Status { index: usize },
|
||||
- /// Device ID update
|
||||
DeviceId,
|
||||
- /// Enable reporting command sent
|
||||
EnableReporting { id: u8 },
|
||||
- /// Mouse is streaming
|
||||
Streaming { id: u8 },
|
||||
}
|
||||
|
||||
@@ -194,9 +188,7 @@ impl MouseState {
|
||||
let cmd = TouchpadCommand::Identify as u8;
|
||||
match MouseTx::new(
|
||||
&[
|
||||
- // Ensure command alignment
|
||||
MouseCommand::SetScaling1To1 as u8,
|
||||
- // Send special identify touchpad command
|
||||
MouseCommandData::SetResolution as u8,
|
||||
0,
|
||||
MouseCommandData::SetResolution as u8,
|
||||
@@ -205,7 +197,6 @@ impl MouseState {
|
||||
0,
|
||||
MouseCommandData::SetResolution as u8,
|
||||
0,
|
||||
- // Status request
|
||||
MouseCommand::StatusRequest as u8,
|
||||
],
|
||||
3,
|
||||
@@ -215,7 +206,7 @@ impl MouseState {
|
||||
*self = MouseState::IdentifyTouchpad { tx };
|
||||
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
}
|
||||
- Err(()) => self.enable_intellimouse(ps2),
|
||||
+ Err(()) => self.enable_intellimouse2(ps2),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +231,27 @@ impl MouseState {
|
||||
}
|
||||
}
|
||||
|
||||
+ fn enable_intellimouse2(&mut self, ps2: &mut Ps2) -> MouseResult {
|
||||
+ match MouseTx::new(
|
||||
+ &[
|
||||
+ MouseCommandData::SetSampleRate as u8,
|
||||
+ 200,
|
||||
+ MouseCommandData::SetSampleRate as u8,
|
||||
+ 200,
|
||||
+ MouseCommandData::SetSampleRate as u8,
|
||||
+ 80,
|
||||
+ ],
|
||||
+ 0,
|
||||
+ ps2,
|
||||
+ ) {
|
||||
+ Ok(tx) => {
|
||||
+ *self = MouseState::EnableIntellimouse2 { tx };
|
||||
+ MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
+ }
|
||||
+ Err(()) => self.enable_intellimouse(ps2),
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
pub fn handle(&mut self, data: u8, ps2: &mut Ps2) -> MouseResult {
|
||||
match *self {
|
||||
MouseState::None | MouseState::Init => {
|
||||
@@ -260,17 +272,22 @@ impl MouseState {
|
||||
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
} else {
|
||||
log::warn!("unknown mouse response {:02X} after reset", data);
|
||||
- self.reset(ps2)
|
||||
+ *self = MouseState::None;
|
||||
+ MouseResult::None
|
||||
}
|
||||
}
|
||||
MouseState::Bat => {
|
||||
if data == MouseId::Base as u8 {
|
||||
- // Enable intellimouse features
|
||||
+ // Base mouse - enable intellimouse features
|
||||
log::debug!("BAT mouse id {:02X} (base)", data);
|
||||
self.identify_touchpad(ps2)
|
||||
} else if data == MouseId::Intellimouse1 as u8 {
|
||||
- // Extra packet already enabled
|
||||
- log::debug!("BAT mouse id {:02X} (intellimouse)", data);
|
||||
+ // Scroll wheel already enabled
|
||||
+ log::debug!("BAT mouse id {:02X} (intellimouse1)", data);
|
||||
+ self.enable_reporting(data, ps2)
|
||||
+ } else if data == MouseId::Intellimouse2 as u8 {
|
||||
+ // Scroll wheel + buttons 4/5 already enabled
|
||||
+ log::debug!("BAT mouse id {:02X} (intellimouse2)", data);
|
||||
self.enable_reporting(data, ps2)
|
||||
} else {
|
||||
log::warn!("unknown mouse id {:02X} after BAT", data);
|
||||
@@ -291,7 +308,17 @@ impl MouseState {
|
||||
Err(()) => self.enable_intellimouse(ps2),
|
||||
}
|
||||
}
|
||||
- MouseState::EnableIntellimouse { ref mut tx } => match tx.handle(data, ps2) {
|
||||
+MouseState::EnableIntellimouse { ref mut tx } => match tx.handle(data, ps2) {
|
||||
+ Ok(done) => {
|
||||
+ if done {
|
||||
+ self.request_status(ps2)
|
||||
+ } else {
|
||||
+ MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
+ }
|
||||
+ }
|
||||
+ Err(()) => self.request_id(ps2),
|
||||
+ },
|
||||
+ MouseState::EnableIntellimouse2 { ref mut tx } => match tx.handle(data, ps2) {
|
||||
Ok(done) => {
|
||||
if done {
|
||||
self.request_status(ps2)
|
||||
@@ -299,7 +326,7 @@ impl MouseState {
|
||||
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
}
|
||||
}
|
||||
- Err(()) => self.request_status(ps2),
|
||||
+ Err(()) => self.enable_intellimouse(ps2),
|
||||
},
|
||||
MouseState::Status { index } => {
|
||||
match index {
|
||||
@@ -324,7 +351,7 @@ impl MouseState {
|
||||
// Command OK response
|
||||
//TODO: handle this separately?
|
||||
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
- } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 {
|
||||
+ } else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 || data == MouseId::Intellimouse2 as u8 {
|
||||
log::debug!("mouse id {:02X}", data);
|
||||
self.enable_reporting(data, ps2)
|
||||
} else {
|
||||
@@ -339,11 +366,15 @@ impl MouseState {
|
||||
MouseResult::None
|
||||
}
|
||||
MouseState::Streaming { id } => {
|
||||
- MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8)
|
||||
+ MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8 || id == MouseId::Intellimouse2 as u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ pub fn streaming_is_intellimouse2(&self) -> bool {
|
||||
+ matches!(self, MouseState::Streaming { id } if *id == MouseId::Intellimouse2 as u8)
|
||||
+ }
|
||||
+
|
||||
pub fn handle_timeout(&mut self, ps2: &mut Ps2) -> MouseResult {
|
||||
match *self {
|
||||
MouseState::None | MouseState::Streaming { .. } => MouseResult::None,
|
||||
@@ -352,12 +383,14 @@ impl MouseState {
|
||||
self.reset(ps2)
|
||||
}
|
||||
MouseState::Reset => {
|
||||
- log::warn!("timeout waiting for mouse reset");
|
||||
- self.reset(ps2)
|
||||
+ log::debug!("timeout waiting for mouse reset, fast-failing");
|
||||
+ *self = MouseState::None;
|
||||
+ MouseResult::None
|
||||
}
|
||||
MouseState::Bat => {
|
||||
- log::warn!("timeout waiting for BAT completion");
|
||||
- self.reset(ps2)
|
||||
+ log::debug!("timeout waiting for BAT completion, fast-failing");
|
||||
+ *self = MouseState::None;
|
||||
+ MouseResult::None
|
||||
}
|
||||
MouseState::IdentifyTouchpad { .. } => {
|
||||
//TODO: retry?
|
||||
@@ -365,10 +398,13 @@ impl MouseState {
|
||||
self.request_status(ps2)
|
||||
}
|
||||
MouseState::EnableIntellimouse { .. } => {
|
||||
- //TODO: retry?
|
||||
log::warn!("timeout enabling intellimouse");
|
||||
self.request_status(ps2)
|
||||
}
|
||||
+ MouseState::EnableIntellimouse2 { .. } => {
|
||||
+ log::warn!("timeout enabling intellimouse2, falling back to intellimouse");
|
||||
+ self.enable_intellimouse(ps2)
|
||||
+ }
|
||||
MouseState::Status { index } => {
|
||||
log::warn!("timeout waiting for mouse status {}", index);
|
||||
self.request_id(ps2)
|
||||
diff --git a/drivers/input/ps2d/src/state.rs b/drivers/input/ps2d/src/state.rs
|
||||
index 9018dc6b..8f5832f6 100644
|
||||
--- a/drivers/input/ps2d/src/state.rs
|
||||
+++ b/drivers/input/ps2d/src/state.rs
|
||||
@@ -1,4 +1,4 @@
|
||||
-use inputd::ProducerHandle;
|
||||
+use inputd::InputProducer;
|
||||
use log::{error, warn};
|
||||
use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent};
|
||||
use std::{
|
||||
@@ -44,7 +44,8 @@ pub struct Ps2d {
|
||||
ps2: Ps2,
|
||||
vmmouse: bool,
|
||||
vmmouse_relative: bool,
|
||||
- input: ProducerHandle,
|
||||
+ keyboard_input: InputProducer,
|
||||
+ mouse_input: InputProducer,
|
||||
time_file: File,
|
||||
extended: bool,
|
||||
mouse_x: i32,
|
||||
@@ -52,16 +53,24 @@ pub struct Ps2d {
|
||||
mouse_left: bool,
|
||||
mouse_middle: bool,
|
||||
mouse_right: bool,
|
||||
+ mouse_button_4: bool,
|
||||
+ mouse_button_5: bool,
|
||||
mouse_state: MouseState,
|
||||
mouse_timeout: Option<TimeSpec>,
|
||||
packets: [u8; 4],
|
||||
packet_i: usize,
|
||||
+ caps_lock: bool,
|
||||
+ num_lock: bool,
|
||||
+ scroll_lock: bool,
|
||||
+ leds_dirty: bool,
|
||||
}
|
||||
|
||||
impl Ps2d {
|
||||
- pub fn new(input: ProducerHandle, time_file: File) -> Self {
|
||||
+ pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self {
|
||||
let mut ps2 = Ps2::new();
|
||||
- ps2.init().expect("failed to initialize");
|
||||
+ if let Err(err) = ps2.init() {
|
||||
+ log::error!("ps2d: controller init failed: {:?}", err);
|
||||
+ }
|
||||
|
||||
// FIXME add an option for orbital to disable this when an app captures the mouse.
|
||||
let vmmouse_relative = false;
|
||||
@@ -77,7 +86,8 @@ impl Ps2d {
|
||||
ps2,
|
||||
vmmouse,
|
||||
vmmouse_relative,
|
||||
- input,
|
||||
+ keyboard_input,
|
||||
+ mouse_input,
|
||||
time_file,
|
||||
extended: false,
|
||||
mouse_x: 0,
|
||||
@@ -85,10 +95,16 @@ impl Ps2d {
|
||||
mouse_left: false,
|
||||
mouse_middle: false,
|
||||
mouse_right: false,
|
||||
+ mouse_button_4: false,
|
||||
+ mouse_button_5: false,
|
||||
mouse_state: MouseState::Init,
|
||||
mouse_timeout: None,
|
||||
packets: [0; 4],
|
||||
packet_i: 0,
|
||||
+ caps_lock: false,
|
||||
+ num_lock: true,
|
||||
+ scroll_lock: false,
|
||||
+ leds_dirty: true,
|
||||
};
|
||||
|
||||
if !this.vmmouse {
|
||||
@@ -96,6 +112,12 @@ impl Ps2d {
|
||||
this.handle_mouse(None);
|
||||
}
|
||||
|
||||
+ // Flush initial LED state (Num Lock on by default)
|
||||
+ if this.leds_dirty {
|
||||
+ this.leds_dirty = false;
|
||||
+ this.ps2.set_leds(this.caps_lock, this.num_lock, this.scroll_lock);
|
||||
+ }
|
||||
+
|
||||
this
|
||||
}
|
||||
|
||||
@@ -272,8 +294,21 @@ impl Ps2d {
|
||||
}
|
||||
};
|
||||
|
||||
+ if scancode != 0 && pressed {
|
||||
+ match scancode {
|
||||
+ orbclient::K_CAPS => { self.caps_lock = !self.caps_lock; self.leds_dirty = true; },
|
||||
+ orbclient::K_NUM => { self.num_lock = !self.num_lock; self.leds_dirty = true; },
|
||||
+ orbclient::K_SCROLL => { self.scroll_lock = !self.scroll_lock; self.leds_dirty = true; },
|
||||
+ _ => (),
|
||||
+ }
|
||||
+ }
|
||||
+ if self.leds_dirty {
|
||||
+ self.leds_dirty = false;
|
||||
+ self.ps2.set_leds(self.caps_lock, self.num_lock, self.scroll_lock);
|
||||
+ }
|
||||
+
|
||||
if scancode != 0 {
|
||||
- self.input
|
||||
+ self.keyboard_input
|
||||
.write_event(
|
||||
KeyEvent {
|
||||
character: '\0',
|
||||
@@ -304,7 +339,7 @@ impl Ps2d {
|
||||
|
||||
if self.vmmouse_relative {
|
||||
if dx != 0 || dy != 0 {
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(
|
||||
MouseRelativeEvent {
|
||||
dx: dx as i32,
|
||||
@@ -320,14 +355,14 @@ impl Ps2d {
|
||||
if x != self.mouse_x || y != self.mouse_y {
|
||||
self.mouse_x = x;
|
||||
self.mouse_y = y;
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(MouseEvent { x, y }.to_event())
|
||||
.expect("ps2d: failed to write mouse event");
|
||||
}
|
||||
};
|
||||
|
||||
if dz != 0 {
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(
|
||||
ScrollEvent {
|
||||
x: 0,
|
||||
@@ -348,7 +383,7 @@ impl Ps2d {
|
||||
self.mouse_left = left;
|
||||
self.mouse_middle = middle;
|
||||
self.mouse_right = right;
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(
|
||||
ButtonEvent {
|
||||
left,
|
||||
@@ -432,22 +467,35 @@ impl Ps2d {
|
||||
}
|
||||
|
||||
let mut dz = 0;
|
||||
+ let mut button_4 = false;
|
||||
+ let mut button_5 = false;
|
||||
if extra_packet {
|
||||
- let mut scroll = (self.packets[3] & 0xF) as i8;
|
||||
- if scroll & (1 << 3) == 1 << 3 {
|
||||
- scroll -= 16;
|
||||
+ let fourth = self.packets[3];
|
||||
+ if self.mouse_state.streaming_is_intellimouse2() {
|
||||
+ let mut scroll = (fourth & 0x0F) as i8;
|
||||
+ if scroll & 0x08 != 0 {
|
||||
+ scroll -= 16;
|
||||
+ }
|
||||
+ dz = -(scroll as i32);
|
||||
+ button_4 = (fourth & 0x10) != 0;
|
||||
+ button_5 = (fourth & 0x20) != 0;
|
||||
+ } else {
|
||||
+ let mut scroll = (fourth & 0xF) as i8;
|
||||
+ if scroll & (1 << 3) == 1 << 3 {
|
||||
+ scroll -= 16;
|
||||
+ }
|
||||
+ dz = -scroll as i32;
|
||||
}
|
||||
- dz = -scroll as i32;
|
||||
}
|
||||
|
||||
if dx != 0 || dy != 0 {
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(MouseRelativeEvent { dx, dy }.to_event())
|
||||
.expect("ps2d: failed to write mouse event");
|
||||
}
|
||||
|
||||
if dz != 0 {
|
||||
- self.input
|
||||
+ self.mouse_input
|
||||
.write_event(ScrollEvent { x: 0, y: dz }.to_event())
|
||||
.expect("ps2d: failed to write scroll event");
|
||||
}
|
||||
@@ -458,11 +506,15 @@ impl Ps2d {
|
||||
if left != self.mouse_left
|
||||
|| middle != self.mouse_middle
|
||||
|| right != self.mouse_right
|
||||
+ || button_4 != self.mouse_button_4
|
||||
+ || button_5 != self.mouse_button_5
|
||||
{
|
||||
self.mouse_left = left;
|
||||
self.mouse_middle = middle;
|
||||
self.mouse_right = right;
|
||||
- self.input
|
||||
+ self.mouse_button_4 = button_4;
|
||||
+ self.mouse_button_5 = button_5;
|
||||
+ self.mouse_input
|
||||
.write_event(
|
||||
ButtonEvent {
|
||||
left,
|
||||
diff --git a/drivers/input/ps2d/src/vm.rs b/drivers/input/ps2d/src/vm.rs
|
||||
index 71b71417..769a78e9 100644
|
||||
--- a/drivers/input/ps2d/src/vm.rs
|
||||
+++ b/drivers/input/ps2d/src/vm.rs
|
||||
@@ -64,8 +64,8 @@ pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
-pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
|
||||
- unimplemented!()
|
||||
+pub unsafe fn cmd(_cmd: u32, _arg: u32) -> (u32, u32, u32, u32) {
|
||||
+ (0, 0, 0, 0)
|
||||
}
|
||||
|
||||
pub fn enable(relative: bool) -> bool {
|
||||
@@ -1,49 +1,7 @@
|
||||
diff --git a/src/acpi/madt/arch/x86.rs b/src/acpi/madt/arch/x86.rs
|
||||
--- a/src/acpi/madt/arch/x86.rs
|
||||
+++ b/src/acpi/madt/arch/x86.rs
|
||||
@@ -446,7 +446,11 @@
|
||||
// Send INIT IPI (Assert)
|
||||
{
|
||||
let mut icr = 0x4500u64;
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
+ if local_apic.x2 {
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
+ } else {
|
||||
+ icr |= u64::from(apic_id as u8) << 56;
|
||||
+ }
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
@@ -456,7 +460,11 @@
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
+ if local_apic.x2 {
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
+ } else {
|
||||
+ icr |= u64::from(apic_id as u8) << 56;
|
||||
+ }
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
@@ -468,7 +476,11 @@
|
||||
{
|
||||
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
|
||||
let mut icr = 0x0600u64 | ap_segment as u64;
|
||||
- icr |= u64::from(apic_id) << 32;
|
||||
+ if local_apic.x2 {
|
||||
+ icr |= u64::from(apic_id) << 32;
|
||||
+ } else {
|
||||
+ icr |= u64::from(apic_id as u8) << 56;
|
||||
+ }
|
||||
local_apic.set_icr(icr);
|
||||
}
|
||||
|
||||
diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/device/local_apic.rs
|
||||
--- a/src/arch/x86_shared/device/local_apic.rs
|
||||
+++ b/src/arch/x86_shared/device/local_apic.rs
|
||||
@@ -61,9 +61,9 @@
|
||||
@@ -59,10 +59,10 @@
|
||||
.is_some_and(|feature_info| feature_info.has_x2apic());
|
||||
|
||||
if !self.x2 {
|
||||
- debug!("Detected xAPIC at {:#x}", physaddr.data());
|
||||
@@ -54,3 +12,4 @@ diff --git a/src/arch/x86_shared/device/local_apic.rs b/src/arch/x86_shared/devi
|
||||
+ info!("Detected x2APIC");
|
||||
}
|
||||
|
||||
self.init_ap();
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
--- a/src/acpi/madt/arch/x86.rs
|
||||
+++ b/src/acpi/madt/arch/x86.rs
|
||||
@@ -189,8 +189,18 @@
|
||||
let preliminary_cpu_count = madt
|
||||
.iter()
|
||||
.filter(|entry| match entry {
|
||||
- MadtEntry::LocalApic(local) => u32::from(local.id) == me.get() || local.flags & 1 == 1,
|
||||
- MadtEntry::LocalX2Apic(local) => local.x2apic_id == me.get() || local.flags & 1 == 1,
|
||||
+ // When x2APIC is active, LocalApic entries use 8-bit IDs that don't
|
||||
+ // match the BSP's 32-bit x2APIC ID. Use LocalX2Apic entries instead.
|
||||
+ MadtEntry::LocalApic(local) if !local_apic.x2 => {
|
||||
+ u32::from(local.id) == me.get() || local.flags & 1 == 1
|
||||
+ }
|
||||
+ MadtEntry::LocalApic(_) => false,
|
||||
+ // xAPIC mode: cannot use 32-bit x2APIC IDs via 8-bit ICR.
|
||||
+ // Skip LocalX2Apic entries and use LocalApic exclusively.
|
||||
+ MadtEntry::LocalX2Apic(local) if local_apic.x2 => {
|
||||
+ local.x2apic_id == me.get() || local.flags & 1 == 1
|
||||
+ }
|
||||
+ MadtEntry::LocalX2Apic(_) => false,
|
||||
_ => false,
|
||||
})
|
||||
.count();
|
||||
@@ -205,18 +215,28 @@
|
||||
let _ = seen_apic_ids.insert(me.get()); // BSP
|
||||
for entry in madt.iter() {
|
||||
match entry {
|
||||
- MadtEntry::LocalApic(local) if local.flags & 1 == 1 => {
|
||||
+ MadtEntry::LocalApic(local) if local.flags & 1 == 1 && !local_apic.x2 => {
|
||||
let id = u32::from(local.id);
|
||||
if !seen_apic_ids.insert(id) {
|
||||
warn!("MADT: duplicate APIC ID {} in LocalApic entry, firmware bug", id);
|
||||
}
|
||||
}
|
||||
- MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 => {
|
||||
+ MadtEntry::LocalApic(local) if local.flags & 1 == 1 && local_apic.x2 => {
|
||||
+ // x2APIC mode: skip 8-bit LocalApic IDs; they conflict with
|
||||
+ // 32-bit x2APIC IDs. Dedup only among LocalX2Apic entries.
|
||||
+ debug!("MADT: ignoring 8-bit LocalApic ID {} in x2APIC mode", local.id);
|
||||
+ }
|
||||
+ MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 && local_apic.x2 => {
|
||||
let id = local.x2apic_id;
|
||||
if !seen_apic_ids.insert(id) {
|
||||
warn!("MADT: duplicate x2APIC ID {} in LocalX2Apic entry, firmware bug", id);
|
||||
}
|
||||
}
|
||||
+ MadtEntry::LocalX2Apic(local) if local.flags & 1 == 1 && !local_apic.x2 => {
|
||||
+ // xAPIC mode: skip 32-bit x2APIC IDs; dedup only among LocalApic entries.
|
||||
+ let id = local.x2apic_id; // Copy from packed struct
|
||||
+ debug!("MADT: ignoring 32-bit x2APIC ID {} in xAPIC mode", id);
|
||||
+ }
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +245,16 @@
|
||||
for madt_entry in madt.iter() {
|
||||
debug!(" {:x?}", madt_entry);
|
||||
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
|
||||
- if u32::from(ap_local_apic.id) == me.get() {
|
||||
+ // x2APIC mode: LocalApic entries have 8-bit IDs that don't match
|
||||
+ // the BSP's 32-bit x2APIC ID. All entries would be treated as APs,
|
||||
+ // and SIPI would target the wrong processors. Skip them and rely
|
||||
+ // on LocalX2Apic entries exclusively.
|
||||
+ if local_apic.x2 {
|
||||
+ debug!(
|
||||
+ " Skipping 8-bit LocalApic id={} (x2APIC active, using LocalX2Apic entries)",
|
||||
+ ap_local_apic.id
|
||||
+ );
|
||||
+ } else if u32::from(ap_local_apic.id) == me.get() {
|
||||
debug!(" This is my local APIC");
|
||||
} else if ap_local_apic.flags & 1 == 1 {
|
||||
// Allocate a stack
|
||||
@@ -388,7 +417,14 @@
|
||||
let apic_id = ap_x2apic.x2apic_id;
|
||||
let flags = ap_x2apic.flags;
|
||||
|
||||
- if apic_id == me.get() {
|
||||
+ // xAPIC mode: cannot target 32-bit x2APIC IDs via 8-bit ICR.
|
||||
+ // Skip LocalX2Apic entries; use LocalApic entries exclusively.
|
||||
+ if !local_apic.x2 {
|
||||
+ debug!(
|
||||
+ " Skipping 32-bit x2APIC id={} (xAPIC mode, using LocalApic entries)",
|
||||
+ apic_id
|
||||
+ );
|
||||
+ } else if apic_id == me.get() {
|
||||
debug!(" This is my local x2APIC");
|
||||
} else if flags & 1 == 1 {
|
||||
let alloc = match allocate_p2frame(4) {
|
||||
@@ -299,7 +299,11 @@ impl Bus for AcpiBus {
|
||||
});
|
||||
}
|
||||
|
||||
log::info!("acpi bus: enumerated {} device(s)", devices.len());
|
||||
if devices.is_empty() {
|
||||
log::debug!("acpi bus: enumerated {} device(s)", devices.len());
|
||||
} else {
|
||||
log::info!("acpi bus: enumerated {} device(s)", devices.len());
|
||||
}
|
||||
Ok(devices)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ fn extract_package_contents(ron: &str) -> Vec<String> {
|
||||
if end_pos.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
let inner = &inner[..end_pos.unwrap()];
|
||||
let inner = &inner[..end_pos.expect("find_matching_bracket returned Some after is_none check")];
|
||||
|
||||
// Split into sub-packages by finding balanced "Package(...)" blocks
|
||||
split_sub_packages(inner)
|
||||
@@ -205,7 +205,7 @@ fn split_sub_packages(inner: &str) -> Vec<String> {
|
||||
if paren_start.is_none() {
|
||||
break;
|
||||
}
|
||||
let abs_paren = pos + paren_start.unwrap();
|
||||
let abs_paren = pos + paren_start.expect("find returned Some after is_none check");
|
||||
|
||||
// Find matching closing ')'
|
||||
let mut depth = 1;
|
||||
@@ -311,7 +311,7 @@ fn extract_package_elements(pkg: &str) -> Vec<String> {
|
||||
if end.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
let inner = &inner[..end.unwrap()];
|
||||
let inner = &inner[..end.expect("find_matching_bracket returned Some after is_none check")];
|
||||
|
||||
// Split by commas at depth 0
|
||||
let mut elements = Vec::new();
|
||||
|
||||
@@ -11,12 +11,16 @@ use redox_scheme::Socket;
|
||||
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
|
||||
|
||||
unsafe fn get_fd(var: &str) -> Option<RawFd> {
|
||||
// Env vars like INIT_NOTIFY are optional — daemons not spawned by init
|
||||
// simply don't have them. Return None silently instead of spewing errors.
|
||||
let fd: RawFd = match std::env::var(var)
|
||||
.map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
|
||||
.ok()
|
||||
.and_then(|val| {
|
||||
val.parse()
|
||||
.map_err(|e| eprintln!("daemon: failed to parse {var} as fd: {e}"))
|
||||
val.parse::<RawFd>()
|
||||
.map_err(|e| {
|
||||
eprintln!("daemon: failed to parse {var} as fd: {e}");
|
||||
e
|
||||
})
|
||||
.ok()
|
||||
}) {
|
||||
Some(fd) => fd,
|
||||
|
||||
@@ -172,8 +172,8 @@ impl IntelDisplay {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
debug!("redox-drm: Intel AUX/DPCD skeleton read on port {}", port);
|
||||
vec![0x12, 0x0A, 0x84, 0x01]
|
||||
debug!("redox-drm: Intel DPCD not yet implemented for port {}", port);
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn set_mode(&self, pipe: &DisplayPipe, mode: &ModeInfo) -> Result<()> {
|
||||
|
||||
@@ -3,7 +3,6 @@ pub mod gtt;
|
||||
pub mod ring;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use log::{debug, info, warn};
|
||||
@@ -27,6 +26,8 @@ const FORCEWAKE: usize = 0xA18C;
|
||||
const PP_STATUS: usize = 0xC7200;
|
||||
const PIPECONF_BASE: usize = 0x70008;
|
||||
const PIPE_STRIDE: usize = 0x1000;
|
||||
const PIPEFRAME_REG: usize = 0x70040;
|
||||
const PIPEFRAME_COUNT_MASK: u32 = 0x00FFFFFF;
|
||||
const DDI_BUF_CTL_BASE: usize = 0x64000;
|
||||
const DDI_PORT_STRIDE: usize = 0x100;
|
||||
const GFX_FLSH_CNTL_REG: usize = 0x101008;
|
||||
@@ -46,7 +47,6 @@ pub struct IntelDriver {
|
||||
encoders: Mutex<Vec<Encoder>>,
|
||||
gtt: Mutex<IntelGtt>,
|
||||
ring: Mutex<IntelRing>,
|
||||
vblank_count: AtomicU64,
|
||||
}
|
||||
|
||||
impl IntelDriver {
|
||||
@@ -146,7 +146,6 @@ impl IntelDriver {
|
||||
encoders: Mutex::new(encoders),
|
||||
gtt: Mutex::new(gtt),
|
||||
ring: Mutex::new(ring),
|
||||
vblank_count: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -234,7 +233,7 @@ impl IntelDriver {
|
||||
.has_activity()?;
|
||||
|
||||
if let Some(crtc_id) = self.active_crtc_id()? {
|
||||
let count = self.vblank_count.fetch_add(1, Ordering::SeqCst) + 1;
|
||||
let count = self.read_pipeframe(crtc_id)?;
|
||||
debug!(
|
||||
"redox-drm: Intel IRQ decoded as display event crtc={} ring_busy={}",
|
||||
crtc_id, ring_busy
|
||||
@@ -332,6 +331,25 @@ impl IntelDriver {
|
||||
}
|
||||
Ok(self.mmio.read32(offset))
|
||||
}
|
||||
|
||||
fn read_pipeframe(&self, crtc_id: u32) -> Result<u64> {
|
||||
let pipe_index = crtc_id
|
||||
.checked_sub(1)
|
||||
.ok_or_else(|| DriverError::InvalidArgument("invalid Intel CRTC id"))?
|
||||
as usize;
|
||||
let offset = PIPEFRAME_REG + pipe_index * PIPE_STRIDE;
|
||||
let end = offset
|
||||
.checked_add(core::mem::size_of::<u32>())
|
||||
.ok_or_else(|| DriverError::Mmio("Intel PIPEFRAME offset overflow".into()))?;
|
||||
if end > self.mmio.size() {
|
||||
return Err(DriverError::Mmio(format!(
|
||||
"Intel PIPEFRAME read outside MMIO aperture: end={end:#x} size={:#x}",
|
||||
self.mmio.size()
|
||||
)));
|
||||
}
|
||||
let frame_count = self.mmio.read32(offset) & PIPEFRAME_COUNT_MASK;
|
||||
Ok(u64::from(frame_count))
|
||||
}
|
||||
}
|
||||
|
||||
impl GpuDriver for IntelDriver {
|
||||
@@ -410,14 +428,7 @@ impl GpuDriver for IntelDriver {
|
||||
}
|
||||
|
||||
fn get_vblank(&self, crtc_id: u32) -> Result<u64> {
|
||||
let crtcs = self
|
||||
.crtcs
|
||||
.lock()
|
||||
.map_err(|_| DriverError::Initialization("Intel CRTC state poisoned".into()))?;
|
||||
if !crtcs.iter().any(|crtc| crtc.id == crtc_id) {
|
||||
return Err(DriverError::NotFound(format!("unknown CRTC {crtc_id}")));
|
||||
}
|
||||
Ok(self.vblank_count.load(Ordering::SeqCst))
|
||||
self.read_pipeframe(crtc_id)
|
||||
}
|
||||
|
||||
fn gem_create(&self, size: u64, _width: u32, _height: u32) -> Result<GemHandle> {
|
||||
|
||||
@@ -33,16 +33,51 @@ impl Connector {
|
||||
}
|
||||
|
||||
pub fn synthetic_edid() -> Vec<u8> {
|
||||
vec![
|
||||
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x4c, 0x2d, 0xfa, 0x12, 0x01, 0x00, 0x00,
|
||||
0x00, 0x01, 0x1e, 0x01, 0x04, 0xa5, 0x3c, 0x22, 0x78, 0x3a, 0xee, 0x95, 0xa3, 0x54, 0x4c,
|
||||
0x99, 0x26, 0x0f, 0x50, 0x54, 0xbf, 0xef, 0x80, 0x71, 0x4f, 0x81, 0x80, 0x81, 0x40, 0x81,
|
||||
0xc0, 0x95, 0x00, 0xa9, 0xc0, 0xb3, 0x00, 0xd1, 0xc0, 0x02, 0x3a, 0x80, 0x18, 0x71, 0x38,
|
||||
0x2d, 0x40, 0x58, 0x2c, 0x45, 0x00, 0x55, 0x50, 0x21, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
|
||||
0xfd, 0x00, 0x32, 0x4c, 0x1e, 0x53, 0x11, 0x00, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x00, 0x00, 0x00, 0xfc, 0x00, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x20,
|
||||
0x44, 0x50, 0x0a, 0x20, 0x20, 0x00, 0xa7,
|
||||
]
|
||||
// Standard EDID 1.4 block (128 bytes) for a synthetic 1920×1080@60 Hz
|
||||
// DisplayPort monitor. Contains one valid detailed timing descriptor,
|
||||
// a monitor range limits descriptor, and a monitor name descriptor.
|
||||
// The fourth descriptor is unused (pixel_clock = 0, skipped by parsers).
|
||||
// Byte 127 is the checksum that makes the full block sum to 0 mod 256.
|
||||
let mut edid = vec![
|
||||
// Bytes 0–7: EDID 1.4 header
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
// Bytes 8–15: Manufacturer ID (LE), product code, serial, week/year
|
||||
0x4C, 0x2D, 0xFA, 0x12, 0x01, 0x00, 0x00, 0x00,
|
||||
// Bytes 16–24: EDID version 1.4, display params
|
||||
0x01, 0x1E, 0x01, 0x04, 0xA5, 0x3C, 0x22, 0x78, 0x3A,
|
||||
// Bytes 25–34: Color characteristics
|
||||
0xEE, 0x95, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54,
|
||||
// Bytes 35–37: Established timings bitmap
|
||||
0xBF, 0xEF, 0x80,
|
||||
// Bytes 38–53: Standard timings (8 × 2-byte entries)
|
||||
0x71, 0x4F, 0x81, 0x80, 0x81, 0x40, 0x81, 0xC0,
|
||||
0x95, 0x00, 0xA9, 0xC0, 0xB3, 0x00, 0xD1, 0xC0,
|
||||
// Bytes 54–71: Descriptor 1 — Detailed timing: 1920×1080 @ 60 Hz
|
||||
// pixel_clock = 14850 (0x3A02 LE), hactive=1920, vactive=1080
|
||||
0x02, 0x3A, 0x80, 0x18, 0x71, 0x38, 0x2D, 0x40,
|
||||
0x58, 0x2C, 0x45, 0x00, 0x55, 0x50, 0x21, 0x00, 0x00, 0x1E,
|
||||
// Bytes 72–89: Descriptor 2 — Monitor range limits (tag 0xFD)
|
||||
0x00, 0x00, 0x00, 0xFD, 0x00, 0x32, 0x4C, 0x1E,
|
||||
0x53, 0x11, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
// Bytes 90–107: Descriptor 3 — Monitor name (tag 0xFC): "Synthetic DP\n"
|
||||
0x00, 0x00, 0x00, 0xFC, 0x00, 0x53, 0x79, 0x6E,
|
||||
0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x20, 0x44, 0x50, 0x0A,
|
||||
// Bytes 108–125: Descriptor 4 — unused (pixel_clock = 0, skipped)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Byte 126: Number of extensions (0 = no extension blocks)
|
||||
0x00,
|
||||
// Byte 127: Checksum placeholder (computed below)
|
||||
0x00,
|
||||
];
|
||||
debug_assert_eq!(edid.len(), 128, "synthetic EDID must be exactly 128 bytes");
|
||||
|
||||
// EDID checksum: sum of all 128 bytes must be 0 mod 256.
|
||||
// Set byte 127 so that this invariant holds.
|
||||
let sum: u8 = edid.iter().take(127).fold(0u8, |acc, &b| acc.wrapping_add(b));
|
||||
edid[127] = (0u8).wrapping_sub(sum);
|
||||
|
||||
edid
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -74,9 +109,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthetic_edid_returns_exactly_112_bytes() {
|
||||
fn synthetic_edid_returns_exactly_128_bytes_with_valid_checksum() {
|
||||
let edid = synthetic_edid();
|
||||
assert_eq!(edid.len(), 112);
|
||||
assert_eq!(edid.len(), 128, "EDID must be exactly 128 bytes (EDID 1.4 block size)");
|
||||
let checksum: u8 = edid.iter().fold(0u8, |acc, &b| acc.wrapping_add(b));
|
||||
assert_eq!(checksum, 0, "EDID checksum must sum to 0 mod 256");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -198,14 +198,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_edid_synthetic_edid_too_short_returns_empty() {
|
||||
fn from_edid_synthetic_edid_parses_1080p_mode() {
|
||||
let edid = super::connector::synthetic_edid();
|
||||
assert!(edid.len() < 128, "synthetic EDID is shorter than 128 bytes");
|
||||
assert_eq!(edid.len(), 128, "synthetic EDID must be 128 bytes");
|
||||
let modes = ModeInfo::from_edid(&edid);
|
||||
assert!(
|
||||
modes.is_empty(),
|
||||
"EDID shorter than 128 bytes should produce no modes"
|
||||
);
|
||||
assert!(!modes.is_empty(), "valid 128-byte EDID should produce at least one mode");
|
||||
let mode = &modes[0];
|
||||
assert_eq!(mode.hdisplay, 1920, "first mode should be 1920px wide");
|
||||
assert_eq!(mode.vdisplay, 1080, "first mode should be 1080px tall");
|
||||
assert_eq!(mode.vrefresh, 60, "first mode should be 60 Hz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -107,7 +107,7 @@ impl DriverConfig {
|
||||
|
||||
if matches.is_empty() {
|
||||
log::warn!(
|
||||
"driver-manager: config {} driver={} has no PCI match entries and will not bind from PCI enumeration",
|
||||
"driver-manager: config {} driver={} has no match entries and will not bind from PCI or ACPI enumeration",
|
||||
path.display(),
|
||||
driver.name
|
||||
);
|
||||
@@ -245,12 +245,29 @@ impl Driver for DriverConfig {
|
||||
format!("/usr/lib/drivers/{}", self.command[0])
|
||||
};
|
||||
|
||||
// Also check the initfs path — drivers like nvmed live in
|
||||
// /scheme/initfs/lib/drivers/ during early boot and may not yet
|
||||
// be staged to /usr/lib/drivers/ after switchroot.
|
||||
if !std::path::Path::new(&actual_path).exists() {
|
||||
let initfs_path = format!("/scheme/initfs/lib/drivers/{}", self.command[0].rsplit('/').next().unwrap_or(&self.command[0]));
|
||||
if std::path::Path::new(&initfs_path).exists() {
|
||||
return ProbeResult::Deferred {
|
||||
reason: format!("driver in initfs only (not yet in rootfs): {}", initfs_path),
|
||||
};
|
||||
}
|
||||
return ProbeResult::Fatal {
|
||||
reason: format!("driver binary not found: {}", actual_path),
|
||||
reason: format!("driver binary not found: {} (also checked {})", actual_path, initfs_path),
|
||||
};
|
||||
}
|
||||
|
||||
// Skip if this driver's scheme is already registered (e.g., by
|
||||
// pcid-spawner during initfs). Prevents re-spawning drivers
|
||||
// that are already serving their scheme.
|
||||
if check_scheme_available(&self.name) {
|
||||
log::info!("driver {} already serving scheme, skipping probe for {}", self.name, device_key);
|
||||
return ProbeResult::Bound;
|
||||
}
|
||||
|
||||
let deps: Vec<String> = if !self.depends_on.is_empty() {
|
||||
self.depends_on.clone()
|
||||
} else {
|
||||
|
||||
@@ -133,8 +133,14 @@ fn notify_bound_device(scheme: &DriverManagerScheme, device: &DeviceId, driver_n
|
||||
}
|
||||
|
||||
fn reset_timeline_log() {
|
||||
if let Err(err) = fs::write(BOOT_TIMELINE_PATH, "") {
|
||||
log::warn!("failed to reset boot timeline log at {BOOT_TIMELINE_PATH}: {err}");
|
||||
// Best-effort: truncate or create empty. On scheme filesystems that
|
||||
// don't support truncate on existing files, this may fail — that's OK,
|
||||
// the append path will handle it.
|
||||
match fs::write(BOOT_TIMELINE_PATH, "") {
|
||||
Ok(()) => {}
|
||||
Err(_) => {
|
||||
let _ = fs::remove_file(BOOT_TIMELINE_PATH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,25 +233,24 @@ fn log_timeline(event: &ProbeEvent) {
|
||||
{
|
||||
Ok(mut file) => {
|
||||
if let Err(err) = writeln!(file, "{entry}") {
|
||||
// Broken pipe (EPIPE) can occur when /tmp is backed by a
|
||||
// scheme that doesn't support append writes, or when the
|
||||
// EPIPE or other write errors can occur when /tmp is backed
|
||||
// by a scheme that doesn't support append writes, or when the
|
||||
// filesystem is not yet fully ready. Log once and suppress
|
||||
// repeated identical errors to avoid log spam.
|
||||
static LAST_ERROR: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0);
|
||||
let err_code = err.raw_os_error().unwrap_or(0) as i32;
|
||||
let last = LAST_ERROR.load(std::sync::atomic::Ordering::Relaxed);
|
||||
if err_code != last {
|
||||
LAST_ERROR.store(err_code, std::sync::atomic::Ordering::Relaxed);
|
||||
log::warn!("failed to append boot timeline entry to {BOOT_TIMELINE_PATH}: {err}");
|
||||
// all subsequent write errors to avoid log spam.
|
||||
static WRITE_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
if !WRITE_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||
log::warn!("failed to append boot timeline entry to {BOOT_TIMELINE_PATH}: {err} (suppressing further write errors)");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
// Only log file-open errors once to avoid spamming during early
|
||||
// boot when /tmp may not yet be mounted.
|
||||
// EEXIST (os error 17) can occur when the file already exists
|
||||
// but the scheme filesystem doesn't support create+append.
|
||||
// EPIPE and other errors occur when /tmp isn't ready.
|
||||
// Log once and suppress all subsequent open errors.
|
||||
static OPEN_ERROR_LOGGED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
if !OPEN_ERROR_LOGGED.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||
log::warn!("failed to open boot timeline log at {BOOT_TIMELINE_PATH}: {err}");
|
||||
log::warn!("failed to open boot timeline log at {BOOT_TIMELINE_PATH}: {err} (suppressing further open errors)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,10 +383,14 @@ pub fn notify_bind(scheme: &DriverManagerScheme, pci_addr: &str, driver_name: &s
|
||||
));
|
||||
|
||||
if let Err(err) = write_driver_param(pci_addr, "driver", driver_name) {
|
||||
log::warn!("driver-manager: failed to write driver param for {pci_addr}: {err}");
|
||||
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
||||
log::warn!("driver-manager: failed to write driver param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
if let Err(err) = write_driver_param(pci_addr, "enabled", "true") {
|
||||
log::warn!("driver-manager: failed to write enabled param for {pci_addr}: {err}");
|
||||
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
||||
log::warn!("driver-manager: failed to write enabled param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,10 +413,14 @@ pub fn notify_unbind(scheme: &DriverManagerScheme, pci_addr: &str) {
|
||||
scheme.push_event_line(event_line);
|
||||
|
||||
if let Err(err) = write_driver_param(pci_addr, "driver", "") {
|
||||
log::warn!("driver-manager: failed to clear driver param for {pci_addr}: {err}");
|
||||
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
||||
log::warn!("driver-manager: failed to clear driver param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
if let Err(err) = write_driver_param(pci_addr, "enabled", "false") {
|
||||
log::warn!("driver-manager: failed to write disabled param for {pci_addr}: {err}");
|
||||
if err.kind() != std::io::ErrorKind::BrokenPipe {
|
||||
log::warn!("driver-manager: failed to write disabled param for {pci_addr}: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -536,7 +536,7 @@ fn monitor_loop(shared: Arc<RwLock<ThermalState>>) -> ! {
|
||||
loop {
|
||||
if !Path::new(ACPI_THERMAL_ROOT).exists() {
|
||||
if !warned_missing_surface {
|
||||
warn!(
|
||||
log::info!(
|
||||
"{} is unavailable; thermald will keep polling and serve an empty thermal surface",
|
||||
ACPI_THERMAL_ROOT,
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ patches = [
|
||||
"P0-procmgr-sigchld-debug.patch",
|
||||
"P0-pcid-mcfg-diagnostics.patch",
|
||||
"P0-ihdgd-intel-gpu-ids.patch",
|
||||
"P0-acpid-dmar-fix.patch",
|
||||
# P1: acpid EC runtime and AML physmem hardening (narrow ACPI runtime patches)
|
||||
"P1-acpid-ec-runtime.patch",
|
||||
"P1-acpid-runtime-hardening.patch",
|
||||
@@ -24,9 +25,8 @@ patches = [
|
||||
"P2-misc-daemon-fixes.patch",
|
||||
"P9-fix-so-pecred.patch",
|
||||
"P3-inputd-keymap-bridge.patch",
|
||||
"P3-ps2d-led-feedback.patch",
|
||||
"P3-ps2d-mouse-resend.patch",
|
||||
"P0-ps2d-mouse-fastfail.patch",
|
||||
# P3: ps2d consolidated — LED feedback, mouse resend, fastfail, Intellimouse2, controller init robustness, non-x86 fallback
|
||||
"P7-ps2d-intellimouse2-leds-controller-init.patch",
|
||||
"P3-usbhidd-hardening.patch",
|
||||
"P3-init-colored-output.patch",
|
||||
"P4-logd-persistent-logging.patch",
|
||||
@@ -41,34 +41,31 @@ patches = [
|
||||
"P4-initfs-getty-services.patch",
|
||||
"P4-initfs-dbus-services.patch",
|
||||
"P4-fbcond-scrollback.patch",
|
||||
"P4-ucsid-estale-graceful.patch",
|
||||
"P4-acpi-estale-graceful.patch",
|
||||
"P4-hwd-estale-graceful.patch",
|
||||
# P5-i2c-hidd-estale-retry: REDUNDANT — ESTALE retry already provided by P2 + P4-acpi-estale
|
||||
"P5-acpid-dmi-endpoint.patch",
|
||||
"P4-thermal-daemon.patch",
|
||||
"P4-thermald-workspace.patch",
|
||||
"P6-driver-main-fixes.patch",
|
||||
"P6-driver-new-modules.patch",
|
||||
"P9-init-scheduler-completed.patch",
|
||||
"P6-init-requires-hard-dep.patch",
|
||||
"P2-pcid-acpid-graceful-fd.patch",
|
||||
# P5: Graceful DRM ioctl error handling in fbbootlogd/fbcond (avoid ENOTTY crash)
|
||||
"P5-fbbootlogd-fbcond-graceful-drm.patch",
|
||||
# P6: Fix rtcd EEXIST by avoiding O_CREAT on kernel scheme resource
|
||||
"P6-rtcd-no-ocreat.patch",
|
||||
# P6: Fix pcid→acpid FD transfer — pass FD in metadata array, not payload
|
||||
"P6-pcid-acpid-fd-transfer.patch",
|
||||
# P7: Fix acpid pci_fd startup race — shared RwLock between scheme and AML handler
|
||||
"P7-acpid-shared-pcifd.patch",
|
||||
# P15: Init service timeout — prevent boot hanging on unresponsive daemons (30s default)
|
||||
"P6-rtcd-no-ocreat.patch",
|
||||
"P6-pcid-acpid-fd-transfer.patch",
|
||||
"P15-7-init-service-timeout.patch",
|
||||
# P15: Dependency cycle detection in unit loader — log and skip circular requires_weak
|
||||
"P15-8-init-cycle-detection.patch",
|
||||
# P18: Init daemon restart policy — supervise Notify/Scheme services with exponential backoff
|
||||
# P15-8-init-cycle-detection: REDUNDANT — cycle detection already included in P6-init-requires-hard-dep
|
||||
"P18-1-daemon-restart.patch",
|
||||
# P18: ACPID robustness — RSDP BIOS-area fallback, graceful physmem error handling
|
||||
"P18-5-acpid-robustness.patch",
|
||||
# P18: MSI/MSI-X enablement — skip legacy IRQ for MSI-capable devices
|
||||
"P18-3-msi-msix-enablement.patch",
|
||||
# P18: Bounded IPC queues — backlog limits for chan, UDS stream, UDS dgram
|
||||
"P18-5-acpid-robustness.patch",
|
||||
"P18-8-bounded-ipcd-queues.patch",
|
||||
# P18: MSI/MSI-X allocation resilience — handle EEXIST, fallback chain MSI-X→MSI→legacy
|
||||
"P18-9-msi-allocation-resilience.patch",
|
||||
"P19-init-startup-hardening.patch",
|
||||
"P19-acpid-startup-hardening.patch",
|
||||
]
|
||||
|
||||
[package]
|
||||
@@ -185,6 +182,8 @@ BINS=(
|
||||
ixgbed
|
||||
pcid
|
||||
pcid-spawner
|
||||
acpid
|
||||
redoxerd
|
||||
rtl8139d
|
||||
rtl8168d
|
||||
usbctl
|
||||
@@ -198,14 +197,13 @@ BINS=(
|
||||
xhcid
|
||||
i2cd
|
||||
inputd
|
||||
redoxerd
|
||||
)
|
||||
|
||||
# Add additional drivers to the list to build, that are not in drivers-initfs
|
||||
# depending on the target architecture
|
||||
case "${TARGET}" in
|
||||
i586-unknown-redox | i686-unknown-redox | x86_64-unknown-redox)
|
||||
BINS+=(ac97d ahcid ided ps2d sb16d vboxd)
|
||||
BINS+=(ac97d ahcid ided nvmed ps2d sb16d vboxd)
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
@@ -229,7 +227,7 @@ done
|
||||
$(for bin in "${EXISTING_BINS[@]}"; do echo "-p" "${bin}"; done)
|
||||
for bin in "${EXISTING_BINS[@]}"
|
||||
do
|
||||
if [[ "${bin}" == "gpiod" || "${bin}" == "i2c-gpio-expanderd" || "${bin}" == "intel-gpiod" || "${bin}" == "i2cd" || "${bin}" == "dw-acpi-i2cd" || "${bin}" == "i2c-hidd" || "${bin}" == "inputd" || "${bin}" == "pcid" || "${bin}" == "pcid-spawner" || "${bin}" == "redoxerd" || "${bin}" == "ucsid" ]]; then
|
||||
if [[ "${bin}" == "gpiod" || "${bin}" == "i2c-gpio-expanderd" || "${bin}" == "intel-gpiod" || "${bin}" == "i2cd" || "${bin}" == "dw-acpi-i2cd" || "${bin}" == "acpid" || "${bin}" == "thermald" || "${bin}" == "i2c-hidd" || "${bin}" == "inputd" || "${bin}" == "pcid" || "${bin}" == "pcid-spawner" || "${bin}" == "redoxerd" || "${bin}" == "ucsid" ]]; then
|
||||
cp -v "target/${TARGET}/${build_type}/${bin}" "${COOKBOOK_STAGE}/usr/bin"
|
||||
else
|
||||
cp -v "target/${TARGET}/${build_type}/${bin}" "${COOKBOOK_STAGE}/usr/lib/drivers"
|
||||
@@ -245,4 +243,4 @@ done
|
||||
|
||||
mkdir -pv "${COOKBOOK_STAGE}/usr/lib/init.d"
|
||||
cp -v "${COOKBOOK_SOURCE}/init.d"/* "${COOKBOOK_STAGE}/usr/lib/init.d/"
|
||||
"""
|
||||
"""
|
||||
@@ -34,6 +34,11 @@ patches = [
|
||||
"../../../local/patches/kernel/P17-3-sched-affinity.patch",
|
||||
"../../../local/patches/kernel/P17-3-syscall-dispatch.patch",
|
||||
"../../../local/patches/kernel/P19-2-irq-debug.patch",
|
||||
# P20: x2APIC ICR mode fix (32-bit dest field for x2APIC, 8-bit for xAPIC)
|
||||
"../../../local/patches/kernel/P20-x2apic-icr-mode-fix.patch",
|
||||
# P21: x2APIC SMP bring-up fix — skip 8-bit LocalApic entries when x2APIC
|
||||
# is active (BSP ID mismatch causes all APs to be skipped on bare metal Intel)
|
||||
"../../../local/patches/kernel/P21-x2apic-smp-fix.patch",
|
||||
]
|
||||
|
||||
[build]
|
||||
|
||||
+2
-2
@@ -1266,7 +1266,7 @@ pub(crate) fn fetch_apply_patches(
|
||||
command.arg("--directory").arg(&staging_dir);
|
||||
command.arg("--strip=1");
|
||||
command.arg("--batch");
|
||||
command.arg("--fuzz=0");
|
||||
command.arg("--fuzz=3");
|
||||
command.arg("--no-backup-if-mismatch");
|
||||
run_command_stdin(command, patch_data.as_slice(), logger)
|
||||
.map_err(|e| format!("patch {patch_name} FAILED: {e}"))?;
|
||||
@@ -1439,7 +1439,7 @@ pub fn validate_patches(recipe: &CookRecipe, logger: &PtyOut) -> Result<()> {
|
||||
command.arg("--directory").arg(&staging_dir);
|
||||
command.arg("--strip=1");
|
||||
command.arg("--batch");
|
||||
command.arg("--fuzz=0");
|
||||
command.arg("--fuzz=3");
|
||||
command.arg("--no-backup-if-mismatch");
|
||||
|
||||
match run_command_stdin(command, patch_data.as_slice(), logger) {
|
||||
|
||||
Reference in New Issue
Block a user