9 Commits

Author SHA1 Message Date
vasilito 0cbad35638 chore: kernel source patches, local recipe updates, and build artifacts
Kernel source (ephemeral — changes durable in local/patches/kernel/):
- P20 x2apic ICR mode fix, P21 x2apic SMP fix applied
- ACPI MADT, RSDP, SDT improvements
- Context switch, percpu, event, IRQ scheme updates
- MSI/vector allocation, NUMA/SLIT/SRAT support

Local recipe source updates:
- redox-driver-acpi: bus/prt hardening
- redox-drm: Intel display, KMS connector improvements
- driver-manager: config/scheme hardening
- thermald: main.rs fix
- uutils-tar, ninja-build: source updates

Other:
- bootloader, installer, redoxfs, relibc, userutils source updates
- recipe.toml.backup, libxcvt source directory
2026-05-18 14:20:54 +03:00
vasilito 29ff1ea8fc 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
2026-05-18 14:07:42 +03:00
vasilito d856a63dfb fix: remove P20 forward patch — cascading context issues with P16-1
x2APIC ICR mode fix cannot be a forward patch because P16-1-sipi-timing
references the ICR line as context. Modifying P1 (which introduces the line)
would require updating P16-1's offsets. Will address by consolidating into
an existing early patch in a future revision.
2026-05-17 15:25:36 +03:00
vasilito 0442ab6eb0 chore: remove kernel patch artifacts 2026-05-17 14:57:18 +03:00
vasilito 4a2c33750b feat: raw framebuffer fallback for fbbootlogd when DRM unavailable
- Add RawFb struct: direct framebuffer rendering via physmap
- Add RawTextScreen: simple text renderer using orbclient font
- Fallback in FbbootlogScheme::new() when V2GraphicsHandle fails
- Reads FRAMEBUFFER_ADDR/WIDTH/HEIGHT/STRIDE from bootloader env
- Scroll via ptr::copy on pixel rows, clear bottom line
- No DRM, no shadow buffer, no GPU required — like MS-DOS text mode
- Add common dependency to fbbootlogd Cargo.toml
2026-05-17 14:56:50 +03:00
vasilito ea7234f44b fix: correct P20 patch line numbers from actual diff
- Derived line offsets from real pre-P20 vs post-P20 diff
- x86.rs: 3 hunks at @@ -446/-456/-468 converting hardcoded <<32 to
  local_apic.x2-gated format with xAPIC <<56 fallback
- local_apic.rs: 1 hunk promoting debug! to info! for bootlog visibility
2026-05-17 14:51:58 +03:00
vasilito 6f12b4e2a8 chore: remove patch leftovers (.orig/.rej) 2026-05-17 13:57:56 +03:00
vasilito 5562ab0aaf fix: x2APIC ICR format + build system durability docs
- Fix LocalX2Apic handler: use local_apic.x2 to select correct ICR
  format (<<32 for x2APIC, <<56 for xAPIC) instead of hardcoded <<32
- Promote x2APIC/xAPIC detection from debug! to info! for bootlog
- Document build system durability in AGENTS.md: cardinal rule,
  two-layer architecture, correct workflow, anti-patterns
2026-05-17 13:57:37 +03:00
vasilito 45e086558e fix: boot process improvements — dependency cycle, INIT_NOTIFY, probing loop, and log spam fixes
- Fix P15-8-init-cycle-detection.patch: replace visiting+error with seen+silent-skip
  to eliminate 11 false-positive 'dependency cycle detected' errors on shared deps
- Fix P0-daemon-fix-init-notify-unwrap.patch: remove eprintln! for missing
  INIT_NOTIFY (expected for oneshot_async services, ~7 daemons affected)
- Fix driver-manager hotplug loop: add PERMANENTLY_SKIPPED static set shared
  between hotplug handler and DriverConfig::probe() to stop infinite re-probing
  of Fatal/NotSupported/deferred-exhausted device+driver pairs (e.g. ided)
- Fix driver-manager log_timeline: suppress repeated EPIPE/ENOENT errors with
  AtomicI32 dedup and AtomicBool one-shot guards for boot timeline JSON
- Add driver-manager SIGTERM handler, ACPI bus registration, --status mode,
  driver reap loop, graceful shutdown, and reduced deferred retries (30→3)
2026-05-17 12:34:02 +03:00
4038 changed files with 579372 additions and 1680075 deletions
+217 -2
View File
@@ -19,7 +19,109 @@ human-initiated operations. Durable Red Bear state belongs in `local/patches/`,
The current baseline is **Red Bear OS 0.1.0** (Redox snapshot at build-system commit `f55acba68`). The current baseline is **Red Bear OS 0.1.0** (Redox snapshot at build-system commit `f55acba68`).
All recipe sources are pinned and archived in `sources/redbear-0.1.0/`. All recipe sources are pinned and archived in `sources/redbear-0.1.0/`.
## NO SILENT UPSTREAM PULLS — OFFLINE-FIRST POLICY ## BUILD SYSTEM DURABILITY — THE CARDINAL RULE
**Never edit files under `recipes/*/source/` directly. Those trees are ephemeral — they are
destroyed and regenerated on every `repo fetch`, `repo cook`, `make clean`, and
`make distclean`. Any edit made there will be silently lost on the next build.**
This is the #1 mistake AI agents and new contributors make. It has caused repeated work loss
in this project. The rule is:
| What you want to do | Where to do it |
|---|---|
| Change a kernel source file | Create or update a patch in `local/patches/kernel/` |
| Change an init or daemon source file | Create or update a patch in `local/patches/base/` |
| Change relibc | Create or update a patch in `local/patches/relibc/` |
| Change a driver | Create or update a patch in `local/patches/base/` or `local/patches/<driver>/` |
| Add a new package | Create a recipe in `local/recipes/<category>/<name>/` |
| Change build config | Edit `config/redbear-*.toml` |
| Add documentation | Write to `local/docs/` |
### How the build system works
```
repo cook <package>
├── repo fetch <package>
│ ├── Clone/fetch upstream source → recipes/<pkg>/source/
│ ├── Apply patches from recipe.toml → patches are read from local/patches/<pkg>/
│ └── Source tree is now fully patched and ready for build
├── Cargo/cmake/configure build
└── Stage artifacts into sysroot
```
The `source/` directory is a disposable working copy. It is produced at the start of every
build by cloning the upstream source + applying patches sequentially. The recipe's
`patches = [...]` list in `recipe.toml` controls which patches are applied.
### Two-layer architecture
```
Layer 1: Ephemeral (destroyed on clean/fetch/rebuild)
recipes/<pkg>/source/ ← working tree, cloned + patched
build/ ← build outputs
target/ ← cargo target dir
Layer 2: Durable (survives clean/fetch/rebuild/release provisioning)
local/patches/<pkg>/ ← .patch files — the actual source code changes
local/recipes/<pkg>/ ← custom recipe directories
config/redbear-*.toml ← Red Bear OS build configs
local/docs/ ← planning and integration docs
recipes/<pkg>/recipe.toml ← the patches list (git-tracked)
```
### The correct workflow for any source change
1. **Make the change** in `recipes/<pkg>/source/` to validate it compiles
2. **Generate a patch**: `cd recipes/<pkg>/source && git diff > ../../../local/patches/<pkg>/my-fix.patch`
3. **Wire the patch**: add `"my-fix.patch"` to the recipe's `recipe.toml` `patches = [...]` list
4. **Validate**: `./target/release/repo validate-patches <pkg>`
5. **Rebuild**: `./target/release/repo cook <pkg>`
6. **Commit**: `git add local/patches/ recipes/<pkg>/recipe.toml && git commit`
### Common anti-patterns
| Anti-pattern | Why it fails |
|---|---|
| Editing `source/` files then running `make all` | `make all` calls `repo fetch` which regenerates `source/` — edits are lost |
| Creating a patch but not wiring it into `recipe.toml` | Patch file exists but is never applied — build uses unpatched source |
| Editing `recipe.toml` patches list without creating the actual `.patch` file | Build fails with "missing patch" error |
| Expecting `source/` changes to survive `make clean` | `make clean` deletes `source/` directories |
| Running `repo cook` without `--allow-protected` for core packages | Protected recipes (kernel, relibc, base) are offline-only by default |
### Patch file location convention
- `local/patches/base/` — for the `base` package (init, daemon, all drivers)
- `local/patches/kernel/` — for the kernel
- `local/patches/relibc/` — for relibc
- `local/patches/installer/` — for the installer
- `local/patches/bootloader/` — for the bootloader
- `local/patches/<package>/` — for any other patched package
### Recipe patch wiring
Each recipe's `recipe.toml` lists patches relative to `local/patches/<pkg>/`:
```toml
[source]
git = "https://gitlab.redox-os.org/redox-os/base.git"
rev = "463f76b96..."
patches = [
"P0-daemon-fix-init-notify-unwrap.patch", # applied first
"P9-init-scheduler-completed.patch", # applied second
# ... more patches
]
```
Patches are applied in listed order. Dependencies between patches must be respected (a patch
that defines a type must come before a patch that uses it).
### Kernel-specific notes
The kernel source at `recipes/core/kernel/source/` is a separate git worktree (rev `866dfad`).
The kernel recipe is at `recipes/core/kernel/recipe.toml` and patches are at
`local/patches/kernel/`. The same durability rules apply — all kernel changes must be
in `local/patches/kernel/*.patch`, never in the `source/` tree directly.
**Red Bear OS is offline-first by default. No script, build target, or tool may silently pull **Red Bear OS is offline-first by default. No script, build target, or tool may silently pull
from any upstream repository without explicit user instruction.** from any upstream repository without explicit user instruction.**
@@ -178,10 +280,24 @@ make all
→ mk/fstools.mk (build cookbook repo binary + fstools) → mk/fstools.mk (build cookbook repo binary + fstools)
→ mk/repo.mk (repo cook --filesystem=config/*.toml) → mk/repo.mk (repo cook --filesystem=config/*.toml)
→ For each recipe: fetch source → apply patches → build → stage into sysroot → For each recipe: fetch source → apply patches → build → stage into sysroot
→ Each successful build produces repo/<arch>/<name>.pkgar + <name>.toml
→ mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso or harddrive.img) → mk/disk.mk (create filesystem.img, harddrive.img, redbear-live.iso or harddrive.img)
→ redoxfs-mkfs → redox_installer → bootloader embedding → redoxfs-mkfs → redox_installer → bootloader embedding
``` ```
### Build Outputs
Every successful `repo cook <package>` produces:
| Artifact | Location | Purpose |
|----------|----------|---------|
| Package archive | `repo/x86_64-unknown-redox/<name>.pkgar` | Binary package for image assembly |
| Package manifest | `repo/x86_64-unknown-redox/<name>.toml` | Metadata, version, deps, hashes |
| Staged sysroot | `recipes/*/<name>/target/.../stage/` | Files for `repo push` |
| Source tree | `recipes/*/<name>/source/` | Fetched + patched source (disposable) |
**A build is not complete until the .pkgar and .toml exist in `repo/`.**
## CONVENTIONS ## CONVENTIONS
- **Rust edition 2024**, nightly channel - **Rust edition 2024**, nightly channel
@@ -444,6 +560,65 @@ or any path that is already git-tracked and not inside a fetched source tree.
## BUILD SYSTEM POLICIES ## BUILD SYSTEM POLICIES
### Build Durability Rule — Every Build Lands in the Repo
Every successful `repo cook` produces two durable artifacts:
1. **Package in the repo**: `repo/x86_64-unknown-redox/<name>.pkgar` + `<name>.toml`
2. **Patched source form**: All source modifications are in `local/patches/<component>/` and wired into `recipe.toml`
A build is **not complete** until both artifacts exist:
```bash
# After cooking, verify the package is in the repo
./target/release/repo find <package>
# Check the repo manifest exists
ls repo/x86_64-unknown-redox/<package>.toml
ls repo/x86_64-unknown-redox/<package>.pkgar
```
If a package was built but the repo artifacts are missing, the build did not complete.
Re-run `repo cook <package>` to regenerate them.
If source patches were applied but not mirrored to `local/patches/`, see the
DURABILITY POLICY section above.
### Cascade Rebuild Rule
When a low-level package changes (relibc, kernel, base, or any library), **all
packages that depend on it must be rebuilt**. A stale dependent silently produces
link errors, ABI mismatches, or runtime crashes.
Use the cascade rebuild script:
```bash
# Rebuild relibc and everything that depends on it
./local/scripts/rebuild-cascade.sh relibc
# Dry run: show what would be rebuilt without building
./local/scripts/rebuild-cascade.sh --dry-run relibc
# Multiple root packages
./local/scripts/rebuild-cascade.sh relibc ncurses
```
The script:
1. Finds all packages whose `recipe.toml` lists the target in `dependencies`
2. Transitively expands the reverse dependency graph (BFS)
3. Builds the root package(s) first, then dependents in order
4. Pushes all rebuilt packages to the sysroot
**When to use cascade rebuilds:**
- After changing relibc headers or ABI
- After rebuilding a shared library (ncurses, zlib, openssl, etc.)
- After kernel ABI changes that affect userspace
- After any change to a package listed in other packages' `dependencies`
**When NOT to use cascade rebuilds:**
- Standalone applications with no dependents (editors, games, utilities)
- Terminal/leaf packages that nothing depends on
### Atomic Patch Application ### Atomic Patch Application
The cookbook tool (`src/cook/fetch.rs`) applies patches **atomically**: The cookbook tool (`src/cook/fetch.rs`) applies patches **atomically**:
@@ -466,12 +641,52 @@ Patches may use either format:
Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`, Git-specific headers (`diff --git`, `diff -ruN`, `index`, `new file mode`, `rename from/to`,
`similarity index`, `dissimilarity index`) are automatically stripped before `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. **Timestamps in `---`/`+++` lines** (common in `diff -ruN` output) should be removed.
Use `--- a/path` and `+++ b/path` without timestamps. The `normalize_patch` function 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. 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 ### Protected Recipes
Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying Core recipes (`base`, `kernel`, `relibc`, `bootloader`, etc.) and any recipe carrying
Executable
BIN
View File
Binary file not shown.
+109
View File
@@ -0,0 +1,109 @@
# Red Bear OS boot stage targets
#
# Semantic boot stages that create ordering through the init system's
# BFS dependency traversal. Each target depends on the previous one.
#
# Stage mapping:
# 00_base.target — kernel schemes ready (defined in base package initfs)
# 02_early_hw.target — ACPI + PCI bus access ready
# 04_drivers.target — driver spawning complete
# 06_services.target — system services (D-Bus, session broker)
# 08_userland.target — user-facing (console, greeter, desktop)
#
# Services use requires_weak against their stage target.
# Targets use requires_weak to chain to the previous stage.
#
# Serial boot markers (02-08_serial_*.service) echo a stage completion
# message to stderr, which appears on the serial console for diagnostics.
[[files]]
path = "/etc/init.d/02_early_hw.target"
data = """
[unit]
description = "Early hardware: ACPI + PCI bus access"
requires_weak = [
"00_base.target",
]
"""
[[files]]
path = "/etc/init.d/02_serial_early_hw.service"
data = """
[unit]
description = "Serial boot marker: early hardware stage"
requires_weak = ["02_early_hw.target"]
[service]
cmd = "echo"
args = ["RB_STAGE_02_EARLY_HW"]
type = "oneshot"
"""
[[files]]
path = "/etc/init.d/04_drivers.target"
data = """
[unit]
description = "Driver spawning stage"
requires_weak = [
"02_early_hw.target",
]
"""
[[files]]
path = "/etc/init.d/04_serial_drivers.service"
data = """
[unit]
description = "Serial boot marker: drivers stage"
requires_weak = ["04_drivers.target"]
[service]
cmd = "echo"
args = ["RB_STAGE_04_DRIVERS"]
type = "oneshot"
"""
[[files]]
path = "/etc/init.d/06_services.target"
data = """
[unit]
description = "System services: D-Bus, session broker, seat management"
requires_weak = [
"04_drivers.target",
]
"""
[[files]]
path = "/etc/init.d/06_serial_services.service"
data = """
[unit]
description = "Serial boot marker: services stage"
requires_weak = ["06_services.target"]
[service]
cmd = "echo"
args = ["RB_STAGE_06_SERVICES"]
type = "oneshot"
"""
[[files]]
path = "/etc/init.d/08_userland.target"
data = """
[unit]
description = "User-facing: console, greeter, desktop"
requires_weak = [
"06_services.target",
]
"""
[[files]]
path = "/etc/init.d/08_serial_userland.service"
data = """
[unit]
description = "Serial boot marker: userland stage"
requires_weak = ["08_userland.target"]
[service]
cmd = "echo"
args = ["RB_STAGE_08_USERLAND"]
type = "oneshot"
"""
+114 -22
View File
@@ -1,6 +1,11 @@
# Red Bear OS shared device-service wiring # Red Bear OS shared device-service wiring
# #
# Shared by profiles that ship the firmware/input/Wi-Fi control compatibility stack. # Shared by profiles that ship the firmware/input/Wi-Fi control compatibility stack.
#
# Driver matching: driver-manager reads /lib/drivers.d/*.toml and matches against
# devices from both PCI and ACPI buses. ACPI devices are classified with PCI-equivalent
# class/subclass/vendor codes by redox-driver-acpi's AcpiBus, allowing reuse of existing
# driver match rules.
[packages] [packages]
redbear-quirks = {} redbear-quirks = {}
@@ -32,9 +37,9 @@ data = """
path = "/etc/init.d/12_boot-late.target" path = "/etc/init.d/12_boot-late.target"
data = """ data = """
[unit] [unit]
description = "Late boot services target" description = "Late boot services target (compat alias for 04_drivers.target)"
requires_weak = [ requires_weak = [
"00_base.target", "04_drivers.target",
] ]
""" """
@@ -54,6 +59,7 @@ priority = 100
command = ["/usr/lib/drivers/nvmed"] command = ["/usr/lib/drivers/nvmed"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 8 subclass = 8
@@ -64,6 +70,7 @@ priority = 100
command = ["/usr/lib/drivers/ahcid"] command = ["/usr/lib/drivers/ahcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 6 subclass = 6
@@ -74,6 +81,7 @@ priority = 100
command = ["/usr/lib/drivers/ided"] command = ["/usr/lib/drivers/ided"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 1 subclass = 1
@@ -84,6 +92,7 @@ priority = 100
command = ["/usr/lib/drivers/virtio-blkd"] command = ["/usr/lib/drivers/virtio-blkd"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
device = 0x1001 device = 0x1001
class = 1 class = 1
@@ -100,6 +109,7 @@ priority = 50
command = ["/usr/lib/drivers/e1000d"] command = ["/usr/lib/drivers/e1000d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 2 class = 2
@@ -110,6 +120,7 @@ priority = 50
command = ["/usr/lib/drivers/rtl8168d"] command = ["/usr/lib/drivers/rtl8168d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x10EC vendor = 0x10EC
class = 2 class = 2
@@ -120,6 +131,7 @@ priority = 50
command = ["/usr/lib/drivers/rtl8139d"] command = ["/usr/lib/drivers/rtl8139d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x10EC vendor = 0x10EC
device = 0x8139 device = 0x8139
@@ -130,6 +142,7 @@ priority = 50
command = ["/usr/lib/drivers/ixgbed"] command = ["/usr/lib/drivers/ixgbed"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 2 class = 2
subclass = 0 subclass = 0
@@ -141,6 +154,7 @@ priority = 50
command = ["/usr/lib/drivers/virtio-netd"] command = ["/usr/lib/drivers/virtio-netd"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
class = 2 class = 2
""" """
@@ -155,6 +169,7 @@ priority = 80
command = ["/usr/lib/drivers/xhcid"] command = ["/usr/lib/drivers/xhcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x0C class = 0x0C
subclass = 0x03 subclass = 0x03
prog_if = 0x30 prog_if = 0x30
@@ -169,6 +184,7 @@ command = ["/usr/lib/drivers/ehcid"]
# control-transfer pass-through while the wider USB stack continues converging. # control-transfer pass-through while the wider USB stack continues converging.
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x0C class = 0x0C
subclass = 0x03 subclass = 0x03
prog_if = 0x20 prog_if = 0x20
@@ -180,6 +196,7 @@ priority = 80
command = ["/usr/lib/drivers/ohcid"] command = ["/usr/lib/drivers/ohcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x0C class = 0x0C
subclass = 0x03 subclass = 0x03
prog_if = 0x10 prog_if = 0x10
@@ -191,6 +208,7 @@ priority = 80
command = ["/usr/lib/drivers/uhcid"] command = ["/usr/lib/drivers/uhcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x0C class = 0x0C
subclass = 0x03 subclass = 0x03
prog_if = 0x00 prog_if = 0x00
@@ -206,6 +224,7 @@ priority = 60
command = ["/usr/bin/redox-drm"] command = ["/usr/bin/redox-drm"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x03 class = 0x03
""" """
@@ -233,6 +252,7 @@ priority = 40
command = ["/usr/lib/drivers/ihdad"] command = ["/usr/lib/drivers/ihdad"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 0x04 class = 0x04
@@ -243,10 +263,89 @@ priority = 40
command = ["/usr/lib/drivers/ac97d"] command = ["/usr/lib/drivers/ac97d"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x04 class = 0x04
subclass = 0x01 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]]
path = "/lib/drivers.d/60-gpio-i2c.toml"
data = """
# I2C bus registry — infrastructure, no hardware match
[[driver]]
name = "i2cd"
description = "I2C host adapter registry"
priority = 85
command = ["/usr/lib/drivers/i2cd"]
# GPIO pin registry — infrastructure, no hardware match
[[driver]]
name = "gpiod"
description = "GPIO controller registry"
priority = 85
command = ["/usr/lib/drivers/gpiod"]
# Intel ACPI I2C controller (DesignWare)
# Matches: INT33C3, INT3433, INT3442, INT3446, INT3447, INT3455, INT34B9
[[driver]]
name = "dw-acpi-i2cd"
description = "DesignWare ACPI I2C controller"
priority = 80
command = ["/usr/lib/drivers/dw-acpi-i2cd"]
depends_on = ["acpi", "i2c"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x05
vendor = 0x8086
# AMD MP2 I2C controller
# Matches: AMDI0010, AMDI0510, AMDI0019
[[driver]]
name = "amd-mp2-i2cd"
description = "AMD MP2 I2C controller"
priority = 80
command = ["/usr/lib/drivers/amd-mp2-i2cd"]
depends_on = ["acpi", "i2c"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x05
vendor = 0x1022
# Intel ACPI GPIO controller
# Matches: INT33C7, INT3437, INT3450, INT345D, INT34BB
[[driver]]
name = "intel-gpiod"
description = "Intel ACPI GPIO registrar"
priority = 80
command = ["/usr/lib/drivers/intel-gpiod"]
depends_on = ["acpi", "gpio"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x80
vendor = 0x8086
"""
[[files]] [[files]]
path = "/lib/drivers.d/70-usb-class.toml" path = "/lib/drivers.d/70-usb-class.toml"
data = """ data = """
@@ -289,7 +388,7 @@ data = """
[unit] [unit]
description = "Red Bear driver manager" description = "Red Bear driver manager"
requires_weak = [ requires_weak = [
"00_base.target", "02_early_hw.target",
] ]
[service] [service]
@@ -298,33 +397,26 @@ args = ["--hotplug"]
type = "oneshot_async" 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]] [[files]]
path = "/etc/init.d/10_evdevd.service" path = "/etc/init.d/30_thermald.service"
data = """ data = """
[unit] [unit]
description = "Evdev input daemon" description = "Thermal management daemon (suppressed; use 15_thermald.service)"
requires_weak = [
"12_boot-late.target",
"00_driver-manager.service",
]
[service] [service]
cmd = "evdevd" cmd = "echo"
type = "oneshot_async" args = ["thermald: started earlier as 15_thermald.service"]
type = "oneshot"
""" """
[[files]]
path = "/etc/firmware-fallbacks.d"
data = ""
directory = true
mode = 0o755
[[files]] [[files]]
path = "/etc/init.d/15_cpufreqd.service" path = "/etc/init.d/15_cpufreqd.service"
data = """ data = """
[unit] [unit]
description = "CPU frequency scaling daemon" description = "CPU frequency scaling daemon"
requires_weak = ["12_boot-late.target"] requires_weak = ["04_drivers.target"]
[service] [service]
cmd = "/usr/bin/cpufreqd" cmd = "/usr/bin/cpufreqd"
@@ -336,7 +428,7 @@ path = "/etc/init.d/15_thermald.service"
data = """ data = """
[unit] [unit]
description = "Thermal management daemon" description = "Thermal management daemon"
requires_weak = ["12_boot-late.target"] requires_weak = ["04_drivers.target"]
[service] [service]
cmd = "/usr/bin/thermald" cmd = "/usr/bin/thermald"
@@ -372,7 +464,7 @@ path = "/etc/init.d/16_redbear-acmd.service"
data = """ data = """
[unit] [unit]
description = "USB CDC ACM serial daemon" description = "USB CDC ACM serial daemon"
requires_weak = ["12_boot-late.target"] requires_weak = ["04_drivers.target"]
[service] [service]
cmd = "/usr/bin/redbear-acmd" cmd = "/usr/bin/redbear-acmd"
@@ -384,7 +476,7 @@ path = "/etc/init.d/16_redbear-ecmd.service"
data = """ data = """
[unit] [unit]
description = "USB CDC ECM/NCM ethernet daemon" description = "USB CDC ECM/NCM ethernet daemon"
requires_weak = ["12_boot-late.target"] requires_weak = ["04_drivers.target"]
[service] [service]
cmd = "/usr/bin/redbear-ecmd" cmd = "/usr/bin/redbear-ecmd"
@@ -396,7 +488,7 @@ path = "/etc/init.d/16_redbear-usbaudiod.service"
data = """ data = """
[unit] [unit]
description = "USB Audio Class daemon" description = "USB Audio Class daemon"
requires_weak = ["12_boot-late.target"] requires_weak = ["04_drivers.target"]
[service] [service]
cmd = "/usr/bin/redbear-usbaudiod" cmd = "/usr/bin/redbear-usbaudiod"
+7 -32
View File
@@ -237,7 +237,7 @@ data = """
[unit] [unit]
description = "Boot essential services target" description = "Boot essential services target"
requires_weak = [ requires_weak = [
"00_base.target", "04_drivers.target",
] ]
""" """
@@ -261,7 +261,7 @@ data = """
[unit] [unit]
description = "DRM/KMS display driver (AMD + Intel + VirtIO)" description = "DRM/KMS display driver (AMD + Intel + VirtIO)"
requires_weak = [ requires_weak = [
"05_boot-essential.target", "04_drivers.target",
] ]
[service] [service]
@@ -276,7 +276,7 @@ data = """
[unit] [unit]
description = "D-Bus system bus" description = "D-Bus system bus"
requires_weak = [ requires_weak = [
"12_boot-late.target", "06_services.target",
"00_ipcd.service", "00_ipcd.service",
] ]
@@ -292,6 +292,7 @@ data = """
[unit] [unit]
description = "Red Bear session broker (org.freedesktop.login1)" description = "Red Bear session broker (org.freedesktop.login1)"
requires_weak = [ requires_weak = [
"06_services.target",
"12_dbus.service", "12_dbus.service",
] ]
@@ -306,6 +307,7 @@ data = """
[unit] [unit]
description = "seatd seat management daemon" description = "seatd seat management daemon"
requires_weak = [ requires_weak = [
"06_services.target",
"12_dbus.service", "12_dbus.service",
"13_redbear-sessiond.service", "13_redbear-sessiond.service",
] ]
@@ -425,6 +427,7 @@ data = """
[unit] [unit]
description = "Red Bear greeter service" description = "Red Bear greeter service"
requires_weak = [ requires_weak = [
"08_userland.target",
"00_driver-manager.service", "00_driver-manager.service",
"14_redox-drm.service", "14_redox-drm.service",
"12_dbus.service", "12_dbus.service",
@@ -445,7 +448,7 @@ data = """
[unit] [unit]
description = "Activate fallback console VT" description = "Activate fallback console VT"
requires_weak = [ requires_weak = [
"05_boot-essential.target", "08_userland.target",
] ]
[service] [service]
@@ -517,34 +520,6 @@ members = ["greeter"]
gid = 100 gid = 100
members = ["messagebus"] members = ["messagebus"]
[[files]]
path = "/etc/pcid.d/ihdgd.toml"
data = """
[[drivers]]
name = "Intel GPU (VGA compatible)"
class = 0x03
vendor = 0x8086
subclass = 0x00
command = ["redox-drm"]
[[drivers]]
name = "Intel GPU (3D controller)"
class = 0x03
vendor = 0x8086
subclass = 0x02
command = ["redox-drm"]
"""
[[files]]
path = "/etc/pcid.d/virtio-gpud.toml"
data = """
[[drivers]]
name = "VirtIO GPU"
class = 0x03
vendor = 0x1af4
device = 0x1050
command = ["/usr/bin/redox-drm"]
"""
[[files]] [[files]]
path = "/etc/environment.d/90-dbus.conf" path = "/etc/environment.d/90-dbus.conf"
+2 -2
View File
@@ -8,7 +8,7 @@ data = """
[unit] [unit]
description = "Boot essential services target" description = "Boot essential services target"
requires_weak = [ requires_weak = [
"00_base.target", "04_drivers.target",
] ]
""" """
@@ -101,7 +101,7 @@ data = """
[unit] [unit]
description = "Activate fallback console VT" description = "Activate fallback console VT"
requires_weak = [ requires_weak = [
"05_boot-essential.target", "08_userland.target",
] ]
[service] [service]
+9 -3
View File
@@ -9,7 +9,7 @@
# - all non-graphics, non-firmware packages from the full profile # - all non-graphics, non-firmware packages from the full profile
# - no linux-firmware payload, no firmware-loader, no GPU/display drivers # - no linux-firmware payload, no firmware-loader, no GPU/display drivers
include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml"] include = ["minimal.toml", "redbear-legacy-base.toml", "redbear-netctl.toml", "redbear-device-services.toml", "redbear-boot-stages.toml"]
[general] [general]
filesystem_size = 1536 filesystem_size = 1536
@@ -231,6 +231,7 @@ path = "/etc/init.d/00_i2c-dw-acpi.service"
data = """ data = """
[unit] [unit]
description = "DesignWare ACPI I2C controller (non-blocking)" description = "DesignWare ACPI I2C controller (non-blocking)"
default_dependencies = false
requires_weak = [ requires_weak = [
"00_i2cd.service", "00_i2cd.service",
] ]
@@ -245,6 +246,7 @@ path = "/etc/init.d/00_intel-gpiod.service"
data = """ data = """
[unit] [unit]
description = "Intel ACPI GPIO registrar (non-blocking)" description = "Intel ACPI GPIO registrar (non-blocking)"
default_dependencies = false
requires_weak = [ requires_weak = [
"00_gpiod.service", "00_gpiod.service",
"00_i2cd.service", "00_i2cd.service",
@@ -260,6 +262,7 @@ path = "/etc/init.d/00_i2c-gpio-expanderd.service"
data = """ data = """
[unit] [unit]
description = "I2C GPIO expander companion bridge (non-blocking on live-mini)" description = "I2C GPIO expander companion bridge (non-blocking on live-mini)"
default_dependencies = false
requires_weak = [ requires_weak = [
"00_i2cd.service", "00_i2cd.service",
"00_gpiod.service", "00_gpiod.service",
@@ -275,6 +278,8 @@ path = "/etc/init.d/00_i2c-hidd.service"
data = """ data = """
[unit] [unit]
description = "ACPI I2C HID bring-up daemon (non-blocking)" description = "ACPI I2C HID bring-up daemon (non-blocking)"
default_dependencies = false
requires = ["00_acpid.service"]
requires_weak = [ requires_weak = [
"00_i2cd.service", "00_i2cd.service",
"00_i2c-dw-acpi.service", "00_i2c-dw-acpi.service",
@@ -292,6 +297,7 @@ path = "/etc/init.d/00_ucsid.service"
data = """ data = """
[unit] [unit]
description = "USB-C UCSI topology detector (non-blocking on live-mini)" description = "USB-C UCSI topology detector (non-blocking on live-mini)"
default_dependencies = false
requires_weak = [ requires_weak = [
"00_base.target", "00_base.target",
"00_i2cd.service", "00_i2cd.service",
@@ -306,9 +312,9 @@ type = { scheme = "ucsi" }
path = "/etc/init.d/12_boot-late.target" path = "/etc/init.d/12_boot-late.target"
data = """ data = """
[unit] [unit]
description = "Late boot services target" description = "Late boot services target (compat alias for 04_drivers.target)"
requires_weak = [ requires_weak = [
"00_base.target", "04_drivers.target",
] ]
""" """
+92
View File
@@ -1,5 +1,29 @@
# RED BEAR OS — DERIVATIVE OF REDOX OS # RED BEAR OS — DERIVATIVE OF REDOX OS
## INTERNAL-FIRST POLICY
When building packages, resolving dependencies, or choosing between implementations,
**always prefer internal Red Bear recipes and patched sources** over upstream or
third-party alternatives. This applies at every decision point:
| Decision | Always prefer |
|----------|--------------|
| Recipe location | `local/recipes/` over `recipes/` (upstream) |
| Patch source | `local/patches/` over in-tree edits or ad-hoc sed |
| Source tarball | Patched archive in `sources/redbear-0.1.0/tarballs/` over re-download |
| Implementation | Red Bear Rust implementation over upstream C port |
| Config | `config/redbear-*.toml` over mainline `config/*.toml` |
| Scripts | `local/scripts/` over ad-hoc shell commands |
**Concretely:** if `local/recipes/<category>/<name>/` exists and is symlinked into the
recipe tree, that is the authoritative recipe — never fall back to the upstream
`recipes/` version. If a local recipe has a `redox.patch`, that patch is the
maintained Red Bear delta — never work around it by editing the source tree directly.
**Rationale:** the local overlay is the durable, version-controlled, release-safe layer.
Upstream recipes are disposable and may be overwritten by `make distclean` or release
provisioning. Only `local/` survives across rebuilds and releases.
## TUI CONVENTION — `-i` INTERACTIVE SWITCH ## TUI CONVENTION — `-i` INTERACTIVE SWITCH
All Red Bear desktop applications that offer a TUI mode MUST use `-i`/`--interactive` All Red Bear desktop applications that offer a TUI mode MUST use `-i`/`--interactive`
@@ -50,6 +74,58 @@ files, Wayland protocol stubs, D-Bus service stubs, and any other layer of the s
**No exceptions. No "temporary." No "until we fix it properly."** **No exceptions. No "temporary." No "until we fix it properly."**
## BUILD DURABILITY AND CASCADE POLICY
### Every Build Lands in the Repo
Every successful `repo cook <package>` MUST produce two durable artifacts:
1. **Package in the repo**: `repo/x86_64-unknown-redox/<name>.pkgar` + `<name>.toml`
2. **Patched source form**: All source modifications mirrored to `local/patches/<component>/`
A build is **not complete** until both exist. Verify after every cook:
```bash
./target/release/repo find <package> # Must find the package
ls repo/x86_64-unknown-redox/<package>.toml # Manifest must exist
ls repo/x86_64-unknown-redox/<package>.pkgar # Archive must exist
```
If a package was built but the repo artifacts are missing, the build did not complete.
If source patches exist only in `recipes/*/source/` but not in `local/patches/`,
the patches are not durable (see Source-of-Truth Rule below).
### Cascade Rebuild Rule
When a low-level package changes, **all packages that transitively depend on it
must be rebuilt**. A stale dependent silently produces link errors, ABI mismatches,
or runtime crashes.
```bash
# Rebuild relibc and everything that depends on it
./local/scripts/rebuild-cascade.sh relibc
# Dry run: show what would be rebuilt without building
./local/scripts/rebuild-cascade.sh --dry-run relibc
# Multiple root packages
./local/scripts/rebuild-cascade.sh relibc ncurses
```
The script performs BFS over reverse dependencies: it finds all packages whose
`recipe.toml` lists the target in `dependencies`, transitively expands, then builds
root-first followed by dependents.
**Always use cascade rebuilds after changing:**
- relibc (headers, ABI, any patches)
- Kernel (syscall ABI changes)
- Shared libraries (ncurses, zlib, openssl, etc.)
- Any package listed in other packages' `dependencies`
**Example:** Changing relibc's `sys/types/internal.h` header requires rebuilding
bison, m4, flex, and every other gnulib-based package that includes system headers
through the relibc include chain.
## DESIGN PRINCIPLE ## DESIGN PRINCIPLE
Red Bear OS is a **full fork** based on frozen Redox OS snapshots: Red Bear OS is a **full fork** based on frozen Redox OS snapshots:
@@ -73,10 +149,21 @@ make all CONFIG_NAME=redbear-full
→ mk/config.mk resolves to the active desktop/graphics compile target → mk/config.mk resolves to the active desktop/graphics compile target
→ Desktop/graphics are available only on redbear-full → Desktop/graphics are available only on redbear-full
→ repo cook builds all packages from local sources (offline by default) → repo cook builds all packages from local sources (offline by default)
→ Each successful cook produces repo/<arch>/<name>.pkgar + <name>.toml
→ mk/disk.mk creates harddrive.img with Red Bear branding → mk/disk.mk creates harddrive.img with Red Bear branding
→ REDBEAR_RELEASE=0.1.0 ensures immutable, archived sources → REDBEAR_RELEASE=0.1.0 ensures immutable, archived sources
``` ```
Cascade rebuild flow (when a low-level package changes):
```
./local/scripts/rebuild-cascade.sh <package>
→ Finds all packages whose recipe.toml lists <package> in dependencies
→ BFS expands the reverse dependency graph
→ Builds root package first, then dependents in dependency order
→ Pushes all rebuilt packages to sysroot
→ Every rebuilt package lands in repo/ (.pkgar + .toml)
```
Release flow: Release flow:
``` ```
# Sources are immutable — build from archives, never from network # Sources are immutable — build from archives, never from network
@@ -259,6 +346,7 @@ redox-master/ ← git pull updates mainline Redox
│ │ └── images/ ← Red Bear OS icon (1254x1254) + loading bg (1536x1024) │ │ └── images/ ← Red Bear OS icon (1254x1254) + loading bg (1536x1024)
│ ├── firmware/ ← GPU firmware blobs (gitignored, fetched) │ ├── firmware/ ← GPU firmware blobs (gitignored, fetched)
│ ├── scripts/ │ ├── scripts/
│ │ ├── rebuild-cascade.sh ← Rebuild package + all dependents (BFS reverse-dep graph)
│ │ ├── provision-release.sh ← Provision new release from Redox ref │ │ ├── provision-release.sh ← Provision new release from Redox ref
│ │ ├── build-redbear.sh ← Unified Red Bear OS build script │ │ ├── build-redbear.sh ← Unified Red Bear OS build script
│ │ ├── fetch-firmware.sh ← Download bounded AMD or Intel firmware subsets from linux-firmware │ │ ├── fetch-firmware.sh ← Download bounded AMD or Intel firmware subsets from linux-firmware
@@ -311,6 +399,10 @@ scripts/build-iso.sh redbear-full # Full desktop live ISO
scripts/build-iso.sh redbear-mini # Text-only mini (default) scripts/build-iso.sh redbear-mini # Text-only mini (default)
scripts/build-iso.sh redbear-grub # Text-only + GRUB scripts/build-iso.sh redbear-grub # Text-only + GRUB
# Rebuild a package and all its dependents (cascade)
./local/scripts/rebuild-cascade.sh relibc # Rebuild relibc + all dependents
./local/scripts/rebuild-cascade.sh --dry-run ncurses # Show cascade without building
# VM-network baseline validation helpers # VM-network baseline validation helpers
./local/scripts/validate-vm-network-baseline.sh ./local/scripts/validate-vm-network-baseline.sh
./local/scripts/test-vm-network-qemu.sh redbear-mini ./local/scripts/test-vm-network-qemu.sh redbear-mini
+4
View File
@@ -7,6 +7,7 @@ priority = 100
command = ["/usr/lib/drivers/nvmed"] command = ["/usr/lib/drivers/nvmed"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 8 subclass = 8
@@ -17,6 +18,7 @@ priority = 100
command = ["/usr/lib/drivers/ahcid"] command = ["/usr/lib/drivers/ahcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 6 subclass = 6
@@ -27,6 +29,7 @@ priority = 100
command = ["/usr/lib/drivers/ided"] command = ["/usr/lib/drivers/ided"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 1 class = 1
subclass = 1 subclass = 1
@@ -37,6 +40,7 @@ priority = 100
command = ["/usr/lib/drivers/virtio-blkd"] command = ["/usr/lib/drivers/virtio-blkd"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
device = 0x1001 device = 0x1001
class = 1 class = 1
+5
View File
@@ -7,6 +7,7 @@ priority = 50
command = ["/usr/lib/drivers/e1000d"] command = ["/usr/lib/drivers/e1000d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 2 class = 2
@@ -17,6 +18,7 @@ priority = 50
command = ["/usr/lib/drivers/rtl8168d"] command = ["/usr/lib/drivers/rtl8168d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x10EC vendor = 0x10EC
class = 2 class = 2
@@ -27,6 +29,7 @@ priority = 50
command = ["/usr/lib/drivers/rtl8139d"] command = ["/usr/lib/drivers/rtl8139d"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x10EC vendor = 0x10EC
device = 0x8139 device = 0x8139
@@ -37,6 +40,7 @@ priority = 50
command = ["/usr/lib/drivers/ixgbed"] command = ["/usr/lib/drivers/ixgbed"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 2 class = 2
subclass = 0 subclass = 0
@@ -48,5 +52,6 @@ priority = 50
command = ["/usr/lib/drivers/virtio-netd"] command = ["/usr/lib/drivers/virtio-netd"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
class = 2 class = 2
+43
View File
@@ -44,6 +44,49 @@ priority = 80
command = ["/usr/lib/drivers/uhcid"] command = ["/usr/lib/drivers/uhcid"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x0C
subclass = 0x03
prog_if = 0x30
# EHCI (USB 2.0)
[[driver]]
name = "ehcid"
description = "EHCI USB 2.0 host controller"
priority = 80
command = ["/usr/lib/drivers/ehcid"]
# EHCI now owns a simple /scheme/usb controller surface for per-port status and
# control-transfer pass-through while the wider USB stack continues converging.
[[driver.match]]
bus = "pci"
class = 0x0C
subclass = 0x03
prog_if = 0x20
# OHCI (USB 1.1 — non-Intel chipsets)
[[driver]]
name = "ohcid"
description = "OHCI USB 1.1 host controller"
priority = 80
command = ["/usr/lib/drivers/ohcid"]
[[driver.match]]
bus = "pci"
class = 0x0C
subclass = 0x03
prog_if = 0x10
# UHCI (USB 1.1 — Intel chipsets)
[[driver]]
name = "uhcid"
description = "UHCI USB 1.1 host controller (Intel)"
priority = 80
command = ["/usr/lib/drivers/uhcid"]
[[driver.match]]
bus = "pci"
class = 0x0C class = 0x0C
subclass = 0x03 subclass = 0x03
prog_if = 0x00 prog_if = 0x00
+7
View File
@@ -7,6 +7,7 @@ priority = 60
command = ["/usr/lib/drivers/vesad"] command = ["/usr/lib/drivers/vesad"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x03 class = 0x03
[[driver]] [[driver]]
@@ -18,14 +19,17 @@ command = ["/usr/bin/redox-drm"]
# Only match known GPU vendors. Class 0x03 alone catches QEMU VGA # Only match known GPU vendors. Class 0x03 alone catches QEMU VGA
# (vendor 0x1234) which redox-drm rejects with a fatal error. # (vendor 0x1234) which redox-drm rejects with a fatal error.
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1002 vendor = 0x1002
class = 0x03 class = 0x03
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 0x03 class = 0x03
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
class = 0x03 class = 0x03
@@ -36,6 +40,7 @@ priority = 61
command = ["/usr/bin/redox-drm"] command = ["/usr/bin/redox-drm"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1AF4 vendor = 0x1AF4
class = 0x03 class = 0x03
@@ -47,6 +52,7 @@ priority = 61
command = ["/usr/bin/redox-drm"] command = ["/usr/bin/redox-drm"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 0x03 class = 0x03
subclass = 0x00 subclass = 0x00
@@ -59,6 +65,7 @@ priority = 61
command = ["/usr/bin/redox-drm"] command = ["/usr/bin/redox-drm"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x1002 vendor = 0x1002
class = 0x03 class = 0x03
subclass = 0x00 subclass = 0x00
+2
View File
@@ -7,6 +7,7 @@ priority = 40
command = ["/usr/lib/drivers/ihdad"] command = ["/usr/lib/drivers/ihdad"]
[[driver.match]] [[driver.match]]
bus = "pci"
vendor = 0x8086 vendor = 0x8086
class = 0x04 class = 0x04
@@ -17,6 +18,7 @@ priority = 40
command = ["/usr/lib/drivers/ac97d"] command = ["/usr/lib/drivers/ac97d"]
[[driver.match]] [[driver.match]]
bus = "pci"
class = 0x04 class = 0x04
subclass = 0x01 subclass = 0x01
+95 -5
View File
@@ -1,49 +1,139 @@
# GPIO and I2C controller drivers # GPIO and I2C controller drivers
#
# These drivers match against both PCI and ACPI devices.
# ACPI devices are classified by _HID → PCI-equivalent class/subclass/vendor
# codes via redox-driver-acpi's classify_acpi_device().
#
# Match criteria use the standard [[driver.match]] format with class/subclass/vendor.
# The ACPI bus fills these fields from the _HID classification table.
# --- I2C/SPI controller infrastructure ---
[[driver]] [[driver]]
name = "i2cd" name = "i2cd"
description = "I2C host adapter registry" description = "I2C host adapter registry"
priority = 85 priority = 85
command = ["/usr/lib/drivers/i2cd"] command = ["/usr/lib/drivers/i2cd"]
# i2cd is the I2C bus registry — spawned as infrastructure before
# specific I2C controller drivers. Does not match against hardware
# directly; it provides /scheme/i2c for controller drivers to register with.
[[driver]] [[driver]]
name = "gpiod" name = "gpiod"
description = "GPIO controller registry" description = "GPIO controller registry"
priority = 85 priority = 85
command = ["/usr/lib/drivers/gpiod"] command = ["/usr/lib/drivers/gpiod"]
# gpiod is the GPIO pin registry — spawned as infrastructure before
# specific GPIO controller drivers. Does not match against hardware
# directly; it provides /scheme/gpio for controller drivers to register with.
# --- ACPI I2C controller drivers ---
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
# subclass SMBus/I2C (0x05), by the ACPI bus.
# The ACPI bus maps Intel INT33C3/INT3433/... and AMD AMDI0010 HIDs to these codes.
[[driver]] [[driver]]
name = "dw-acpi-i2cd" name = "dw-acpi-i2cd"
description = "DesignWare ACPI I2C controller" description = "DesignWare ACPI I2C controller"
priority = 80 priority = 80
command = ["/usr/lib/drivers/dw-acpi-i2cd"] command = ["/usr/lib/drivers/dw-acpi-i2cd"]
depends_on = ["acpi", "i2c"]
[[driver]] [[driver.match]]
name = "intel-gpiod" bus = "acpi"
description = "Intel ACPI GPIO registrar" class = 0x0C
priority = 80 subclass = 0x05
command = ["/usr/lib/drivers/intel-gpiod"] vendor = 0x8086
[[driver]] [[driver]]
name = "amd-mp2-i2cd" name = "amd-mp2-i2cd"
description = "AMD MP2 I2C controller" description = "AMD MP2 I2C controller"
priority = 80 priority = 80
command = ["/usr/lib/drivers/amd-mp2-i2cd"] command = ["/usr/lib/drivers/amd-mp2-i2cd"]
depends_on = ["acpi", "i2c"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x05
vendor = 0x1022
[[driver]] [[driver]]
name = "intel-lpss-i2cd" name = "intel-lpss-i2cd"
description = "Intel LPSS I2C controller" description = "Intel LPSS I2C controller"
priority = 80 priority = 80
command = ["/usr/lib/drivers/intel-lpss-i2cd"] command = ["/usr/lib/drivers/intel-lpss-i2cd"]
depends_on = ["acpi", "i2c"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x05
vendor = 0x8086
# --- ACPI SPI controller drivers ---
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
# subclass SPI (0x06), by the ACPI bus.
[[driver]]
name = "intel-lpss-spid"
description = "Intel LPSS SPI controller"
priority = 80
command = ["/usr/lib/drivers/intel-lpss-spid"]
depends_on = ["acpi"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x06
vendor = 0x8086
# --- ACPI GPIO controller drivers ---
# These match against ACPI devices classified as Serial Bus Controller (0x0C),
# subclass Other (0x80), vendor Intel, by the ACPI bus.
# The ACPI bus maps INT33C7/INT3437/INT3450 HIDs to these codes.
[[driver]]
name = "intel-gpiod"
description = "Intel ACPI GPIO registrar"
priority = 80
command = ["/usr/lib/drivers/intel-gpiod"]
depends_on = ["acpi", "gpio"]
[[driver.match]]
bus = "acpi"
class = 0x0C
subclass = 0x80
vendor = 0x8086
# --- ACPI thermal/power drivers ---
# These match against ACPI devices classified as Thermal/Battery (0x0B).
[[driver]]
name = "redbear-thermald"
description = "ACPI thermal zone monitor"
priority = 60
command = ["/usr/lib/drivers/redbear-thermald"]
depends_on = ["acpi"]
[[driver.match]]
bus = "acpi"
class = 0x0B
# --- I2C companion drivers ---
# These depend on I2C bus being available and match against specific
# I2C device addresses (not PCI/ACPI class matching).
[[driver]] [[driver]]
name = "i2c-gpio-expanderd" name = "i2c-gpio-expanderd"
description = "I2C GPIO expander companion bridge" description = "I2C GPIO expander companion bridge"
priority = 75 priority = 75
command = ["/usr/lib/drivers/i2c-gpio-expanderd"] command = ["/usr/lib/drivers/i2c-gpio-expanderd"]
depends_on = ["i2c", "gpio"]
[[driver]] [[driver]]
name = "intel-thc-hidd" name = "intel-thc-hidd"
description = "Intel THC QuickI2C HID transport" description = "Intel THC QuickI2C HID transport"
priority = 75 priority = 75
command = ["/usr/lib/drivers/intel-thc-hidd"] command = ["/usr/lib/drivers/intel-thc-hidd"]
depends_on = ["acpi", "i2c"]
@@ -0,0 +1,846 @@
# Red Bear OS: Boot Process & Hardware Detection Improvement Plan
**Version:** 1.5 (2026-05-15)
**Reference:** Linux 7.1-rc3 (`local/reference/linux-7.1/`)
**Status:** Canonical plan for boot efficiency, hardware detection completeness, and init ordering
## Implementation Status (2026-05-15)
**Approach changed:** Instead of creating a separate `redbear-hwdetect` daemon, we are
**enhancing the existing `driver-manager`** with ACPI bus support and boot stage targets.
This builds on the existing `redox-driver-core` device model (DeviceId, DeviceInfo, Bus,
Driver, DeviceManager) rather than duplicating it.
### Completed
| Wave | Status | What was done |
|------|--------|---------------|
| Wave 0 | ✅ Done | Created `config/redbear-boot-stages.toml` with 4 stage targets (02_early_hw, 04_drivers, 06_services, 08_userland) + serial boot markers |
| Wave 1 | ✅ Done | Created `local/recipes/drivers/redox-driver-acpi/` with `AcpiBus` that enumerates ACPI devices from `/scheme/acpi/symbols/`. Registered in `driver-manager` alongside `PciBus`. Added `_HID`-based device classification (maps ~40 ACPI hardware IDs to PCI-equivalent class/subclass/vendor). 15 unit tests pass. |
| Wave 2 | ✅ Done | Created `resource.rs` — ACPI resource descriptor parser (raw byte buffers → typed structs for IRQ, MMIO, I/O port, DMA, address spaces). Covers all 25 ACPI resource types (types 0-25). Created `prt.rs` — _PRT PCI IRQ routing table resolver (parses RON-serialized Package-of-Packages, resolves static GSI and dynamic link device routing). Fixed `bus.rs` to use child symbol lookup for `_HID`/`_CID` (the RON `Device` variant is unit — properties are separate namespace children). Added `query_device_resources()` API to AcpiBus. 20+ new unit tests across all modules. |
| Wave 2b | ✅ Done | Extended `driver-manager/config.rs` `probe()` to handle ACPI device binding alongside PCI. ACPI devices get `ACPI_DEVICE_PATH`, `ACPI_DEVICE_NAME`, `ACPI_MMIO_N`, `ACPI_IRQ_N`, `ACPI_IO_N` env vars passed to spawned drivers. PCI devices continue using `PCID_CLIENT_CHANNEL`/`PCID_DEVICE_PATH`. Updated `scheme.rs` to accept ACPI device names in the scheme namespace (relaxed PCI-only validation). `main.rs` now notifies bound devices for both buses. |
| Wave 2c | ✅ Done | Created ACPI driver config with match criteria in `60-gpio-i2c.toml` — Intel I2C (class=0x0C/sub=0x05/vendor=0x8086 → dw-acpi-i2cd), AMD I2C (class=0x0C/sub=0x05/vendor=0x1022 → amd-mp2-i2cd), Intel GPIO (class=0x0C/sub=0x80/vendor=0x8086 → intel-gpiod). Wired into `redbear-device-services.toml` as `/lib/drivers.d/60-gpio-i2c.toml`. Infrastructure daemons (i2cd, gpiod) remain as init services (scheme providers); controller drivers are dual-pathed (init fallback + driver-manager matching). |
| Wave 3 | ✅ Done | Rewired all services in `redbear-device-services.toml`, `redbear-mini.toml`, `redbear-full.toml` to use stage targets instead of flat `00_base.target` |
| Wave 4 | ✅ Done | Removed dead `/etc/pcid.d/` entries from `redbear-mini.toml` and `redbear-full.toml`. Confirmed no runtime binary reads `/etc/pcid.d/`. All driver matching now uses `/lib/drivers.d/`. |
| Wave 5 | ✅ Already had | `driver-manager/config.rs` already has scheme-aware deferred probing via `check_scheme_available()` + `depends_on` field |
### Not Yet Started
| Wave | Status | What remains |
|------|--------|---------------|
| Wave 2c | Not started | Runtime _CRS evaluation via ACPI scheme `call()` interface for Method-type _CRS (currently only Buffer-type _CRS is parsed). Link device _CRS resolution for dynamic _PRT entries. Full image build verification. |
### Config consistency verified (2026-05-15)
All `requires_weak` references in config files resolve to valid targets or services:
- `00_base.target` — staged by `base` package at `/usr/lib/init.d/00_base.target`
- Stage targets (`02_early_hw` through `08_userland`) — defined in `redbear-boot-stages.toml`
- `12_boot-late.target` — compat alias defined in `redbear-device-services.toml`
- `05_boot-essential.target` — defined in `redbear-full.toml` and `redbear-greeter-services.toml`
- All service dependencies have corresponding `[[files]]` entries or package-staged definitions
---
## Purpose
This document is the execution plan for making the Red Bear OS boot process **stellar**:
efficient, complete, and — above all — featuring **perfect hardware detection and initialization**.
It is grounded in a comprehensive study of Linux 7.1-rc3's boot flow (`init/main.c`,
`drivers/base/`, `drivers/pci/`, `drivers/acpi/`) and maps Linux's proven patterns
to Red Bear OS's microkernel architecture.
## Honest Current State
### What works today
- UEFI boot on x86_64 (bootloader → kernel → initfs → init → login)
- ACPI boot-baseline: RSDP/SDT/MADT/FADT/HPET parsing in kernel
- PCI enumeration via `pcid` + driver matching via `driver-manager`
- Wired networking (e1000d, rtl8168d, virtio-netd) in QEMU
- PS/2 keyboard/mouse via kernel `serio` scheme
- Framebuffer text console via `vesad`
- Multi-core x2APIC/SMP works
- Greeter/login QEMU proof passes on `redbear-full`
### What is broken or missing (THESE ARE THE GAPS)
| Gap | Linux equivalent | RedBear status |
|-----|-----------------|----------------|
| **No unified hardware detection** | `start_kernel()``driver_init()` → initcalls | Fragmented across `pcid`, `acpid`, `hwd`, `driver-manager` |
| **No device model** | `struct device`, `struct driver`, `struct bus_type` | No common device/driver/bus abstraction |
| **No ACPI device enumeration** | `acpi_bus_scan()` walks namespace, creates platform devices | `acpid` parses tables but doesn't enumerate devices |
| **No deferred probe with real semantics** | `-EPROBE_DEFER` + retry queue in `driver_deferred_probe_trigger()` | `driver-manager` has a 30-retry loop but no dependency graph |
| **No device resource tracking** | `request_region()`, `request_irq()`, `ioremap()` with resource tree | BARs mapped ad-hoc per driver, no global resource registry |
| **No boot-stage ordering** | initcall levels (core → postcore → arch → subsys → device → late) | Flat `requires_weak` everywhere; no semantic stages |
| **PCI enumeration too late** | PCI scanned at `subsys_initcall` level (level 4) | `driver-manager` is a userspace service with no hard dependency |
| **No platform/I2C/SPI device discovery** | ACPI `_HID`/`_CID` creates platform/i2c/spi devices | I2C/SPI daemons exist but no device enumeration from ACPI |
| **No USB device enumeration** | `usb_new_device()` → device descriptor → class matching | xHCI controller starts but no USB topology enumeration |
| **No sysfs/udev equivalent** | `/sys/devices/` tree + udev rules | `udev-shim` exists but is minimal |
| **Silent service failures** | Kernel oops if critical subsystem fails | `requires_weak` + `oneshot_async` → failures are invisible |
## Architecture: What Linux Does That We Must Reimplement
### Linux Boot Flow (from `init/main.c`)
```
start_kernel()
├── setup_arch() → arch-specific: page tables, early param parsing
├── trap_init() → IDT/exception vectors
├── mm_init() → memory management, slab allocator
├── sched_init() → scheduler
├── early_irq_init() → early IRQ descriptors
├── init_IRQ() → architecture IRQ controllers (IOAPIC, LAPIC)
├── time_init() → HPET/PIT/timers
├── console_init() → early console
├── driver_init() → device model core (kobject, sysfs, bus, class)
└── rest_init()
└── kernel_init()
└── do_basic_setup()
└── do_initcalls()
├── level 0 (core): kobject, debugfs, kernel core
├── level 1 (postcore): driver core, workqueue
├── level 2 (arch): arch-specific devices
├── level 3 (subsys): PCI, ACPI, network stack
├── level 4 (fs): filesystems
├── level 5 (device): device drivers
└── level 6 (late): late drivers, networking
```
### Linux Device Model (from `drivers/base/`)
Three core abstractions:
1. **`struct bus_type`** — PCI, ACPI, platform, USB, I2C, SPI
2. **`struct device`** — represents hardware, has parent, bus, driver, resources
3. **`struct device_driver`** — probe/remove/shutdown callbacks, ID table
Binding flow:
```
bus->probe(dev) → driver->probe(dev, id) → device bound to driver
```
Deferred probing (`drivers/base/dd.c`):
```
driver_probe_device() returns -EPROBE_DEFER
→ device added to deferred_probe_pending_list
→ driver_deferred_probe_trigger() retries on schedule
→ wake_up_all() after each successful bind
```
### Linux ACPI Device Discovery (from `drivers/acpi/scan.c`)
```
acpi_init()
└── acpi_bus_scan()
└── acpi_walk_namespace()
├── Read _HID (hardware ID)
├── Read _CID (compatible IDs)
├── Read _STA (status: present, enabled, functional)
├── Read _CRS (current resource settings: IRQ, MMIO, I/O ports)
└── Create device:
├── PCI root bridge → pci_scan_child_bus()
├── I2C controller → i2c_register_adapter()
├── SPI controller → spi_register_controller()
├── GPIO controller → gpiochip_add()
├── Platform device → platform_device_register()
└── Thermal zone → thermal_zone_device_register()
```
### Linux PCI Enumeration (from `drivers/pci/probe.c`)
```
pci_scan_child_bus(bus)
for devfn in 0..0xFF:
pci_scan_slot(bus, devfn)
├── Read PCI_VENDOR_ID → skip if 0xFFFFFFFF
├── Read PCI_HEADER_TYPE → multifunction?
├── Read PCI_CLASS, PCI_REVISION
├── Read BARs (6 base address registers)
├── Parse capability chain (MSI, MSI-X, PCIe, power management)
├── Assign IRQ (from ACPI _PRT or BIOS)
├── If PCI bridge: recursively scan subordinate bus
└── Register device → driver core → bus_probe_device()
```
## Design: RedBear OS Hardware Detection Architecture
### Core Principle
**RedBear OS is a microkernel.** Unlike Linux where everything runs in kernel space,
RedBear OS runs all drivers as **userspace daemons** accessing hardware through schemes.
This means our "device model" lives in **userspace**, not in the kernel. The kernel provides:
- `scheme:irq` — interrupt delivery
- `scheme:memory` — physical memory mapping
- `scheme:pci` — PCI config space access
- `scheme:acpi` — ACPI table access
- `scheme:serio` — PS/2 controller
Everything else — device discovery, driver matching, resource allocation — is userspace.
### Proposed Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Kernel (microkernel) │
│ schemes: irq, memory, pci, acpi, serio, event, time │
│ ACPI early: RSDP, MADT (LAPIC/IOAPIC), HPET │
│ x2APIC/SMP: AP startup, interrupt routing │
└───────────────────────┬─────────────────────────────────┘
│ scheme IPC
┌───────────────────────▼─────────────────────────────────┐
│ redbear-hwdetect (NEW DAEMON) │
│ unified hardware detection & device registry │
│ │
│ 1. PCI bus walk (via scheme:pci) │
│ → enumerate all devices, parse BARs/caps/IRQ │
│ → build device tree with parent-child relationships │
│ │
│ 2. ACPI device scan (via scheme:acpi + acpid) │
│ → walk ACPI namespace for _HID/_CID/_STA/_CRS │
│ → create platform/I2C/SPI devices from ACPI │
│ → resolve PCI IRQ routing via _PRT │
│ │
│ 3. USB topology (via xhcid scheme) │
│ → enumerate USB devices on each controller │
│ → match by class/vendor/product │
│ │
│ 4. Driver matching │
│ → match devices to /lib/drivers.d/*.toml │
│ → spawn driver daemons with correct resources │
│ → deferred retry with real dependency tracking │
│ │
│ 5. Device registry (scheme:hwdetect) │
│ → /scheme/hwdetect/devices → list all detected HW │
│ → /scheme/hwdetect/pci/{bdf} → per-device info │
│ → /scheme/hwdetect/acpi/{path} → per-ACPI device │
│ → /scheme/hwdetect/drivers → driver status │
│ → JSON output for diagnostics │
│ │
│ Registers scheme: hwdetect │
└─────────────────────────────────────────────────────────┘
```
### Why Enhance driver-manager Instead of Creating a New Daemon
> **Decision (2026-05-15):** We chose to enhance the existing `driver-manager` instead of
> creating `redbear-hwdetect`. The `redox-driver-core` crate already provides a solid device
> model (DeviceId, DeviceInfo, Bus trait, Driver trait, DeviceManager with deferred probing),
> and `driver-manager` already uses it for PCI enumeration. Adding ACPI bus support as a
> second `Bus` implementation follows the established pattern and avoids duplicating the
> device model, driver matching, and deferred probe logic.
The current `driver-manager` does PCI matching but:
- No ACPI device enumeration
- No USB topology
- No device tree
- No resource tracking
- No parent-child relationships
- Deferred retry is naive (fixed interval, no dependency graph)
Rather than bolting more onto `driver-manager`, the original plan was to create `redbear-hwdetect` as the
**single source of truth** for hardware state, and `driver-manager` becomes a thin
consumer of its device registry. **However, since `redox-driver-core` already provides the
device model abstractions, we enhance `driver-manager` by registering additional `Bus`
implementations (ACPI, and eventually USB).**
## Implementation Plan
### Wave 0: Boot Stage Definitions (config-only, zero code)
**Goal:** Replace the flat `requires_weak` service model with explicit boot stages.
**Current problem:** Every service uses `requires_weak = ["00_base.target"]` which means
no real ordering guarantee. Services can start in any order and silently fail.
**Linux equivalent:** initcall levels (core → postcore → arch → subsys → device → late)
**Proposed boot stages:**
```
Stage 0: PLATFORM — kernel schemes ready (irq, memory, pci, acpi, serio)
Stage 1: CORE — tmpdir, logging, random, null/zero
Stage 2: EARLY_HW — acpid (ACPI tables), pcid (PCI bus access)
Stage 3: BUS_ENUM — redbear-hwdetect (PCI walk, ACPI scan, USB topology)
Stage 4: DRIVERS — driver spawning (storage, network, GPU, audio, USB class)
Stage 5: LATE_HW — IOMMU, firmware loading, NUMA topology
Stage 6: SERVICES — D-Bus, session broker, seat management
Stage 7: USERLAND — console, greeter, desktop
```
**Implementation:** Add target files:
```toml
# /etc/init.d/00_platform.target
[unit]
description = "Platform stage: kernel schemes ready"
# /etc/init.d/01_core.target
[unit]
description = "Core stage: basic services"
requires = ["00_platform.target"]
# /etc/init.d/02_early_hw.target
[unit]
description = "Early hardware: ACPI + PCI bus access"
requires = ["01_core.target"]
# /etc/init.d/03_bus_enum.target
[unit]
description = "Bus enumeration: PCI walk + ACPI scan"
requires = ["02_early_hw.target"]
# /etc/init.d/04_drivers.target
[unit]
description = "Driver spawning stage"
requires = ["03_bus_enum.target"]
# /etc/init.d/05_late_hw.target
[unit]
description = "Late hardware: firmware, IOMMU, NUMA"
requires = ["04_drivers.target"]
# /etc/init.d/06_services.target
[unit]
description = "System services: D-Bus, session broker"
requires = ["05_late_hw.target"]
# /etc/init.d/07_userland.target
[unit]
description = "User-facing: console, greeter, desktop"
requires = ["06_services.target"]
```
**Key change:** Use `requires` (hard dependency, blocks if not met) instead of
`requires_weak` for stages. Services within a stage use `requires_weak` against
their stage target.
### Wave 1: redbear-hwdetect — The Unified Hardware Detection Daemon
**Goal:** Create a single daemon that discovers ALL hardware, builds a device tree,
and manages driver lifecycle.
**Source location:** `local/recipes/system/redbear-hwdetect/source/`
**Cargo.toml:**
```toml
[package]
name = "redbear-hwdetect"
version = "0.1.0"
edition = "2024"
[dependencies]
redox-daemon = "0.1"
redox-scheme = "0.11"
libredox = "0.1"
redox_syscall = "0.7"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
log = "0.4"
[features]
default = []
```
**Module structure:**
```
redbear-hwdetect/source/src/
├── main.rs — daemon entry, scheme registration, event loop
├── device.rs — Device trait, DeviceInfo, DeviceType, DeviceStatus
├── registry.rs — DeviceRegistry: HashMap<DeviceId, DeviceInfo>
├── pci/
│ ├── mod.rs — PciEnumerator: bus walk via scheme:pci
│ ├── config.rs — PCI config space reader
│ ├── capability.rs — PCI capability chain parser (MSI, MSI-X, PCIe, PM)
│ └── resource.rs — BAR parsing, IRQ assignment, resource allocation
├── acpi/
│ ├── mod.rs — AcpiScanner: device enumeration from ACPI tables
│ ├── namespace.rs — ACPI namespace walker (via acpid)
│ ├── resource.rs — _CRS parser (IRQ, MMIO, I/O port resources)
│ └── pci_routing.rs — _PRT (PCI IRQ routing table) resolver
├── usb/
│ ├── mod.rs — UsbScanner: USB topology via xHCI schemes
│ └── descriptor.rs — USB device/class descriptor parsing
├── driver/
│ ├── mod.rs — DriverMatcher: load /lib/drivers.d/*.toml
│ ├── match.rs — Device-driver matching (class, vendor, subclass)
│ └── spawn.rs — Driver process spawning with resource handoff
├── deferred.rs — Deferred probe queue with dependency graph
└── scheme.rs — scheme:hwdetect handler
```
**Key data structures:**
```rust
/// Unique device identifier
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub enum DeviceId {
Pci { domain: u16, bus: u8, device: u8, function: u8 },
Acpi { path: String }, // ACPI namespace path (e.g., "\_SB.PCI0.I2C0")
Usb { controller: u8, port: u8, address: u8 },
Platform { name: String, id: u32 },
}
/// Device information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
pub id: DeviceId,
pub device_type: DeviceType,
pub status: DeviceStatus,
pub vendor_id: Option<u16>,
pub device_id: Option<u16>,
pub class_code: Option<u8>,
pub subclass_code: Option<u8>,
pub prog_if: Option<u8>,
pub revision: Option<u8>,
pub parent: Option<DeviceId>,
pub resources: Vec<Resource>,
pub driver: Option<DriverInfo>,
pub quirks: Vec<String>,
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeviceType {
PciDevice,
PciBridge,
AcpiDevice,
UsbController,
UsbDevice,
PlatformDevice,
I2cController,
I2cDevice,
SpiController,
SpiDevice,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeviceStatus {
Detected, // Found during scan, not yet probed
Probing, // Driver probe in progress
Bound, // Driver successfully bound
Deferred, // Probe deferred (dependency not ready)
Failed(String), // Probe failed permanently
NoDriver, // No matching driver found
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource {
pub resource_type: ResourceType,
pub base: u64,
pub size: u64,
pub flags: ResourceFlags,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ResourceType {
Mmio, // Memory-mapped I/O
IoPort, // I/O port range
Irq, // Interrupt (GSI number)
Dma, // DMA channel/range
Firmware, // Required firmware blob
}
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct ResourceFlags: u32 {
const PREFETCHABLE = 0x01;
const CACHEABLE = 0x02;
const SHARED = 0x04;
const MSI = 0x08;
const MSI_X = 0x10;
}
}
/// Driver match rule (from /lib/drivers.d/*.toml)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriverMatch {
pub vendor: Option<u16>,
pub device: Option<u16>,
pub class: Option<u8>,
pub subclass: Option<u8>,
pub prog_if: Option<u8>,
}
/// Driver configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriverConfig {
pub name: String,
pub description: String,
pub priority: u32,
pub command: Vec<String>,
pub depends_on: Vec<String>, // scheme names that must exist before spawn
pub matches: Vec<DriverMatch>,
}
```
**PCI enumeration flow (from Linux `drivers/pci/probe.c`):**
```rust
impl PciEnumerator {
/// Walk all PCI buses (mirrors Linux pci_scan_child_bus)
pub fn scan_all_buses(&mut self) -> Result<Vec<DeviceInfo>> {
let mut devices = Vec::new();
// Read from scheme:pci — get all PCI devices
for entry in self.read_pci_scheme()? {
let domain = entry.domain;
let bus = entry.bus;
let dev = entry.device;
let func = entry.function;
// Read config space (mirrors Linux pci_scan_slot)
let config = self.read_config(domain, bus, dev, func)?;
// Skip invalid devices (vendor 0xFFFF)
if config.vendor_id == 0xFFFF {
continue;
}
// Parse device info (mirrors Linux pci_setup_device)
let mut device = DeviceInfo {
id: DeviceId::Pci { domain, bus, device: dev, function: func },
device_type: if config.is_bridge() {
DeviceType::PciBridge
} else {
DeviceType::PciDevice
},
status: DeviceStatus::Detected,
vendor_id: Some(config.vendor_id),
device_id: Some(config.device_id),
class_code: Some(config.class_code),
subclass_code: Some(config.subclass_code),
prog_if: Some(config.prog_if),
revision: Some(config.revision),
parent: self.find_parent_bridge(domain, bus),
resources: Vec::new(),
driver: None,
quirks: Vec::new(),
description: format!("PCI {:04x}:{:02x}:{:02x}.{} [{:04x}:{:04x}]",
domain, bus, dev, func, config.vendor_id, config.device_id),
};
// Parse BARs (mirrors Linux pci_read_bases)
for bar_idx in 0..6 {
if let Some(resource) = self.parse_bar(domain, bus, dev, func, bar_idx)? {
device.resources.push(resource);
}
}
// Parse capability chain (mirrors Linux pci_init_capabilities)
self.parse_capabilities(&mut device, domain, bus, dev, func)?;
// Assign IRQ (from ACPI _PRT or IOAPIC routing)
if let Some(irq) = self.assign_irq(&device)? {
device.resources.push(Resource {
resource_type: ResourceType::Irq,
base: irq as u64,
size: 1,
flags: ResourceFlags::empty(),
});
}
// Apply quirks
self.apply_quirks(&mut device)?;
devices.push(device);
}
Ok(devices)
}
}
```
**Deferred probe with real dependency graph (from Linux `drivers/base/dd.c`):**
```rust
pub struct DeferredQueue {
/// Devices waiting for dependencies
pending: HashMap<DeviceId, Vec<String>>, // device → missing dependencies
/// Maximum retries per device
max_retries: u32,
/// Retry interval in ms
retry_interval: u64,
}
impl DeferredQueue {
/// Add a deferred device (mirrors Linux driver_deferred_probe_add)
pub fn add(&mut self, device_id: DeviceId, missing_deps: Vec<String>) {
self.pending.insert(device_id, missing_deps);
}
/// Retry all deferred devices (mirrors Linux driver_deferred_probe_trigger)
pub fn retry_cycle(&mut self, registry: &mut DeviceRegistry) -> Vec<DeviceInfo> {
let mut resolved = Vec::new();
// Check each deferred device
let pending_ids: Vec<DeviceId> = self.pending.keys().cloned().collect();
for id in &pending_ids {
if let Some(missing) = self.pending.get(id) {
// Check if all dependencies are now available
let all_ready = missing.iter().all(|dep| {
// Check if the scheme/file exists
std::path::Path::new(&format!("/scheme/{}", dep)).exists()
|| std::path::Path::new(&format!("/bin/{}", dep)).exists()
});
if all_ready {
let deps = self.pending.remove(id).unwrap();
log::info!("Deferred device {:?} resolved (deps: {:?})", id, deps);
if let Some(device) = registry.get_mut(id) {
device.status = DeviceStatus::Detected; // Reset to retry
resolved.push(device.clone());
}
}
}
}
resolved
}
}
```
### Wave 2: ACPI Device Enumeration
**Goal:** Walk the ACPI namespace to discover non-PCI devices (I2C, SPI, GPIO,
thermal, battery, AC adapter, platform devices).
**Linux reference:** `drivers/acpi/scan.c::acpi_bus_scan()`
**Implementation in redbear-hwdetect:**
```rust
impl AcpiScanner {
/// Enumerate ACPI devices (mirrors Linux acpi_bus_scan)
pub fn scan(&mut self) -> Result<Vec<DeviceInfo>> {
let mut devices = Vec::new();
// Connect to acpid via scheme:acpi
let acpi = File::open("/scheme/acpi")?;
// Walk ACPI namespace (read device entries)
// Linux does: acpi_walk_namespace(ACPI_TYPE_DEVICE, ...)
// RedBear: read entries from acpid's device enumeration
for entry in self.enumerate_acpi_devices(&acpi)? {
let hid = self.read_hid(&entry)?;
let cid = self.read_cid(&entry)?;
let sta = self.read_sta(&entry)?;
// Skip if not present (mirrors Linux acpi_bus_check_add)
if !sta.present {
continue;
}
// Parse _CRS resources (mirrors Linux acpi_walk_resources)
let resources = self.parse_crs(&entry)?;
// Determine device type from _HID/_CID
let device_type = match hid.as_str() {
"PNP0A03" | "PNP0A08" => DeviceType::PciBridge, // PCI root bridge
"INT33C3" | "INT3433" | "AMDI0010" => DeviceType::I2cController,
"INT33C0" | "INT3430" | "AMDI0061" => DeviceType::SpiController,
_ => DeviceType::PlatformDevice,
};
let device = DeviceInfo {
id: DeviceId::Acpi { path: entry.path.clone() },
device_type,
status: DeviceStatus::Detected,
vendor_id: None,
device_id: None,
class_code: None,
subclass_code: None,
prog_if: None,
revision: None,
parent: Some(DeviceId::Acpi { path: entry.parent.clone() }),
resources,
driver: None,
quirks: Vec::new(),
description: format!("ACPI device {} ({})", entry.path, hid),
};
devices.push(device);
}
Ok(devients)
}
}
```
### Wave 3: Service Ordering Fix
**Goal:** Replace the current flat `requires_weak` model with stage-based ordering.
**Changes to config files:**
1. **Add stage targets** to `config/redbear-device-services.toml` (shared fragment)
2. **Rewire services** to depend on their stage target instead of `00_base.target`
**New service wiring example:**
```toml
# acpid: early hardware stage
[[files]]
path = "/etc/init.d/02_acpid.service"
data = """
[unit]
description = "ACPI daemon"
requires = ["02_early_hw.target"]
[service]
cmd = "acpid"
type = { scheme = "acpi" }
"""
# redbear-hwdetect: bus enumeration stage
[[files]]
path = "/etc/init.d/03_redbear-hwdetect.service"
data = """
[unit]
description = "Hardware detection and device registry"
requires = ["03_bus_enum.target", "02_acpid.service"]
[service]
cmd = "redbear-hwdetect"
type = { scheme = "hwdetect" }
"""
# driver-manager: driver spawning stage (now consumes hwdetect registry)
[[files]]
path = "/etc/init.d/04_driver-manager.service"
data = """
[unit]
description = "Driver manager (consumes hwdetect registry)"
requires = ["04_drivers.target", "03_redbear-hwdetect.service"]
[service]
cmd = "driver-manager"
type = "oneshot_async"
"""
```
### Wave 4: Driver Config Unification
**Goal:** Consolidate `/etc/pcid.d/` and `/lib/drivers.d/` into a single config format.
**Current problem:** Two config systems exist:
- `/etc/pcid.d/*.toml` — legacy pcid format
- `/lib/drivers.d/*.toml` — driver-manager format
**Solution:** Use only `/lib/drivers.d/*.toml` (driver-manager format).
Remove all `/etc/pcid.d/` config file generation from TOML configs.
**Updated driver config format (enhanced from current):**
```toml
[[driver]]
name = "e1000d"
description = "Intel Gigabit Ethernet"
priority = 50
command = ["/usr/lib/drivers/e1000d"]
depends_on = ["pci"] # scheme dependencies (NEW)
capabilities = ["net"] # declares what it provides (NEW)
[[driver.match]]
vendor = 0x8086
class = 0x02
subclass = 0x00
# Optional: specific device IDs for better matching
[[driver.match]]
vendor = 0x8086
device = 0x100e # 82540EM
class = 0x02
```
### Wave 5: Boot Diagnostics
**Goal:** Make boot failures visible and diagnosable.
**Implementation:**
1. **`redbear-hwdetect --status`** — print detected hardware and driver status
2. **Boot marker on serial**`echo "STAGE_03_BUS_ENUM_COMPLETE"` at each stage
3. **Device failure logging** — every deferred/failed probe logged with reason
4. **JSON diagnostic output**`redbear-hwdetect --json` for automated testing
### Wave 6: USB Topology Enumeration
**Goal:** Discover USB devices beyond just the xHCI controller.
**Linux reference:** `drivers/usb/core/hub.c::hub_events()`
This is a later wave because it depends on xHCI IRQ stability (per the blocker chain).
**Implementation approach:**
- Query each xHCI controller for its device list
- Parse USB device descriptors
- Match USB class drivers (HID, mass storage, audio, CDC ACM)
- Register in device registry
## Execution Order
| Wave | Duration | Deliverable | Depends on |
|------|----------|-------------|------------|
| Wave 0 | 1 day | Boot stage targets in config | Nothing |
| Wave 1 | 2-3 weeks | `redbear-hwdetect` daemon with PCI enumeration | Wave 0 |
| Wave 2 | 1-2 weeks | ACPI device enumeration in hwdetect | Wave 1 |
| Wave 3 | 1 week | Service rewiring to stage targets | Wave 0 |
| Wave 4 | 3-5 days | Driver config unification | Wave 1 |
| Wave 5 | 3-5 days | Boot diagnostics | Wave 1 |
| Wave 6 | 2-3 weeks | USB topology enumeration | Wave 1, xHCI IRQ stability |
**Total estimate:** 6-10 weeks for waves 0-5 (core boot and hardware detection).
Wave 6 (USB) follows the blocker chain after low-level controller quality.
## Acceptance Criteria
### Boot process is "stellar" when:
1. ✅ Boot completes from power-on to login in < 10 seconds on QEMU
2. ✅ Every PCI device is enumerated and logged with full info (vendor, device, class, BARs, IRQ)
3. ✅ Every ACPI device with a present status is discovered
4. ✅ Every device that has a matching driver is bound within 3 seconds of enumeration
5. ✅ Deferred probes resolve within 5 seconds of dependency availability
6. ✅ Boot failures are visible on serial console with stage markers
7.`redbear-hwdetect --status` shows complete hardware state
8. ✅ No `requires_weak` remains for critical boot-path services
9. ✅ Service ordering is deterministic: same order on every boot
10. ✅ Missing hardware does not cause panics or hangs
### Hardware detection is "perfect" when:
1. ✅ PCI: all devices on all buses enumerated, including behind bridges
2. ✅ PCI: BARs parsed correctly (type, size, prefetchable)
3. ✅ PCI: capabilities parsed (MSI, MSI-X, PCIe, power management, vendor-specific)
4. ✅ PCI: IRQ assigned from ACPI _PRT or IOAPIC routing
5. ✅ ACPI: all devices with _STA present enumerated
6. ✅ ACPI: _CRS resources parsed (IRQ, MMIO, I/O ports, DMA)
7. ✅ USB: all devices on all controllers discovered (Wave 6)
8. ✅ Platform: I2C/SPI/GPIO controllers discovered from ACPI (Wave 2)
9. ✅ Quirks: hardware-specific quirks applied automatically
10. ✅ Hotplug: new devices detected and drivers spawned in < 2 seconds
## Relationship to Other Plans
| Plan | Relationship |
|------|-------------|
| `ACPI-IMPROVEMENT-PLAN.md` | ACPI robustness is prerequisite for Wave 2 |
| `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` | IRQ quality is prerequisite for hardware detection reliability |
| `USB-IMPLEMENTATION-PLAN.md` | USB topology (Wave 6) depends on USB maturity |
| `CONSOLE-TO-KDE-DESKTOP-PLAN.md` | Desktop path benefits from better boot/hardware detection |
| `QUIRKS-SYSTEM.md` | Quirks integrated into hwdetect's device discovery |
## Linux 7.1 Reference Files
Key files to consult when implementing:
| RedBear component | Linux 7.1 reference |
|---|---|
| PCI enumeration | `drivers/pci/probe.c`, `drivers/pci/setup-bus.c` |
| PCI driver matching | `drivers/pci/pci-driver.c` |
| ACPI device scan | `drivers/acpi/scan.c`, `drivers/acpi/bus.c` |
| ACPI resource parsing | `drivers/acpi/resource.c` |
| PCI IRQ routing | `drivers/acpi/pci_irq.c`, `drivers/acpi/pci_link.c` |
| Device model core | `drivers/base/core.c`, `drivers/base/bus.c`, `drivers/base/dd.c` |
| Deferred probing | `drivers/base/dd.c` |
| Boot initcalls | `init/main.c`, `include/linux/init.h` |
| IRQ management | `kernel/irq/manage.c`, `kernel/irq/chip.c` |
| Resource management | `kernel/resource.c` |
| DMA mapping | `kernel/dma/mapping.c` |
@@ -0,0 +1,477 @@
# Red Bear OS Comprehensive Boot Improvement Plan
**Version**: 2.0 — 2026-05-16
**Status**: Active
**Supersedes**: `SMP-BOOT-HARDENING-PLAN.md` v1.0 (P15 section) for forward work
**Scope**: Kernel SMP, AP startup, x2APIC, per-CPU data, TLB shootdowns, IRQ routing, scheduler, userspace boot, daemon robustness, IPC hardening.
## Assessment Summary
Three parallel deep-dives completed:
1. **Kernel SMP**: 20 source files, cross-referenced with Intel SDM, AMD APM, ACPI 6.5
2. **Userspace boot**: 22 source files across init, acpid, pcid, pcid-spawner, driver-manager, IPC
3. **Modern specs**: Intel SDM Vol 3A Ch 8, AMD64 APM Vol 2 Ch 7, ACPI 6.5, Linux smpboot.c, Zircon lk_main
**Total issues: 38 kernel + 16 userspace (from v1.0) + 29 new userspace + 8 new kernel = 91 issues**
- Critical: 6 kernel + 3 userspace = 9 (original) + deferred P15-3, P15-5
- High: 9 kernel + 4 userspace = 13
- Medium: 12 kernel + 12 userspace = 24
- Low: 15 kernel + 17 userspace = 32
---
## Current State (After P17 — All Scheduler Patches Complete)
### Completed Patches
| Patch | Issue | Status |
|-------|-------|--------|
| P9-P14 | Bottlenecks #1-#7 | ✅ Per-CPU context switch, broadcast TLB, IOAPIC affinity, MCS lock, range TLB, PI, NUMA |
| P15-1 | K1: AP CPU_ID race | ✅ SeqCst fetch_add |
| P15-2 | K2: AP_READY sync | ✅ AtomicU8 trampoline + fence |
| P15-4 | K4: MCS ordering | ✅ Release/Acquire fences |
| P15-6 | U1: Init deadlock | ✅ default_dependencies=false |
| P15-7 | U2: Service timeout | ✅ poll()-based 30s timeout |
| P15-8 | U3: Cycle detection | ✅ BTreeSet visiting set |
| P15-9 | U6: /tmp creation | ✅ create_dir_all |
| P15-10 | K10: TLB range ordering | ✅ Release/Acquire stores |
| P16-1 | K39/K43/K46: SIPI timing | ✅ TSC-calibrated 10ms/200µs delays, xAPIC ICR fix, second SIPI, ESR checks |
| P16-2 | K40: ESR clear/check | ✅ ESR clear before SIPI + check after, CPU count log |
| P16-3 | MAX_CPU 128→256 | ✅ Supports 256 CPUs |
| P16-4 | MADT validation | ✅ SDT checksum, MADT validation, duplicate APIC ID detection |
| P17-2 | K5: Transitive PI | ✅ Chain following via waiting_on_lock, MAX_PI_CHAIN_DEPTH=8, cycle detection |
| P17-4 | Preemption interval | ✅ Per-CPU configurable preempt_interval, default 3 ticks ≈ 6.75ms |
| P17-3 | CPU Affinity syscalls | ✅ SYS_SCHED_SETAFFINITY/GETAFFINITY (987/988), pid=0 support, RawMask-based |
| P17-1/5 | NUMA-aware selection | ✅ Same-node preference in select_next_context(), cross-node fallback |
| P18-1 | U4: Daemon restart | ✅ RestartPolicy (Never/OnFailure/Always), exponential backoff 1s→30s, max 3 restarts |
| P18-5 | U17: ACPID robustness | ✅ RSDP BIOS-area fallback, graceful physmem error handling (no panics) |
| P18-7 | U39: SIGTERM handling | ✅ driver-manager SIGTERM handler with graceful shutdown |
| P18-2 | U5: Process monitoring | ✅ reap_exited_children() in driver-manager, non-blocking waitpid |
| P18-3 | U27: MSI/MSI-X | ✅ MSI detect+log, keep legacy IRQ as baseline for all devices (v2) |
### Deferred from P15
| Patch | Issue | Reason |
|-------|-------|--------|
| P15-3 | K3: TLB shootdown range race | Needs PercpuBlock refactor — range packing into AtomicU64 |
| P15-5 | K8: NUMA node before CPU visible | Needs understanding of GDT/startup ordering |
---
## New Findings (This Assessment)
### New Kernel Issues
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| K39 | High | xAPIC path has NO delay between INIT and first SIPI | `madt/arch/x86.rs:195-218` | Intel SDM requires 10ms. Only x2APIC path has spin-count delays. xAPIC path sends INIT then immediately sends SIPI. |
| K40 | Medium | No ESR clear/check during AP startup | `madt/arch/x86.rs` | `esr()` method exists in local_apic.rs but never called during AP bringup. Intel SDM: clear ESR before SIPI, read after to verify acceptance. |
| K41 | Low | Sequential AP startup only | `madt/arch/x86.rs` | Linux does parallel bringup for 96+ cores. Current code starts APs one-by-one. |
| K42 | Low | No cpu_callout_mask / cpu_callin_mask handshake | `madt/arch/x86.rs` | Linux uses two-phase handshake for AP validation. Current code uses AP_READY bool only. |
| K43 | Medium | xAPIC SIPI has spurious bit 14 (Level=Assert) | `madt/arch/x86.rs:209` | ICR value 0x4600 has bit 14 set. Per Intel SDM, this bit is reserved/zero for SIPI. Works in QEMU but may cause issues on real hardware. |
| K44 | Low | No self-IPI MSR optimization | `local_apic.rs` | Self-IPI via MSR 0x83F is the fastest single-IPI path for x2APIC. Not implemented. |
| K45 | Low | No CPUID topology detection for AMD | `local_apic.rs` | CPUID leaf 0x8000001E for AMD topology (ext_apic_id, core_id, node_id) not used. |
| K46 | Low | xAPIC path missing second SIPI | `madt/arch/x86.rs:206-218` | Only x2APIC path sends second SIPI. Intel SDM recommends sending SIPI twice for compatibility. |
### New Userspace Issues
#### ACPID (8 issues)
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| U17 | High | AML panic on missing RSDP_ADDR | `acpid/src/acpi.rs` | Panics instead of graceful fallback when env var absent |
| U18 | Medium | Single PCI fd limitation | `acpid/src/main.rs` | Multi-segment PCIe systems can't work with single fd |
| U19 | Medium | No physmap bounds checking | `acpid/src/aml_physmem.rs` | Crafted ACPI table could cause kernel panic via unbounded physmap |
| U20 | Low | EC timeout 10ms may be insufficient | `acpid/src/ec.rs` | Slow embedded controllers need more time |
| U21 | Low | No S4 (hibernate) support | `acpid/src/acpi.rs` | S5 (shutdown) only |
| U22 | Low | Battery assumes single battery | `acpid/src/scheme.rs` | Multiple battery methods would need array |
| U35 | Medium | Page cache unbounded growth | `acpid/src/scheme.rs` | No LRU or eviction on ACPI table cache |
| U36 | Low | No FD limit on sendfd | `acpid/src/scheme.rs` | Could exhaust kernel FD table |
#### PCID (6 issues)
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| U23 | Low | No Type 2 CardBus bridge support | `pcid/src/main.rs` | Only Type 0/1 PCI headers parsed |
| U24 | Medium | Hardcoded bus 0x80 scan workaround | `pcid/src/main.rs` | Arrow Lake-specific, not portable |
| U25 | Medium | Multi-segment ECAM not implemented | `pcid/src/cfg_access/mod.rs` | Skips non-zero segment groups |
| U26 | Medium | Single global PCI mutex | `pcid/src/scheme.rs` | Serializes all PCI config access |
| U27 | High | MSI/MSI-X never enabled | `pcid/src/main.rs` | Code only disables MSI/MSI-X, never enables for drivers |
| U28 | High | Hardcoded IRQ line 9 | `pcid/src/main.rs` | All non-MSI devices get IRQ 9 regardless of actual routing |
#### Driver Manager (4 issues)
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| U29 | High | Race with legacy pcid-spawner | `driver-manager` | Both enumerate PCI and spawn drivers simultaneously |
| U30 | Low | Different retry limits (30 vs 5) | `driver-manager` | 30 for init, 5 for hotplug — no justification documented |
| U31 | Medium | No hotplug for ACPI devices | `driver-manager/src/hotplug.rs` | PCI hotplug only |
| U32 | Medium | Poll-based hotplug inefficient | `driver-manager/src/hotplug.rs` | 2s poll interval instead of event-driven |
#### IPC/Scheme (4 issues)
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| U33 | High | No scheme authentication | `ipcd` | Anyone can register any scheme name |
| U34 | Medium | No scheme conflict detection | `ipcd` | No check for duplicate registration |
| U37 | Low | SO_PEERCRED stale after exec | `ipcd/src/uds/stream.rs` | Credentials may be outdated |
| U38 | Low | No FD limit on sendfd | IPC | Kernel FD table exhaustion possible |
#### Daemon Robustness (7 issues)
| # | Severity | Issue | Detail |
|---|----------|-------|--------|
| U39 | High | No SIGTERM handling | No daemon handles SIGTERM for graceful shutdown |
| U40 | Medium | No SIGCHLD handling | Abnormal child exits not detected |
| U41 | High | No watchdog/health monitoring | No health-check ping for critical services |
| U42 | Medium | unwrap()/expect() in critical paths | Multiple panics instead of graceful degradation |
| U43 | Medium | No rollback on rootfs switch failure | Boot continues in undefined state |
| U44 | Low | No boot milestone tracking | No checkpoint/restart capability |
| U45 | Low | Low batch size (50) | Modern systems have 100+ devices |
---
## Improvement Plan — Patch Series
### Phase 1: Stabilize SMP Boot (P16) — 6 patches
**Goal**: Make AP startup reliable on real hardware with calibrated timing, error checking, and firmware bug detection.
#### P16-1: TSC-Calibrated SIPI Delays (High K7, K39, K43, K46)
**Files**: `src/acpi/madt/arch/x86.rs`
**Changes**:
1. Add `udelay(us: u64)` function using TSC (read via `rdtsc`, calibrated from `cpu_khz` if available, else use known CPU frequency). For early boot before TSC calibration, use a conservative spin loop.
2. **xAPIC path** (currently no delay):
- After INIT IPI: `udelay(10_000)` (10ms per Intel SDM)
- After SIPI #1: `udelay(200)` (200µs)
- Send SIPI #2 (currently missing)
- After SIPI #2: `udelay(200)` (200µs)
3. **x2APIC path** (currently spin-count delays):
- Replace `for _ in 0..100_000 { spin_loop() }` with `udelay(10_000)` (10ms)
- Replace `for _ in 0..2_000_000 { spin_loop() }` with `udelay(200)` (200µs)
4. Fix xAPIC SIPI ICR: change `0x4600` to `0x0600` (remove spurious bit 14 Assert)
**Early-boot TSC strategy**: At AP startup time, the kernel has already calibrated the TSC (it's needed for the scheduler timer). Use `crate::time::monotonic()` or direct `rdtsc` with the known CPU frequency. If no TSC freq is available yet, use a conservative spin loop calibrated for at least 10ms at minimum CPU speed.
**Reference**: Intel SDM Vol 3A §8.4.4, Linux `wakeup_secondary_cpu_via_init()`
#### P16-2: AP Startup ESR Check + Graceful Degradation (Medium K40)
**Files**: `src/acpi/madt/arch/x86.rs`
**Changes**:
1. Before sending INIT IPI: `local_apic.esr()` to clear ESR
2. After each SIPI: read ESR to check for delivery errors
3. If ESR indicates error after both SIPIs, log warning and skip that CPU
4. Track `cpu_online_mask` (AtomicU32 bitmap) separately from `cpu_possible_mask`
5. On timeout (trampoline or AP_READY), log which CPU failed and why, continue boot
**Code structure**: Extract the common AP startup sequence into a helper function to avoid the duplicated code between xAPIC and x2APIC paths.
#### P16-3: MAX_CPU_COUNT Increase to 256 (High K12)
**Files**: `src/cpu_set.rs`
**Changes**:
1. Change `MAX_CPU_COUNT` from 128 to 256 for 64-bit targets
2. Add boot-time log: "N CPUs detected, MAX_CPU_COUNT=256"
3. Add boot-time warning if CPU count > 200 (approaching limit)
**Impact**: SET_WORDS grows from 2 to 4 (256/64). LogicalCpuSet becomes 32 bytes instead of 16. All users are by-value or reference, so no ABI break.
#### P16-4: Firmware Bug Detection (Medium)
**Files**: `src/acpi/madt/mod.rs`, `src/acpi/mod.rs`
**Changes**:
1. **Duplicate APIC ID detection**: During MADT iteration in `arch::init()`, collect all APIC IDs in a `BTreeSet<u32>`. If duplicate found, log warning with both entries. Keep first, skip duplicates.
2. **SDT checksum validation**: In `acpi/mod.rs`, add `fn validate_sdt_checksum(sdt: &Sdt) -> bool` that sums all bytes and checks == 0. Call for MADT, SRAT, SLIT before use. Log warning and skip table if checksum fails.
3. **Unknown MADT type logging**: Already logs via `debug!` but upgrade to `info!` for unknown types. Add MADT revision check.
#### P16-5: TLB Shootdown Range Race Fix (Critical K3, deferred from P15-3)
**Files**: `src/percpu.rs`
**Changes**: Pack TLB range into a single `AtomicU64`:
- Bits [63:32] = start page (up to 2^32 pages = 16TB address space)
- Bits [31:0] = count (up to 4 billion pages)
- Single `compare_exchange` or `swap` sets the flag + range atomically
- Handler unpacks with single `load`
- If range is too large for packing, fall back to full shootdown
**Risk**: Medium. Affects all TLB shootdowns. Must verify no regressions.
#### P16-6: NUMA Node Before CPU Visible (High K8, deferred from P15-5)
**Files**: `src/acpi/madt/arch/x86.rs`
**Changes**:
1. Move `record_apic_mapping()` and `percpu.numa_node.set()` BEFORE `CPU_COUNT.fetch_add()`
2. Add `fence(SeqCst)` between them so scheduler sees NUMA data before the CPU becomes schedulable
3. This requires PercpuBlock to be allocated and initialized before the fetch_add — verify that `allocate_and_init_pcr()` and the percpu allocation happen early enough
**Risk**: Low-Medium. Reordering of operations, must verify AP startup still works.
---
### Phase 2: Desktop-Safe Scheduler (P17) — ✅ COMPLETE (6 patches)
#### P17-1: NUMA-Aware Work Stealing (Medium K20) — ✅ DONE
**Files**: `src/context/switch.rs`
**Patch**: `P17-1-numa-selection.patch`
**Change**: In `select_next_context()`, prefer contexts whose last CPU is on the same NUMA node. Two-phase selection: scan for same-node candidates first, fall back to cross-node. New contexts (no last CPU) treated as same-node. Uses `percpu.numa_node` set by P14 SRAT parsing.
#### P17-2: Transitive Priority Inheritance (Critical K5) — ✅ DONE
**Files**: `src/sync/mcs.rs`, `src/percpu.rs`
**Patches**: `P17-2a-percpu-waiting.patch`, `P17-2b-transitive-pi.patch`
**Change**: Added `waiting_on_lock: AtomicPtr<McsRawLock>` to PercpuBlock. Rewrote `maybe_donate_priority()` to follow the PI chain transitively up to `MAX_PI_CHAIN_DEPTH` (8) hops with cycle detection. Each CPU records which MCS lock it's spinning on before entering the spin loop; the donation function follows `waiting_on_lock → holder_cpu` chains to propagate priority through A→B→C nesting.
#### P17-3: CPU Affinity Syscalls (New Feature) — ✅ DONE (pid=0)
**Files**: `src/syscall/process.rs`, `src/syscall/mod.rs`
**Patches**: `P17-3-sched-affinity.patch`, `P17-3-syscall-dispatch.patch`
**Change**: Added `SYS_SCHED_SETAFFINITY` (987) and `SYS_SCHED_GETAFFINITY` (988) as local syscall constants. `sched_affinity: LogicalCpuSet` already existed on Context and was checked in `update_runnable()`. New handlers read/write `RawMask` ([usize; 4], 32 bytes) to/from userspace. Currently supports pid=0 (current process only); PID-based lookup deferred pending lock token architecture work.
#### P17-4: Configurable Preemption Interval — ✅ DONE
**Files**: `src/context/switch.rs`
**Patch**: `P17-4-configurable-preempt.patch`
**Change**: Replaced hardcoded `new_ticks >= 3` with per-CPU `preempt_interval: Cell<usize>` on `ContextSwitchPercpu`. Default: `DEFAULT_PREEMPT_INTERVAL = 3` (≈6.75 ms). Infrastructure ready for runtime tuning via syscall or kernel command line.
#### P17-5: Load Balancing — ✅ MERGED INTO P17-1
**Note**: The global run queues (shared by all CPUs) make traditional work-stealing unnecessary. The NUMA-aware selection in P17-1 effectively provides the same benefit — idle CPUs naturally pick up cross-node work when same-node work is unavailable.
---
### Phase 3: Harden Userspace Boot & IPC (P18) — 8/8 complete
#### P18-1: Daemon Restart Policy (High U4) — ✅ DONE
**Files**: `init/src/service.rs`, `scheduler.rs`, `init/src/main.rs`
**Patch**: `local/patches/base/P18-1-daemon-restart.patch`
**Status**: RestartPolicy enum (Never/OnFailure/Always), max_restarts (default 3), exponential backoff (1s→2s→4s→8s→16s, max 30s). Scheduler tracks supervised PID→ServiceState in BTreeMap. handle_child_exit() in main loop applies restart policy. Built and boot-tested on redbear-mini.
#### P18-2: Process Monitoring & Cleanup (High U5) — ✅ DONE
**Files**: `local/recipes/system/driver-manager/source/src/config.rs`, `main.rs`
**Reference Patch**: `local/patches/driver-manager/P18-2-process-monitoring.patch`
**Status**: `reap_exited_children()` method on DriverConfig — non-blocking `try_wait()` for all spawned children. `reap_all_drivers()` function polls all configs. Called in deferred retry loop and idle loop (every 5s). Exited drivers are removed from the spawned map and logged.
#### P18-3: MSI/MSI-X Enablement (High U27) — ✅ DONE (v2)
**Files**: `drivers/pcid/src/main.rs`
**Patch**: `local/patches/base/P18-3-msi-msix-enablement.patch`
**Status v2**: In `enable_function()`, MSI/MSI-X capabilities are detected and logged, then disabled to clean state. Legacy IRQ is configured for ALL devices as a baseline (including MSI-capable ones). Drivers that support MSI (e.g., virtio-netd, nvmed) enable MSI themselves via `pci_allocate_interrupt_vector()`. Drivers without MSI support (e.g., ahcid) use the legacy interrupt. Validated on q35 (AHCI MSI device) and i440fx — no panics. Pre-existing virtio-netd MSI allocation bug (irq_helpers.rs:193 .expect() on EEXIST) exposed but not caused by this change.
#### P18-4: pcid-spawner / driver-manager Unification (High U29)
**Files**: `local/recipes/system/driver-manager/`, `recipes/core/base/source/drivers/pcid-spawner/`
**Change**: Eliminate the race between pcid-spawner and driver-manager by making driver-manager the sole PCI driver spawner. Deprecate pcid-spawner. Driver-manager already has the config infrastructure.
#### P18-5: ACPID Robustness (High U17) — ✅ DONE
**Files**: `drivers/acpid/src/acpi.rs`, `drivers/acpid/src/aml_physmem.rs`
**Patch**: `local/patches/base/P18-5-acpid-robustness.patch`
**Status**: RSDP_ADDR env var now falls back to BIOS-area probe (0xE00000xFFFFF) scanning for "RSD PTR " signature. read_phys_or_fault returns zero instead of panic. map_physical_region maps zero-page fallback on failure. unmap_physical_region logs error instead of expect-panic. Built and boot-tested on redbear-mini.
#### P18-6: Watchdog/Health Monitoring (High U41)
**Files**: `recipes/core/base/source/init/src/main.rs`
**Change**: Optional health-check ping in scheme protocol. Init checks critical services every 5s. On failure, restart per restart policy.
#### P18-7: SIGTERM Handling in Daemons (High U39) — ✅ DONE (driver-manager)
**Files**: `local/recipes/system/driver-manager/source/src/main.rs`, `Cargo.toml`
**Reference Patch**: `local/patches/driver-manager/P18-7-sigterm-handler.patch`
**Status**: SIGTERM handler via libc::signal setting AtomicBool flag. idle_forever() polls flag every 1s (was 3600s). Deferred retry loop checks flag. graceful_shutdown() function. Added libc dependency. Built and boot-tested on redbear-mini. ACPID shutdown is already handled via kernel kstop pipe.
#### P18-8: Bounded Scheme Request Queues (Medium) — ✅ COMPLETE
**Files**: `recipes/core/base/source/ipcd/` (chan.rs, uds/stream.rs, uds/dgram.rs)
**Patch**: `local/patches/base/P18-8-bounded-ipcd-queues.patch`
**Change**: Added bounded queue depth limits to ipcd: MAX_LISTENER_BACKLOG (64) for channel listeners, MAX_UDS_LISTENER_BACKLOG (64) for UDS stream listeners, MAX_UDS_PACKET_QUEUE (256) for UDS stream packet queues, MAX_DGRAM_QUEUE (256) for UDS datagram queues. Returns ECONNREFUSED when connection backlog is full, EAGAIN when packet/datagram queue is full. Built and boot-tested on redbear-mini.
#### P18-9: MSI/MSI-X Allocation Resilience (High U27) — ✅ DONE
**Files**: `drivers/pcid/src/driver_interface/irq_helpers.rs`, `drivers/virtio-core/src/transport.rs`, `drivers/virtio-core/src/arch/x86.rs`, `drivers/net/virtio-netd/src/main.rs`, `drivers/storage/virtio-blkd/src/main.rs`, `drivers/usb/xhcid/src/main.rs`
**Patch**: `local/patches/base/P18-9-msi-allocation-resilience.patch`
**Status**: Six-file fix for pre-existing MSI vector allocation panic:
1. `allocate_aligned_interrupt_vectors()`: Handles `EEXIST` by releasing partial range and restarting search from next aligned position (renamed `first``first_aligned` to enable resetting).
2. `allocate_single_interrupt_vector_for_msi()`: Returns `Option<(MsiAddrAndData, File)>` instead of panicking. Logs warning on allocation failure.
3. `allocate_first_msi_interrupt_on_bsp()`: Returns `Option<File>` instead of panicking.
4. `pci_allocate_interrupt_vector()`: Proper MSI-X → MSI → legacy fallback chain. MSI-X is only enabled in config space after successful vector allocation. On failure, falls back without leaving MSI-X enabled.
5. `virtio-core/transport.rs`: Added `MsiAllocationFailed` error variant.
6. `virtio-core/arch/x86.rs`: Uses `ok_or(Error::MsiAllocationFailed)?` instead of panicking.
7. `virtio-netd/main.rs` and `virtio-blkd/main.rs`: `daemon_runner` logs error and exits cleanly instead of `.unwrap()` panic.
8. `xhcid/main.rs`: MSI-X → MSI → legacy → polling fallback chain.
**Validated**: Boots on q35/4CPU with zero panics. virtio-netd exits gracefully when no vectors available. ahcid uses legacy IRQ. Rest of system continues normally.
---
### Phase 4: Stress Test & Validation (P19) — 2/4 complete
#### P19-1: Multi-Core Driver Stress Test — ✅ PASS (2026-05-17)
**Result**: QEMU q35 machine with 4 CPUs booted to login successfully. AHCI, virtio-blk, and all core drivers started without panics.
**Script**: `local/scripts/test-smp-stress-qemu.sh`
**Findings**:
- ✅ 4 CPUs online, SMP scheduler stable
- ✅ AHCI driver started (IRQ 10 legacy fallback) — P18-3 v2 fix validated
- ✅ virtio-blk disk detected (3M sectors)
- ✅ ACPID, pcid, ipcd all stable
- ✅ virtio-netd exits gracefully instead of panicking — P18-9 fix (was: irq_helpers.rs:193 .expect() on EEXIST)
- ✅ driver-manager probe loop bounded by P18-2 max_retries=3 (reduced from 30)
- ❌ dd-based I/O stress ineffective — Redox `/dev/null` is a scheme, shell redirection fails
- **Remaining**: (1) Root cause why CPU 0 has no available MSI vectors on q35 (kernel vector count investigation), (2) Redesign stress test for Redox scheme-based I/O
#### P19-2: IRQ Vector Debug + Close Bug Fix — ✅ DONE (2026-05-17)
**Patch**: `local/patches/kernel/P19-2-irq-debug.patch`
**Changes** (kernel `scheme/irq.rs` + `arch/x86_shared/idt.rs`):
1. **Bug fix**: `Handle::Irq` now stores `cpu_id: LogicalCpuId` alongside `irq` and `ack`. Previously, `close()` always unreserved on BSP (`LogicalCpuId::BSP`) regardless of which CPU the vector was allocated on — a correctness bug causing vector leaks on APs.
2. **Debug logging**: `available_irqs_iter()` logs `cpu_id` and available vector count per call.
3. **Debug logging**: IRQ `getdents` for `Handle::Avail` logs `cpu_id`, `opaque`, and number of entries listed.
4. **Debug logging**: IRQ `close()` logs which CPU the vector is being unreserved on.
**Purpose**: Runtime diagnosis of the IRQ vector scarcity mystery on q35 (CPU 0 appearing to have zero available MSI vectors despite ~201 expected). The debug logs will reveal whether the IDT reservations are correct at runtime and whether `read_dir` is returning empty or if the issue is elsewhere.
**Note**: This is a diagnostic patch. Once the IRQ vector scarcity root cause is confirmed and fixed, the `log::info!` calls should be removed or converted to `log::debug!`.
#### P19-2b: Repo Cook Fork Safety Hardening — ✅ DONE (2026-05-17)
**Changes** (build system `src/cook/fetch.rs` + `cookbook.toml`):
1. **`cookbook.toml`**: Created with explicit `offline = true` — makes the offline-first policy explicit rather than relying on code defaults.
2. **Auto-protect patched recipes**: `recipe_has_patches()` function checks if a recipe has patches in its `recipe.toml`. `redbear_should_protect()` now protects any recipe that either (a) is on the explicit protected list, OR (b) has patches. This prevents accidental upstream re-fetching from breaking patch context lines.
3. **Warning on bypass**: When `--allow-protected` is used on a patched recipe, a `[WARN]` message is logged: "recipe X has patches but --allow-protected is set — upstream source changes may break patches".
**Audit result**: The 3-layer protection (COOKBOOK_OFFLINE=true → fetch_offline, redbear_protected_recipe → redirect to fetch_offline, REDBEAR_RELEASE → block explicit fetch) is solid. The auto-protect addition closes the gap where a recipe with patches but not on the explicit list could be re-fetched from upstream.
---
## Priority Ordering
### ✅ Completed (P16) — This Session
1.**P16-3**: MAX_CPU_COUNT 128→256
2.**P16-1**: TSC-calibrated SIPI delays + fix xAPIC ICR + add second SIPI
3.**P16-2**: ESR check + graceful degradation + CPU count log
4.**P16-4**: Firmware bug detection (duplicate APIC IDs, SDT checksums)
### Next (P17) — Desktop-Safe Scheduler
Depends on P16 completion. See individual patches above.
### Then (P18) — Userspace Hardening + Firmware
Depends on P16+P17 for stable kernel foundation. Includes firmware loading fixes.
### Finally (P19) — Stress Testing
Depends on P16+P17+P18 for full stack validation.
---
## Acceptance Criteria
- [ ] All Critical and High issues resolved
- [ ] Boot to login prompt in <10s on QEMU (4 cores)
- [ ] No panics under 72-hour stress test (4 cores, all driver types)
- [ ] AP startup race-free with 256 simulated CPUs
- [ ] NUMA topology correctly discovered from QEMU SRAT
- [ ] Service restart within 5 seconds of crash
- [ ] No priority inversion >100ms under load
- [ ] MSI/MSI-X enabled for all PCI devices that support it
- [ ] No duplicate scheme registrations possible
- [ ] All patches in `local/patches/kernel/` or `local/patches/base/`, wired into `recipe.toml`
- [ ] Boot-tested on QEMU UEFI with `scripts/run_mini.sh`
## Dependency Graph
```
P16-3 (MAX_CPU) ──────────────────────────────┐
P16-1 (SIPI timing) ──────────────────────────┤
P16-2 (ESR check + graceful degradation) ─────┤
P16-4 (firmware bugs) ────────────────────────┼──→ P17-* (scheduler)
P16-5 (TLB range race, from P15-3) ───────────┤
P16-6 (NUMA ordering, from P15-5) ────────────┘
P17-* ──→ P18-1 (restart policy)
P18-2 (crash cleanup)
P18-3 (MSI/MSI-X enablement)
P18-4 (pcid-spawner unification)
P18-5 (acpid robustness)
P18-6 (watchdog)
P18-7 (SIGTERM)
P18-8 (bounded queues)
P18-* ──→ P19-* (stress tests)
```
---
## Firmware Loading Assessment (Added 2026-05-16)
### Architecture
The firmware loading system is well-designed with three-tier caching:
1. **In-memory cache** (`HashMap<String, CachedBlob>`)
2. **Persistent cache** (`/var/lib/firmware/cache`) — survives daemon restarts
3. **Filesystem** (`/lib/firmware`) — primary source
**Fallback chains**: TOML-configured in `/etc/firmware-fallbacks.d/`, with built-in fallbacks for AMD DCN and Intel Wi-Fi.
**Linux KPI compatibility**: `request_firmware()` / `release_firmware()` via `linux-kpi/source/src/rust_impl/firmware.rs`.
### Firmware Issues
| # | Severity | Issue | File | Detail |
|---|----------|-------|------|--------|
| FW1 | Critical | No real AMD GPU firmware files | `local/firmware/` (empty) | DCN 3.5+, GC 11.x, PSP, SDMA, VCN firmware missing |
| FW2 | Critical | No real Intel Wi-Fi firmware files | `local/firmware/` (empty) | AX200/AX201/AX210/AX211 .ucode files missing |
| FW3 | Critical | Driver vs firmware-loader race | `driver-manager/config.rs:236` | Only checks scheme path, not specific files |
| FW4 | Critical | No firmware-ready notifications | `firmware-loader/async.rs` | Uevents dispatched but no consumers |
| FW5 | Critical | No firmware dependency in driver config | `driver-manager/config.rs:532` | Drivers can't declare required firmware files |
| FW6 | High | No boot-critical firmware pre-population | initfs | Display firmware not embedded for early boot |
| FW7 | High | Deferred probe timeout too short | `driver-manager/main.rs:407` | 15s total (500ms × 30 retries) insufficient for large GPU firmware |
| FW8 | High | No firmware loader crash recovery | init | If firmware-loader crashes, /scheme/firmware gone permanently |
| FW9 | High | No firmware version pinning | `manifest.rs` | SHA256 hashes generated but never validated on load |
| FW10 | Medium | Cache poisoning on concurrent access | `blob.rs:645` | Mutex poisoned on panic, subsequent cache accesses fail silently |
| FW11 | Medium | No per-operation firmware load timeout | `scheme.rs:16` | Single 5s timeout for all firmware regardless of size |
| FW12 | Medium | No firmware inventory tool | `main.rs` | No `/proc/firmware` equivalent for debugging |
| FW13 | Medium | No firmware size limits | `linux-kpi/firmware.rs:65` | Arbitrary-size allocation, potential DoS |
| FW14 | Low | No firmware signature verification | all | SHA256 hashes not validated on load |
### Firmware Loading Patches (P18-FW Series)
#### P18-FW1: Firmware Availability Handshake (Critical FW3, FW5)
**Files**: `local/recipes/system/firmware-loader/source/src/scheme.rs`, `local/recipes/system/driver-manager/source/src/config.rs`
**Change**:
1. firmware-loader publishes indexed firmware list at `/scheme/firmware/.index`
2. driver-manager checks specific firmware files before probing driver
3. Add `firmware_requires = [...]` to driver config TOML schema
#### P18-FW2: Firmware Loader Watchdog + Restart (High FW8)
**Files**: `recipes/core/base/source/init/src/service.rs`
**Change**: Add `restart = "always"` to firmware-loader service. Init respawns on crash.
#### P18-FW3: Extended Deferred Probe Timeout (High FW7)
**Files**: `local/recipes/system/driver-manager/source/src/main.rs`
**Change**: Increase max_retries to 60 (30s total), add per-driver `probe_timeout` config.
#### P18-FW4: Firmware Pre-Population for Boot-Critical Devices (High FW6)
**Files**: `config/redbear-full.toml`
**Change**: Add AMD DMCU and Intel Wi-Fi firmware blobs to image via `[[files]]` or dedicated firmware package.
---
## Implementation Status
### Completed This Session (2026-05-16)
-**P16-1**: TSC-calibrated SIPI delays + fix xAPIC ICR (0x4600→0x0600) + add second SIPI
-**P16-2**: ESR check before/after SIPI + CPU count log + approaching-limit warning
-**P16-3**: MAX_CPU_COUNT 128→256
-**P16-4**: Firmware bug detection (duplicate APIC IDs, SDT checksum validation)
-**P16-1/2/3/4 patches**: Generated, validated (25/25 pass), wired into recipe.toml
-**Build + boot test**: Kernel cooks, full image builds, QEMU boots with zero panics
-**Firmware loading assessment**: 14 issues identified, 4 P18-FW patches planned
### Boot Test Evidence
```
MADT: duplicate APIC ID 0 in LocalApic entry, firmware bug ← P16-4 working
SMP: 1 CPUs online (max 256) ← P16-3 working
```
+357
View File
@@ -0,0 +1,357 @@
# Red Bear OS SMP Boot & Scheduler Hardening Plan
**Version**: 1.0 — 2026-05-16
**Status**: Active
**Canonical**: This document supersedes `SMP-SCHEDULER-IMPROVEMENT-PLAN.md` for forward work.
**Scope**: Kernel SMP, AP startup, x2APIC, per-CPU data, TLB shootdowns, IRQ routing, scheduler, userspace boot, daemon robustness.
## Assessment Summary
Comprehensive assessment of kernel SMP infrastructure (20 source files), userspace boot process (10 source files), and modern Intel/AMD MP specifications. Cross-referenced with Linux `smpboot.c`, Zircon `lk_main`, and seL4 multicore boot.
**Total issues found: 38 kernel + 16 userspace = 54 issues**
- Critical: 6 kernel + 3 userspace = 9
- High: 7 kernel + 4 userspace = 11
- Medium: 10 kernel + 5 userspace = 15
- Low: 15 kernel + 4 userspace = 19
---
## Kernel SMP Issues
### Critical (6)
| # | Issue | File | Root Cause |
|---|-------|------|------------|
| K1 | AP startup LogicalCpuId race | `madt/arch/x86.rs:153,244,276,365` | Two APs `CPU_COUNT.load(Relaxed)` → same ID → both `fetch_add(1)` |
| K2 | AP_READY dual-mechanism sync race | `madt/arch/x86.rs:174-225` | Trampoline u64 `ap_ready.write(0)` + static `AtomicBool AP_READY` — inconsistent ordering, UB on cast |
| K3 | TLB shootdown range race | `percpu.rs:134-137` | Concurrent shootdowns overwrite `tlb_flush_start`/`tlb_flush_count` between flag set and IPI |
| K4 | MCS lock missing memory fences | `sync/mcs.rs:74-101` | No Release after `next.store()`, no Acquire before `locked.load()` |
| K5 | Unbounded priority inversion chain | `sync/mcs.rs:126-145` | PI donation goes one level only; transitive chains unbounded |
| K6 | Scheduler context switch flag not cleared on panic | `switch.rs:164,298` | `in_context_switch` stays true → permanent CPU lockup |
### High (7)
| # | Issue | File | Root Cause |
|---|-------|------|------------|
| K7 | Missing SIPI timing delays | `madt/arch/x86.rs:192-337` | Spin-count delays, not TSC-based. Intel SDM requires 10ms INIT→SIPI |
| K8 | NUMA node set after CPU visible | `madt/arch/x86.rs:244,253` | `CPU_COUNT.fetch_add()` before `numa_node.set()` |
| K9 | Empty memory fence before AP starts | `madt/arch/x86.rs:188` | `asm!("")` is compiler barrier only, not hardware fence |
| K10 | TLB range Relaxed ordering | `percpu.rs:146,179` | Range stores use `Relaxed`, no barrier before IPI send |
| K11 | IOAPIC affinity no CPU online check | `ioapic.rs:126-137` | Accepts any ApicId without validation |
| K12 | MAX_CPU_COUNT=128 too small | `cpu_set.rs:44` | AMD EPYC has 128C/256T, Threadripper PRO 96C/192T |
| K13 | Global IRQ count lock | `scheme/irq.rs:67` | `COUNTS.lock()` is global spinlock on hot path |
### Medium (10)
| # | Issue | File | Root Cause |
|---|-------|------|------------|
| K14 | x2APIC detection no fallback | `local_apic.rs:56-66` | If x2APIC init fails, no fallback to xAPIC |
| K15 | AP startup timeout not time-based | `madt/arch/x86.rs:44` | `AP_SPIN_LIMIT=1_000_000` spin counts vary by clock speed |
| K16 | TLB shootdown no timeout | `percpu.rs:134-143` | Spin waits indefinitely if target CPU crashed |
| K17 | Broadcast shootdown sequential flag-setting | `percpu.rs:151-184` | O(n) flag set loop on 128+ core systems |
| K18 | PI donation write-once | `sync/mcs.rs:62` | Later higher-priority waiter doesn't update |
| K19 | PI donation Relaxed ordering | `sync/mcs.rs:142` | `pi_donated_prio.store(Relaxed)` may not be visible |
| K20 | Scheduler NUMA-unaware | `switch.rs:357-495` | `same_node()` exists but never used in work stealing |
| K21 | IOAPIC legacy IRQs always BSP | `ioapic.rs:392` | IRQs 0-15 hardcoded to BSP, no load balancing |
| K22 | RSDP no BIOS scan fallback | `rsdp.rs:19-48` | Only uses bootloader-supplied address |
| K23 | No SDT checksum validation | `acpi/mod.rs:94-180` | Only RSDP checksum verified, not child SDTs |
### Low (15)
K24K38: Trampoline writable+executable, fixed trampoline address 0x8000, no SIPI delivery status check, no PercpuBlock cleanup on AP failure, PercpuBlock registration race, no NUMA barrier, hardcoded preemption timer, no preemption guard enforcement, no MCS recursive detection, scheduler recursion limitation, MADT unknown types silently ignored, no MADT revision check, no SLIT diagonal validation, RSDP length bounds too loose, no APIC ESR clear before SIPI.
---
## Userspace Boot Issues
### Critical (3)
| # | Issue | File | Root Cause |
|---|-------|------|------------|
| U1 | Init dependency deadlock | `redbear-mini.toml:244-256` | `00_intel-gpiod.service` has `default_dependencies=true` → circular wait with driver-manager |
| U2 | No service timeout | `service.rs:78-118` | Notify/Scheme types block forever if daemon hangs |
| U3 | Dependency cycle detection missing | `scheduler.rs:77-95` | BFS `load_units()` loops forever on circular `requires_weak` |
### High (4)
| # | Issue | File | Root Cause |
|---|-------|------|------------|
| U4 | No daemon restart policy | init system | Crashed daemons stay dead, no auto-restart |
| U5 | No crash cleanup | driver-manager | Spontaneous crash doesn't release scheme/PCI/IRQ |
| U6 | Boot timeline /tmp/ missing | `driver-manager main.rs:24` | Writes to `/tmp/...` without ensuring `/tmp` exists |
| U7 | Hotplug redundant enumeration | `hotplug.rs:31-40` | Full PCI/ACPI re-scan every 2s |
### Medium (5)
U8U12: Hotplug unbound device removal bug, ided I/O privilege `expect()`, serial boot markers blocking 800ms, limited parallelism (50/step), no queue overflow handling.
### Low (4)
U13U16: PCI enumeration no timeout, async enumeration no join timeout, boot status command broken if no timeline, no driver health endpoint.
---
## Reference: Modern Hardware Requirements
Sources: Intel 64/IA-32 SDM Vol 3A Ch 8, AMD64 APM Vol 2 Ch 7, ACPI 6.5, Intel x2APIC spec, Linux smpboot.c, Zircon lk_main, seL4 multicore boot.
### AP Startup Timing (Intel SDM)
- INIT deassert → SIPI: **10ms** (modern CPUs: can be shorter)
- SIPI #1 → SIPI #2: **10-300µs** (modern: 10µs, legacy: 300µs)
- AP response timeout: **10 seconds** (Linux)
- ESR check: Clear before each SIPI, read after to verify acceptance
### AP Startup Timing (AMD)
- Similar INIT/SIPI sequence
- CPUID leaf `0x8000001E` for topology (ext_apic_id, core_id, node_id)
- CPUID leaf `0x1F` preferred for V2 extended topology (Intel + newer AMD)
- APIC ID may exceed 255 → x2APIC mandatory
### x2APIC Requirements
- **Mandatory**: CPU count > 255 (8-bit APIC ID exhausted)
- **Detection**: CPUID.01H:ECX[bit 21]
- **ICR**: Single 64-bit MSR write (vs two 32-bit MMIO writes)
- **No delivery status bit**: Hardware guarantees delivery
- **Self-IPI**: Dedicated MSR 0x83F (fastest single-IPI path)
### ACPI MADT Entry Types (ACPI 6.5)
- Type 0: Processor Local APIC (legacy 8-bit)
- Type 1: I/O APIC
- Type 2: Interrupt Source Override
- Type 4: Local APIC NMI
- Type 5: Local APIC Address Override
- **Type 9: Processor Local x2APIC** (32-bit ID, required for modern hardware)
- **Type 10: Local x2APIC NMI**
- Type 20: Multi-Processor Wakeup Structure (ACPI 6.4+)
### Common Firmware Bugs
1. Duplicate APIC IDs in MADT
2. Incorrect enabled flags
3. Missing entries (CPU exists but no MADT entry)
4. MADT UID / DSDT _UID mismatch
5. SLIT diagonal != 10 (Linux validates and rejects)
6. SRAT-SLIT inconsistency
### Linux Best Practices
- Parallel AP bringup (all APs kicked simultaneously) — reduces boot 500ms→100ms on 96-core
- Adaptive SIPI timing: `init_udelay=0` → 10µs for modern CPUs
- 10-second timeout with `schedule()` yield loop
- ESR check after each SIPI, retry up to 2×
- `cpu_callout_mask` / `cpu_callin_mask` handshake
### Zircon Best Practices
- Phased initialization: BSP → topology → AP release → AP init → sync
- 30-second startup timeout, OOPS (not panic) on timeout
- Idle threads pre-allocated before releasing APs
- Init levels coordinate initialization order
### seL4 Best Practices
- Single atomic write releases all APs simultaneously
- Explicit cache maintenance for ARM32
- Big kernel lock for simplicity (not scalable)
- BOOT_BSS section for boot-time variables
---
## Improvement Plan — Patch Series
### Priority 0: Fix All Discovered Issues (P15)
#### P15-1: AP Startup LogicalCpuId Race Fix (Critical K1)
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: Replace `CPU_COUNT.load(Relaxed)` + `LogicalCpuId::new(next_cpu)` + `CPU_COUNT.fetch_add(1)` with single `let cpu_id = LogicalCpuId::new(CPU_COUNT.fetch_add(1, SeqCst))`. Remove separate load. Move all pre-startup setup (PercpuBlock init, NUMA node set) to between allocation and `fetch_add`.
**Risk**: Low. Standard atomic fix.
**Verification**: Boot with 4+ CPUs, verify all get unique IDs.
#### P15-2: AP_READY Sync Consolidation (Critical K2)
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: Replace dual mechanism with single `AtomicU8` at TRAMPOLINE+8. AP writes 1 when ready. BSP polls with SeqCst. Add `fence(SeqCst)` before/after writing trampoline args to ensure AP sees them.
**Risk**: Medium. Changes trampoline protocol.
**Verification**: Boot test on QEMU, verify all APs start correctly.
#### P15-3: TLB Shootdown Range Race Fix (Critical K3)
**Files**: `src/percpu.rs`
**Change**: Pack range into single `AtomicU64` (bits [63:32] = start page, bits [31:0] = count). Single atomic `swap` sets flag + range atomically. Handler unpacks with single `load`.
**Risk**: Medium. Affects all TLB shootdowns.
**Verification**: Multi-core stress test with frequent mmap/munmap.
#### P15-4: MCS Lock Memory Ordering (Critical K4)
**Files**: `src/sync/mcs.rs`
**Change**: Add `fence(Release)` after `next.store(new_node, Relaxed)` at line 55. Add `fence(Acquire)` before `locked.load(Relaxed)` at line 59. Change PI donation store to `Release`.
**Risk**: Low. Standard lock ordering fix.
**Verification**: Multi-threaded contention test.
#### P15-5: NUMA Node Before CPU Visible (High K8)
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: Move `record_apic_mapping()` and `percpu.numa_node.set()` BEFORE `CPU_COUNT.fetch_add()`. Add `fence(SeqCst)` between them so scheduler sees NUMA data.
**Risk**: Low. Reordering of operations.
**Verification**: Boot with QEMU SRAT, verify NUMA nodes set before scheduler sees CPUs.
#### P15-6: Init Dependency Deadlock Fix (Critical U1)
**Files**: `config/redbear-mini.toml`, `config/redbear-full.toml`
**Change**: Add `default_dependencies = false` to `00_intel-gpiod.service`, `00_i2c-dw-acpi.service`, `00_i2c-gpio-expanderd.service`, `00_i2c-hidd.service`, `ucsid.service`. Add explicit `requires_weak` for actual dependencies only.
**Risk**: Low. Config-only change.
**Verification**: Boot redbear-mini, verify all services start without deadlock.
#### P15-7: Service Timeout Mechanism (Critical U2)
**Files**: `recipes/core/base/source/init/src/service.rs`, `recipes/core/base/source/init/src/scheduler.rs`
**Change**: Add `timeout_secs: Option<u32>` to Notify and Scheme variants. Use `set_read_timeout()` on INIT_NOTIFY pipe. On timeout, log error and mark service failed. Boot continues.
**Risk**: Medium. Changes init behavior.
**Verification**: Create a service that never notifies, verify boot continues after timeout.
#### P15-8: Dependency Cycle Detection (Critical U3)
**Files**: `recipes/core/base/source/init/src/scheduler.rs`
**Change**: Add `BTreeSet<UnitId>` visited tracking in `load_units()`. If a unit ID is already in the visiting set, log cycle error and skip.
**Risk**: Low. Defensive programming.
**Verification**: Create circular dependency in test config, verify detection.
#### P15-9: Boot Timeline /tmp/ Creation (Medium U6)
**Files**: `local/recipes/system/driver-manager/source/src/main.rs`
**Change**: Add `let _ = std::fs::create_dir_all("/tmp");` at top of `main()`, before `reset_timeline_log()`.
**Risk**: Trivial.
**Verification**: Boot, verify timeline file created.
#### P15-10: TLB Range Ordering Fix (High K10)
**Files**: `src/percpu.rs`
**Change**: Change `tlb_flush_start`/`tlb_flush_count` stores from `Relaxed` to `Release`. Change handler loads from `Relaxed` to `Acquire`.
**Risk**: Low. Ordering fix.
**Verification**: Multi-core TLB stress test.
---
### Priority 1: Stabilize SMP Boot (P16)
#### P16-1: Calibrated SIPI Delays (High K7)
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: Implement `udelay(us)` using TSC (calibrated during early boot). Replace spin-count delays: 10ms INIT→SIPI, 10µs SIPI→SIPI for modern CPUs.
**Reference**: Linux `wakeup_secondary_cpu_via_init()`, Intel SDM Vol 3A §8.4.
#### P16-2: AP Startup Error Status Check (Medium)
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: After each SIPI, clear and read APIC ESR. If delivery error, retry. Log failure for each CPU. Continue boot with available CPUs.
**Reference**: Linux checks `send_status` + `accept_status`.
#### P16-3: MAX_CPU_COUNT Increase (High K12)
**Files**: `src/cpu_set.rs`
**Change**: Increase `MAX_CPU_COUNT` from 128 to 256. Add boot-time warning if CPUs approach limit.
#### P16-4: AP Startup Graceful Degradation
**Files**: `src/acpi/madt/arch/x86.rs`
**Change**: If AP fails trampoline or AP_READY timeout, log warning, skip CPU, continue boot. Track `cpu_online` mask separately from `cpu_possible`.
#### P16-5: Firmware Bug Detection
**Files**: `src/acpi/madt/mod.rs`, `src/acpi/mod.rs`
**Change**: Add duplicate APIC ID detection during MADT parsing. Add SDT checksum validation (`sum all bytes == 0`). Log warnings for unknown MADT entry types. Cross-reference MADT entries with SRAT for consistency.
---
### Priority 2: Desktop-Safe Scheduler (P17)
#### P17-1: NUMA-Aware Work Stealing (Medium K20)
**Files**: `src/context/switch.rs`
**Change**: In `select_next_context()`, prefer contexts on same NUMA node. Use `numa::topology().same_node()`. Apply penalty for cross-node stealing. Use SLIT distance matrix for weight.
#### P17-2: Transitive Priority Inheritance (Critical K5)
**Files**: `src/sync/mcs.rs`
**Change**: When donating priority to lock holder, check if holder is waiting on another MCS lock. Propagate donation transitively up to 4 levels deep (bounded). Add lock graph cycle detection.
#### P17-3: CPU Affinity (New Feature)
**Files**: `src/context/context.rs`, `src/context/switch.rs`
**Change**: Add `affinity: LogicalCpuSet` to Context. Scheduler respects mask. Default: all CPUs. Add `sched_setaffinity` syscall.
#### P17-4: Preemption Latency Bounds
**Files**: `src/context/switch.rs`
**Change**: Replace hardcoded `new_ticks >= 3` with configurable interval. Enforce `preempt_locks > 0` guard at context switch. Add preemption-safe lock wrappers.
#### P17-5: Load Balancing
**Files**: `src/context/switch.rs`
**Change**: Periodic (every 100ms) load rebalancing. Migrate tasks from CPUs with >2 runnable to idle CPUs. Use NUMA distance for cross-node decisions.
---
### Priority 3: Harden IPC & Scheme Servers (P18)
#### P18-1: Daemon Restart Policy (High U4)
**Files**: `recipes/core/base/source/init/src/service.rs`, `recipes/core/base/source/init/src/scheduler.rs`
**Change**: Add `restart = "on-failure" | "always" | "never"` to service config. Implement exponential backoff: 1s → 2s → 4s → 8s → 30s max. Track restart count, give up after 5 consecutive failures.
#### P18-2: Process Monitoring & Cleanup (High U5)
**Files**: `local/recipes/system/driver-manager/source/src/config.rs`
**Change**: Non-blocking `waitpid(WNOHANG)` poll in hotplug loop. On driver exit: release scheme, unbind PCI device, free IRQ. Notify init of failure.
#### P18-3: Bounded Scheme Request Queues (Medium)
**Change**: Add configurable queue depth limit to scheme daemons. When full, return EBUSY. Prevents memory exhaustion.
#### P18-4: Watchdog/Health Monitoring (High)
**Change**: Optional health-check ping in scheme protocol. Init checks critical services every 5s. On failure, restart per restart policy.
---
### Priority 4: Stress-Test Userspace Drivers (P19)
#### P19-1: Multi-Core Driver Stress Test
**Change**: Parallel I/O to ided/ahcid/nvmed + network e1000d + input evdevd. Verify no panics, no hangs, no data corruption over 1 hour.
#### P19-2: GPU Parallel Submission
**Change**: Multiple processes submit to virtio-gpu / redox-drm simultaneously. Verify fencing correctness, no GPU hang.
#### P19-3: USB Hotplug Under Load
**Change**: Rapid device connect/disconnect while transferring data via usbscsid. Verify cleanup and no resource leaks.
#### P19-4: Hotplug Stress Test
**Change**: QEMU virtio device hot-add/hot-remove while system under load. Verify driver-manager handles changes correctly.
---
## Estimated Effort
| Priority | Patches | Lines | Time |
|----------|---------|-------|------|
| P0 (Fix discovered) | P15-1 through P15-10 | ~800 | 2-3 days |
| P1 (SMP stabilize) | P16-1 through P16-5 | ~500 | 2-3 days |
| P2 (Scheduler) | P17-1 through P17-5 | ~1200 | 5-7 days |
| P3 (IPC harden) | P18-1 through P18-4 | ~800 | 3-5 days |
| P4 (Stress test) | P19-1 through P19-4 | ~600 | 2-3 days |
| **Total** | **24 patches** | **~3900** | **14-21 days** |
## Acceptance Criteria
- [ ] All Critical and High issues resolved
- [ ] Boot to login prompt in <10s on QEMU (4 cores)
- [ ] No panics under 72-hour stress test (4 cores, all driver types)
- [ ] AP startup race-free with 128 simulated CPUs
- [ ] NUMA topology correctly discovered from QEMU SRAT
- [ ] Service restart within 5 seconds of crash
- [ ] No priority inversion >100ms under load
- [ ] All patches in `local/patches/kernel/`, wired into `recipe.toml`
- [ ] Boot-tested on QEMU UEFI with `scripts/run_mini.sh`
## Dependency Graph
```
P15-1 (CPU_COUNT race) ─────┐
P15-2 (AP_READY sync) ──────┤
P15-3 (TLB range race) ─────┤
P15-4 (MCS ordering) ───────┼──→ P16-1 (SIPI timing)
P15-5 (NUMA ordering) ──────┤ P16-2 (ESR check)
P15-10 (TLB ordering) ──────┘ P16-3 (MAX_CPU)
P16-4 (graceful degradation)
P15-6 (init deadlock) ──────────→ P16-5 (firmware bugs)
P15-7 (service timeout)
P15-8 (cycle detection)
P15-9 (/tmp creation)
P16-* ──→ P17-1 (NUMA work stealing)
P17-2 (transitive PI)
P17-3 (CPU affinity)
P17-4 (preemption)
P17-5 (load balancing)
P17-* ──→ P18-1 (restart policy)
P18-2 (crash cleanup)
P18-3 (bounded queues)
P18-4 (watchdog)
P18-* ──→ P19-* (stress tests)
```
@@ -0,0 +1,409 @@
# SMP/Scheduler Improvement Plan
**Status**: Active
**Date**: 2026-05-16
**Authority**: Canonical execution plan for SMP hardening
**Priority order**: Bottleneck #1#2#3#4#5#6#7
## Context
Red Bear OS kernel has functional SMP (multi-core) support with x2APIC, per-CPU run queues,
work stealing, and DWRR+vruntime scheduling. However, several design choices limit scalability
on systems with more than 2-4 cores. This plan addresses the seven identified bottlenecks
in priority order.
### Reference Sources
- Linux 7.1: `local/reference/linux-7.1/` — scheduler, TLB, IRQ affinity
- seL4: `local/reference/seL4/` — lock-free kernel structures, minimal context switch
- Zircon: online reference only (download failed due to network issues)
### Key Kernel Files
| File | Lines | Purpose |
|------|-------|---------|
| `context/switch.rs` | 577 | Scheduler, context switch, DWRR |
| `context/arch/x86_64.rs` | 395 | CONTEXT_SWITCH_LOCK, switch_to, register save/restore |
| `percpu.rs` | 205 | PercpuBlock, TLB shootdown |
| `arch/x86_shared/ipi.rs` | 53 | IPI kinds and dispatch |
| `arch/x86_shared/device/local_apic.rs` | 312 | x2APIC/xAPIC, ICR programming |
| `arch/x86_shared/device/ioapic.rs` | 476 | IOAPIC, IRQ routing, MapInfo |
| `acpi/madt/arch/x86.rs` | 354 | AP startup, SIPI, trampoline |
### Red Bear Patches (already applied)
| Patch | Lines | Purpose |
|-------|-------|---------|
| P8-percpu-sched | 123 | Per-CPU scheduler queues |
| P8-percpu-wiring | 985 | Work stealing, load balancing, vruntime, affinity |
| P8-work-stealing | 190 | Steal statistics, migration helpers |
| P8-msi | 281 | MSI/MSI-X foundation, vector allocation |
| P8-msi-foundation-v2 | 188 | MSI refinement |
---
## Bottleneck #1: Global CONTEXT_SWITCH_LOCK
**Severity**: 🔴 Critical — serializes ALL context switches across ALL CPUs
**Files**: `context/arch/x86_64.rs:19`, `context/switch.rs:123,156,171,296`
### Current State
```rust
// context/arch/x86_64.rs:19
pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
// context/switch.rs:156-162
while arch::CONTEXT_SWITCH_LOCK
.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
.is_err()
{
hint::spin_loop();
percpu.maybe_handle_tlb_shootdown();
}
```
Every context switch on ANY CPU must acquire this single global lock. On an 8-core system,
7 CPUs spin-wait while one CPU performs its context switch. This is the primary SMP scalability
limiter.
### Why It Exists
The comment says: "Acquire the global lock to ensure exclusive access during context switch
and avoid issues that would be caused by the unsafe operations below."
The concern is that during `switch_to`, the CPU is in a transitional state:
1. Prev context's registers are saved
2. Next context's registers are being loaded
3. Stack pointer changes to next context's stack
4. `switch_finish_hook` runs to drop guards and release lock
During steps 1-4, another CPU switching to the same context could cause data races.
### Analysis: The Lock Is Overly Conservative
The per-context write locks (`Arc<ContextLock>`) already prevent concurrent access to the
same context. The `switch()` function:
1. Locks prev context (write) — prevents anyone else from modifying it
2. Locks next context (write) — prevents anyone else from modifying it
3. Updates running flags and CPU IDs (under both write locks)
4. Stores both guards in `switch_result` (kept alive until `switch_finish_hook`)
5. Calls `arch::switch_to` (register swap)
If CPU 0 holds write locks on contexts A and B, CPU 1 cannot lock either A or B.
The global lock adds nothing for correctness — it only serializes independent switches
involving completely different contexts.
### Fix: Per-CPU Flag in PercpuBlock
Replace the global `AtomicBool` with a per-CPU flag in `ContextSwitchPercpu`:
```rust
// percpu.rs — add to ContextSwitchPercpu
pub in_context_switch: AtomicBool,
```
The per-CPU flag:
- Each CPU acquires its own flag before switching — zero cross-CPU contention
- Debug assertion catches re-entrant switches on the same CPU
- Released in `switch_finish_hook` as before
### Implementation Steps
1. Add `in_context_switch: Cell<bool>` to `ContextSwitchPercpu` in `switch.rs`
2. Remove `CONTEXT_SWITCH_LOCK` from `context/arch/x86_64.rs` (and aarch64, riscv64)
3. Replace `arch::CONTEXT_SWITCH_LOCK.compare_exchange_weak(...)` with per-CPU flag check
4. Replace `arch::CONTEXT_SWITCH_LOCK.store(false, ...)` with per-CPU flag release
5. Update `switch_finish_hook` accordingly
6. Rebuild kernel, verify boot
### Risk Assessment
- **Low risk**: The per-CPU flag is structurally equivalent to the global lock for each CPU.
The global lock's only effect was preventing concurrent switches on *different* CPUs, which
is unnecessary given per-context write locks.
- **Safety net**: Keep the per-CPU flag as a debug assertion. If re-entrant switching is
detected, panic instead of corrupting state.
---
## Bottleneck #2: No Broadcast TLB Shootdown
**Severity**: 🔴 Critical — O(N) shootdown on N CPUs, each with individual IPI
**Files**: `percpu.rs:75-113`, `ipi.rs:22-38`
### Current State
```rust
// percpu.rs:106-112 — shootdown_tlb_ipi when target is None (broadcast)
for id in 0..crate::cpu_count() {
// TODO: Optimize: use global counter and percpu ack counters, send IPI using
// destination shorthand "all CPUs".
shootdown_tlb_ipi(Some(LogicalCpuId::new(id)));
}
```
Broadcast TLB shootdown is implemented as a loop, sending individual IPIs to each CPU.
Each IPI requires:
1. Set `wants_tlb_shootdown` flag on target CPU
2. Spin-wait if previous shootdown is still pending
3. Send IPI via `ipi_single()`
4. Target CPU processes IPI in interrupt handler
On a 128-core system, this means 127 individual IPI sends, each with spin-wait overhead.
### Fix: x2APIC Destination Shorthand
The Local APIC supports destination shorthands in the ICR:
- `01b` = "self" (Current)
- `10b` = "all including self" (All)
- `11b` = "all except self" (Other)
The `IpiTarget` enum already defines these values (`ipi.rs:15-19`), and the `ipi()` function
(`ipi.rs:22-38`) already supports them. We just need to use `IpiTarget::Other` for broadcast
TLB shootdowns.
### Implementation Steps
1. Add `tlb_shootdown_pending: AtomicU32` ACK counter to `PercpuBlock`
2. Add global `TLB_SHOOTDOWN_GENERATION: AtomicU32` counter
3. In `shootdown_tlb_ipi(None)`:
- Increment generation counter
- Set `wants_tlb_shootdown` on all CPUs (lock-free)
- Send single IPI with `IpiTarget::Other` shorthand
4. In `maybe_handle_tlb_shootdown()`:
- Process shootdown
- Increment ACK counter
5. Add `wait_for_tlb_acknowledgments()` with timeout
6. Rebuild kernel, verify boot
### x2APIC ICR Format
For x2APIC, the ICR is a single 64-bit MSR write:
```
Bits 63:32 = Destination APIC ID (ignored for shorthands)
Bits 19:18 = Destination Shorthand (0=none, 1=self, 2=all, 3=all-except-self)
Bit 14 = Trigger Mode (0=edge, 1=level)
Bits 11:8 = Delivery Mode (0=fixed)
Bits 7:0 = Vector
```
For "all except self" broadcast with TLB vector (0x41):
```rust
let icr = (3u64 << 18) | (1 << 14) | (IpiKind::Tlb as u64);
// = 0x000C0000_00000041
```
---
## Bottleneck #3: IRQ Affinity Not Wired to IOAPIC
**Severity**: 🟡 Medium — stored but never applied to hardware
**Files**: `ioapic.rs`, MSI patches `P8-msi.patch`
### Current State
The IOAPIC `MapInfo` struct has a `dest: ApicId` field, and `DestinationMode` enum has
`Logical` variant. However:
1. **No `set_affinity()` function** — there's no way to reprogram an IOAPIC redirection
entry to change its destination APIC
2. **Legacy IRQs all route to BSP**`init()` hardcodes `bsp_apic_id` as destination
3. **MSI patches store affinity**`P8-msi.patch` adds `set_irq_affinity()` API but
doesn't reprogram IOAPIC hardware
### Fix: Add IOAPIC IRQ Affinity
Add a function to reprogram the IOAPIC redirection table entry:
```rust
impl IoApic {
pub fn set_irq_affinity(&self, gsi: u32, dest: ApicId) -> bool {
let idx = (gsi - self.gsi_start) as u8;
let mut guard = self.regs.lock();
let Some(mut entry) = guard.read_ioredtbl(idx) else {
return false;
};
// Clear destination (bits 63:56 for xAPIC, bits 63:32 for x2APIC)
// xAPIC: destination is bits 63:56
entry &= !(0xFF << 56);
entry |= u64::from(dest.get()) << 56;
guard.write_ioredtbl(idx, entry)
}
}
```
Add a public API to find the right IOAPIC and call it:
```rust
pub fn set_affinity(irq: u8, dest: ApicId) {
let gsi = resolve(irq);
if let Some(apic) = find_ioapic(gsi) {
apic.set_irq_affinity(gsi, dest);
}
}
```
### Implementation Steps
1. Add `IoApic::set_irq_affinity()` method
2. Add `ioapic::set_affinity()` public function
3. Wire into kernel IRQ scheme `set_affinity` handler
4. Add round-robin or numa-aware default affinity for new IRQs
5. Rebuild kernel, verify boot
---
## Bottleneck #4: Simple Spinlocks for Scheduler Queues
**Severity**: 🟡 Medium — unfair under contention
**Files**: `context/switch.rs` (run_contexts access)
### Current State
Per-CPU run queues use `spin::Mutex` (simple spinlock). Under contention:
- No fairness guarantee — a CPU may spin indefinitely
- No backoff — constant cache line bouncing
- No NUMA awareness — cross-socket contention is expensive
### Fix: MCS Lock or Try-Lock with Backoff
Replace `spin::Mutex` with an MCS lock (John Mellor-Crummey and Michael Scott):
- Each waiter spins on a local flag (cache-line friendly)
- FIFO ordering guarantees fairness
- O(1) cache line transfers on unlock
Alternatively, since per-CPU queues should have low contention:
- Use `try_lock()` with exponential backoff
- Fall back to global queue if per-CPU queue is contended
### Implementation Steps
1. Implement MCS lock primitive in `sync/`
2. Replace `spin::Mutex` in run queue access
3. Add contention statistics to `PercpuBlock`
4. Rebuild kernel, verify boot
---
## Bottleneck #5: No NUMA Topology Awareness
**Severity**: 🟡 Medium — treats all CPUs and memory as uniform
**Files**: `acpi/madt/mod.rs`, `percpu.rs`
### Current State
- No SRAT parsing (NUMA proximity domains)
- No SLIT parsing (NUMA distance matrix)
- Work stealing is random — may steal from a remote socket
- Memory allocation is uniform — no preference for local node
### Fix: ACPI SRAT + SLIT Parsing
1. Parse SRAT (System Resource Affinity Table) for CPU-to-node mapping
2. Parse SLIT (System Locality Information Table) for distance matrix
3. Add `numa_node: u32` to `PercpuBlock`
4. Prefer stealing from same-socket CPUs
5. Prefer allocating memory from local node
### Implementation Steps
1. Add SRAT/SLIT table parsing in `acpi/`
2. Extend `PercpuBlock` with NUMA info
3. Update work stealing to prefer local node
4. Update memory allocator with NUMA hints
5. Rebuild kernel, verify boot
---
## Bottleneck #6: Coarse TLB Flush
**Severity**: 🟡 Low-Medium — full TLB flush instead of range-based
**Files**: `percpu.rs:122`
### Current State
```rust
// percpu.rs:122
crate::memory::RmmA::invalidate_all();
```
Every TLB shootdown flushes the **entire** TLB, even when only a single page changed.
Full TLB flush is extremely expensive on modern CPUs with large TLBs.
### Fix: Range-Based and Single-Page Invalidation
Use x86 `INVLPG` for single-page invalidation:
```rust
// For single page:
x86::tlb::flush(page);
// For range:
for page in range.step_by(PAGE_SIZE) {
x86::tlb::flush(page);
}
// Only use full flush for large ranges (> 32 pages)
```
### Implementation Steps
1. Add `shootdown_range(start: Page, count: usize)` to percpu
2. Store shootdown range in `PercpuBlock` alongside flag
3. Replace `invalidate_all()` with conditional INVLPG
4. Fall back to full flush for large ranges (> 32 pages) or PCID flush
5. Rebuild kernel, verify boot
---
## Bottleneck #7: No Priority Inheritance
**Severity**: 🟡 Low — mutex priority inversion possible
**Files**: `sync/` (various lock primitives)
### Current State
No priority inheritance protocol. A low-priority thread holding a mutex can be preempted,
causing a high-priority thread waiting on the same mutex to block indefinitely (priority
inversion).
### Fix: Priority Inheritance for Mutexes
Implement the Basic Priority Inheritance Protocol (PI):
1. When a thread blocks on a mutex, donate its priority to the mutex holder
2. When the mutex is released, restore the original priority
3. Support multiple donors (priority queue of donors)
### Implementation Steps
1. Add `donated_priority: Option<usize>` to `Context`
2. Implement priority donation in mutex lock acquisition
3. Implement priority restoration in mutex unlock
4. Add debug assertions to detect inversion
5. Rebuild kernel, verify boot
---
## Execution Timeline
| Phase | Bottleneck | Duration | Dependencies |
|-------|-----------|----------|-------------|
| 1 | #1 CONTEXT_SWITCH_LOCK | 1-2 days | None |
| 2 | #2 Broadcast TLB shootdown | 1-2 days | Phase 1 (per-CPU flags) |
| 3 | #3 IOAPIC IRQ affinity | 1-2 days | None |
| 4 | #4 MCS locks | 2-3 days | Phase 1 (reduced contention) |
| 5 | #6 Range TLB flush | 1 day | Phase 2 (shootdown infrastructure) |
| 6 | #5 NUMA awareness | 3-5 days | Phase 4 (scheduler queues) |
| 7 | #7 Priority inheritance | 2-3 days | None |
**Total estimate**: 11-18 days
## Patch Naming Convention
New kernel patches following this plan:
- `P9-percpu-context-switch.patch` — Bottleneck #1
- `P9-broadcast-tlb-shootdown.patch` — Bottleneck #2
- `P9-ioapic-irq-affinity.patch` — Bottleneck #3
- `P9-mcs-locks.patch` — Bottleneck #4
- `P9-range-tlb-flush.patch` — Bottleneck #6
- `P9-numa-awareness.patch` — Bottleneck #5
- `P9-priority-inheritance.patch` — Bottleneck #7
All P9 patches must be applied after P8 patches in `recipe.toml`.
+328
View File
@@ -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 (12 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 (35 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 (24 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 (48 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 (48 weeks)
After Phases 24 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.34.6
└──→ Phase 5 (advanced) ──→ depends on Phases 2, 3, 4
```
### Effort Estimate (2 developers)
| Phase | Duration | Parallelizable? |
|-------|----------|-----------------|
| Phase 1 | 12 weeks | Yes (with Phase 2 start) |
| Phase 2 | 35 weeks | Partially (2.1 blocks 2.22.7) |
| Phase 3 | 24 weeks | Yes (parallel with Phase 2) |
| Phase 4 | 48 weeks | Partially (4.1 gates rest) |
| Phase 5 | 48 weeks | After Phases 24 |
| **Total** | **1427 weeks** | ~814 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
@@ -2,7 +2,7 @@ diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs
index 9f507221..c69c2cfa 100644 index 9f507221..c69c2cfa 100644
--- a/daemon/src/lib.rs --- a/daemon/src/lib.rs
+++ b/daemon/src/lib.rs +++ b/daemon/src/lib.rs
@@ -10,15 +10,26 @@ use libredox::Fd; @@ -10,15 +10,25 @@ use libredox::Fd;
use redox_scheme::Socket; use redox_scheme::Socket;
use redox_scheme::scheme::{SchemeAsync, SchemeSync}; use redox_scheme::scheme::{SchemeAsync, SchemeSync};
@@ -10,7 +10,6 @@ index 9f507221..c69c2cfa 100644
- let fd: RawFd = std::env::var(var).unwrap().parse().unwrap(); - let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
+unsafe fn get_fd(var: &str) -> Option<RawFd> { +unsafe fn get_fd(var: &str) -> Option<RawFd> {
+ let fd: RawFd = match std::env::var(var) + let fd: RawFd = match std::env::var(var)
+ .map_err(|e| eprintln!("daemon: env var {var} not set: {e}"))
+ .ok() + .ok()
+ .and_then(|val| { + .and_then(|val| {
+ val.parse() + val.parse()
@@ -33,7 +32,7 @@ index 9f507221..c69c2cfa 100644
} }
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) { unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
@@ -38,20 +49,26 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) { @@ -38,20 +48,26 @@ unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
/// A long running background process that handles requests. /// A long running background process that handles requests.
#[must_use = "Daemon::ready must be called"] #[must_use = "Daemon::ready must be called"]
pub struct Daemon { pub struct Daemon {
@@ -63,7 +62,7 @@ index 9f507221..c69c2cfa 100644
} }
/// Executes `Command` as a child process. /// Executes `Command` as a child process.
@@ -83,25 +100,28 @@ impl Daemon { @@ -83,25 +99,28 @@ impl Daemon {
/// A long running background process that handles requests using schemes. /// A long running background process that handles requests using schemes.
#[must_use = "SchemeDaemon::ready must be called"] #[must_use = "SchemeDaemon::ready must be called"]
pub struct SchemeDaemon { pub struct SchemeDaemon {
@@ -0,0 +1,79 @@
--- a/init/src/service.rs
+++ b/init/src/service.rs
@@ -1,7 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
use std::ffi::OsString;
use std::io::Read;
-use std::os::fd::{AsRawFd, OwnedFd};
+use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
use std::{env, io};
@@ -11,6 +11,23 @@
use crate::color::{init_error, init_warn, status_fail};
use crate::script::subst_env;
+/// Default timeout in seconds for waiting for service readiness notification.
+/// Prevents boot from hanging indefinitely if a daemon fails to notify.
+const SERVICE_READY_TIMEOUT_SECS: u32 = 30;
+
+/// Wait for data to be available on a file descriptor, with a timeout.
+/// Returns true if data is ready, false if timed out.
+fn poll_fd_timeout(fd: RawFd, timeout_secs: u32) -> bool {
+ let mut pollfd = libc::pollfd {
+ fd,
+ events: libc::POLLIN,
+ revents: 0,
+ };
+ let timeout_ms = (timeout_secs as libc::c_int) * 1000;
+ let result = unsafe { libc::poll(&mut pollfd, 1, timeout_ms) };
+ result > 0
+}
+
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Service {
@@ -75,16 +92,36 @@
let _ = unsafe { libc::close(write_raw) };
match &self.type_ {
- ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
- Ok(()) => {}
- Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
- init_warn(&format!("{:?} exited without notifying readiness", command));
+ ServiceType::Notify => {
+ if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
+ init_warn(&format!(
+ "{:?} timed out after {}s waiting for readiness",
+ command, SERVICE_READY_TIMEOUT_SECS
+ ));
+ let _ = child.kill();
+ let _ = child.wait();
+ return;
}
- Err(err) => {
- init_error(&format!("failed to wait for {:?}: {}", command, err));
+ match read_pipe.read_exact(&mut [0]) {
+ Ok(()) => {}
+ Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
+ init_warn(&format!("{:?} exited without notifying readiness", command));
+ }
+ Err(err) => {
+ init_error(&format!("failed to wait for {:?}: {}", command, err));
+ }
}
- },
+ }
ServiceType::Scheme(scheme) => {
+ if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
+ init_warn(&format!(
+ "{:?} timed out after {}s waiting for scheme registration",
+ command, SERVICE_READY_TIMEOUT_SECS
+ ));
+ let _ = child.kill();
+ let _ = child.wait();
+ return;
+ }
let mut new_fd = usize::MAX;
loop {
match syscall::call_ro(
@@ -0,0 +1,34 @@
--- 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,12 @@
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()];
+ // Track all units ever seen (queued or loaded) to avoid re-queuing.
+ // A true cycle exists only when a unit depends on itself through its
+ // ancestor chain, not when two independent units share a dependency.
+ let mut seen = BTreeSet::new();
+ seen.insert(root_unit);
while let Some(unit_id) = pending_units.pop() {
if self.units.contains_key(&unit_id) {
@@ -86,6 +91,11 @@
if let Some(unit) = unit {
loaded_units.push(unit.clone());
for dep in &self.unit(&unit).info.requires_weak {
+ // If the dependency is already loaded or already queued,
+ // it's a shared dependency — not a cycle. Skip it.
+ if !seen.insert(dep.clone()) {
+ continue;
+ }
pending_units.push(dep.clone());
}
}
@@ -0,0 +1,280 @@
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 @@
pub inherit_envs: BTreeSet<String>,
#[serde(rename = "type")]
pub type_: ServiceType,
+ /// Restart policy for this service. Default: Never (no restart on exit).
+ #[serde(default)]
+ pub restart: RestartPolicy,
+ /// Maximum consecutive restart attempts before giving up. Default: 3.
+ #[serde(default = "default_max_restarts")]
+ pub max_restarts: u32,
+}
+
+fn default_max_restarts() -> u32 {
+ 3
+}
+
+#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+pub enum RestartPolicy {
+ /// Never restart (default — current behavior).
+ #[default]
+ Never,
+ /// Restart on non-zero exit code.
+ OnFailure,
+ /// Restart on any exit (including clean).
+ Always,
}
#[derive(Clone, Debug, Default, Deserialize)]
@@ -53,7 +75,9 @@
}
impl Service {
- pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) {
+ /// Returns Some(child_pid) for long-running services (Notify, Scheme),
+ /// None for Oneshot/OneshotAsync or if the spawn failed.
+ pub fn spawn(&self, base_envs: &BTreeMap<String, OsString>) -> Option<u32> {
let mut command = Command::new(&self.cmd);
command.args(self.args.iter().map(|arg| subst_env(arg)));
command.env_clear();
@@ -72,6 +96,7 @@
status_fail(&format!("failed to execute {:?}: {}", command, err));
}
}
+ None
}
_ => {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
@@ -85,7 +110,7 @@
let _ = unsafe { libc::close(write_raw) };
drop(read_pipe);
status_fail(&format!("failed to execute {:?}: {}", command, err));
- return;
+ return None;
}
};
@@ -100,7 +125,7 @@
));
let _ = child.kill();
let _ = child.wait();
- return;
+ return None;
}
match read_pipe.read_exact(&mut [0]) {
Ok(()) => {}
@@ -111,6 +136,7 @@
init_error(&format!("failed to wait for {:?}: {}", command, err));
}
}
+ Some(child.id())
}
ServiceType::Scheme(scheme) => {
if !poll_fd_timeout(read_pipe.as_raw_fd(), SERVICE_READY_TIMEOUT_SECS) {
@@ -120,7 +146,7 @@
));
let _ = child.kill();
let _ = child.wait();
- return;
+ return None;
}
let mut new_fd = usize::MAX;
loop {
@@ -135,16 +161,16 @@
}) => continue,
Ok(0) => {
init_warn(&format!("{:?} exited without notifying readiness", command));
- return;
+ return None;
}
Ok(1) => break,
Ok(n) => {
init_error(&format!("incorrect amount of fds {} returned", n));
- return;
+ return None;
}
Err(err) => {
init_error(&format!("failed to wait for {:?}: {}", command, err));
- return;
+ return None;
}
}
}
@@ -152,6 +178,7 @@
let current_namespace_fd = libredox::call::getns().expect("TODO");
libredox::call::register_scheme_to_ns(current_namespace_fd, scheme, new_fd)
.expect("TODO");
+ Some(child.id())
}
ServiceType::Oneshot => {
drop(read_pipe);
@@ -165,6 +192,7 @@
init_error(&format!("failed to wait for {:?}: {}", command, err))
}
}
+ None
}
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
@@ -2,17 +2,11 @@
use crate::InitConfig;
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;
-#[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) {
+ if service.restart != RestartPolicy::Never {
+ supervised.insert(
+ pid,
+ ServiceState {
+ unit_id: unit.id.clone(),
+ cmd: service.cmd.clone(),
+ restart_policy: service.restart.clone(),
+ max_restarts: service.max_restarts,
+ restart_count: 0,
+ last_restart_ms: 0,
+ },
+ );
+ }
+ }
}
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 @@
use libredox::flag::{O_RDONLY, O_WRONLY};
-use crate::scheduler::Scheduler;
+use crate::scheduler::{Scheduler, ServiceState};
+use crate::service::RestartPolicy;
use crate::unit::{UnitId, UnitStore};
mod color;
@@ -176,15 +177,95 @@
if scheduler.has_pending() {
// Reap exited children before processing more services.
let mut status = 0;
- while libredox::call::waitpid(0, &mut status, 1).is_ok() {}
+ while let Ok(pid) = libredox::call::waitpid(0, &mut status, 1) {
+ handle_child_exit(pid as u32, status, &mut scheduler, &mut unit_store, &mut init_config);
+ }
scheduler.step(&mut unit_store, &mut init_config);
}
let mut status = 0;
match libredox::call::waitpid(0, &mut status, 1) {
- Ok(_pid) => {}
+ Ok(pid) => {
+ handle_child_exit(pid as u32, status, &mut scheduler, &mut unit_store, &mut init_config);
+ }
Err(err) => init_error(&format!("waitpid error: {}", err)),
}
}
}
+
+fn handle_child_exit(
+ pid: u32,
+ exit_status: i32,
+ scheduler: &mut Scheduler,
+ unit_store: &mut UnitStore,
+ init_config: &mut InitConfig,
+) {
+ let Some(state) = scheduler.supervised.remove(&pid) else {
+ return;
+ };
+
+ let exited_cleanly = exit_status == 0;
+ let should_restart = match state.restart_policy {
+ RestartPolicy::Never => false,
+ RestartPolicy::OnFailure => !exited_cleanly,
+ RestartPolicy::Always => true,
+ };
+
+ if !should_restart {
+ init_warn(&format!(
+ "service {} (pid {}) exited with status {} — not restarting (policy: {:?})",
+ state.cmd, pid, exit_status, state.restart_policy,
+ ));
+ return;
+ }
+
+ if state.restart_count >= state.max_restarts {
+ init_warn(&format!(
+ "service {} exceeded max_restarts ({}) — giving up",
+ state.cmd, state.max_restarts,
+ ));
+ return;
+ }
+
+ let backoff_secs = 1u64 << state.restart_count.min(4);
+ let backoff_secs = backoff_secs.min(30);
+ init_warn(&format!(
+ "service {} (pid {}) exited with status {} — restarting in {}s (attempt {}/{})",
+ state.cmd,
+ pid,
+ exit_status,
+ backoff_secs,
+ state.restart_count + 1,
+ state.max_restarts,
+ ));
+
+ std::thread::sleep(std::time::Duration::from_secs(backoff_secs));
+
+ let unit_id = state.unit_id.clone();
+ let new_restart_count = state.restart_count + 1;
+
+ 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) {
+ init_warn(&format!(
+ "restarted service {} as pid {} (attempt {}/{})",
+ state.cmd, new_pid, new_restart_count, state.max_restarts,
+ ));
+ scheduler.supervised.insert(
+ new_pid,
+ ServiceState {
+ unit_id,
+ cmd: service.cmd.clone(),
+ restart_policy: state.restart_policy.clone(),
+ max_restarts: state.max_restarts,
+ restart_count: new_restart_count,
+ last_restart_ms: std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_millis() as u64,
+ },
+ );
+ }
+ }
+}
@@ -0,0 +1,44 @@
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -162,20 +162,32 @@
| CommandRegister::IO_ENABLE
});
- // Disable MSI and MSI-X in case a previous driver instance enabled them.
- for capability in capabilities {
+ // Detect MSI capabilities for logging
+ let has_msix = capabilities.iter().any(|c| matches!(c, PciCapability::MsiX(_)));
+ let has_msi = capabilities.iter().any(|c| matches!(c, PciCapability::Msi(_)));
+
+ // Disable MSI and MSI-X to start from a clean state.
+ // Drivers that support MSI will re-enable it via the pcid scheme interface
+ // using irq_helpers::pci_allocate_interrupt_vector(), which handles
+ // MSI-X -> MSI -> legacy fallback automatically.
+ for capability in capabilities.iter_mut() {
match capability {
- PciCapability::Msi(capability) => {
- capability.set_enabled(false, pcie);
- }
- PciCapability::MsiX(capability) => {
- capability.set_enabled(false, pcie);
- }
+ PciCapability::Msi(cap) => cap.set_enabled(false, pcie),
+ PciCapability::MsiX(cap) => cap.set_enabled(false, pcie),
_ => {}
}
}
- // Set IRQ line to 9 if not set
+ // Log MSI capability for debugging. Legacy IRQ is still configured as
+ // a baseline — drivers without MSI support (e.g., ahcid) need it.
+ // Drivers with MSI support will switch away from legacy via the scheme interface.
+ if has_msix {
+ info!(" has MSI-X capability (legacy IRQ as fallback)");
+ } else if has_msi {
+ info!(" has MSI capability (legacy IRQ as fallback)");
+ }
+
+ // Legacy IRQ baseline for all devices
let mut irq = 0xFF;
let mut interrupt_pin = 0xFF;
@@ -0,0 +1,94 @@
--- a/drivers/acpid/src/acpi.rs
+++ b/drivers/acpid/src/acpi.rs
@@ -266,7 +266,34 @@
let format_err = |err| format!("{:?}", err);
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 (0xE00000xFFFFF) 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)?;
--- a/drivers/acpid/src/aml_physmem.rs
+++ b/drivers/acpid/src/aml_physmem.rs
@@ -190,7 +190,10 @@
.unwrap_or_else(|poisoned| poisoned.into_inner());
match page_cache.read_from_phys::<T>(address) {
Ok(value) => value,
- Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error),
+ Err(error) => {
+ log::error!("AML physmem read failed at {:#x}: {} — returning zero", address, error);
+ T::zero()
+ }
}
}
@@ -252,13 +255,26 @@
let offset = phys & OFFSET_MASK;
let pages = (offset + size + PAGE_SIZE - 1) / PAGE_SIZE;
let map_size = pages * PAGE_SIZE;
- let virt_page = common::physmap(
+ let virt_page = match common::physmap(
phys_page,
map_size,
common::Prot::RW,
common::MemoryType::default(),
- )
- .expect("failed to map physical region") as usize;
+ ) {
+ Ok(v) => v as usize,
+ Err(err) => {
+ log::error!(
+ "failed to map physical region {:#x}+{:#x}: {:?} — mapping zero page fallback",
+ phys_page,
+ map_size,
+ err
+ );
+ // Map the zero page as a safe fallback so the pointer is at least valid
+ // Reads will return zeroes; this is better than crashing.
+ common::physmap(0, PAGE_SIZE, common::Prot::RW, common::MemoryType::default())
+ .expect("failed to map even the zero page") as usize
+ }
+ };
PhysicalMapping {
physical_start: phys,
virtual_start: NonNull::new((virt_page + offset) as *mut T).unwrap(),
@@ -269,9 +285,8 @@
}
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>) {
let virt_page = region.virtual_start.addr().get() & PAGE_MASK;
- unsafe {
- libredox::call::munmap(virt_page as *mut (), region.mapped_length)
- .expect("failed to unmap physical region")
+ if let Err(e) = unsafe { libredox::call::munmap(virt_page as *mut (), region.mapped_length) } {
+ log::error!("failed to unmap physical region at {:#x}: {:?}", virt_page, e);
}
}
@@ -0,0 +1,118 @@
--- a/ipcd/src/chan.rs
+++ b/ipcd/src/chan.rs
@@ -16,6 +16,9 @@
path: Option<String>,
awaiting: VecDeque<usize>,
}
+
+/// Maximum pending connections per listener (like Linux SOMAXCONN).
+const MAX_LISTENER_BACKLOG: usize = 64;
#[derive(Debug)]
pub enum Extra {
Client(Client),
@@ -66,6 +69,9 @@
pub fn connect(&mut self, other: usize) -> Result<()> {
match self.extra {
Extra::Listener(ref mut listener) => {
+ if listener.awaiting.len() >= MAX_LISTENER_BACKLOG {
+ return Err(Error::new(ECONNREFUSED));
+ }
listener.awaiting.push_back(other);
Ok(())
}
--- a/ipcd/src/uds/stream.rs
+++ b/ipcd/src/uds/stream.rs
@@ -32,6 +32,11 @@
}
}
+/// Maximum pending connections per UDS listener.
+const MAX_UDS_LISTENER_BACKLOG: usize = 64;
+/// Maximum queued data packets per connection before dropping/warning.
+const MAX_UDS_PACKET_QUEUE: usize = 256;
+
#[derive(Debug, Default, Clone, PartialEq, Eq)]
struct Connection {
peer: usize,
@@ -305,14 +310,18 @@
}
_ => return Err(Error::new(ECONNREFUSED)),
}
- self.connect_unchecked(other, client_ucred);
+ self.connect_unchecked(other, client_ucred)?;
Ok(())
}
- fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) {
+ fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> {
+ if self.awaiting.len() >= MAX_UDS_LISTENER_BACKLOG {
+ return Err(Error::new(ECONNREFUSED));
+ }
self.awaiting.push_back((other.primary_id, client_ucred));
other.state = State::Connecting;
other.connection = Some(Connection::new(self.primary_id));
+ Ok(())
}
fn is_listening(&self) -> bool {
@@ -753,6 +762,9 @@
return Ok(0);
}
+ if connection.packets.len() >= MAX_UDS_PACKET_QUEUE {
+ return Err(Error::new(EAGAIN));
+ }
connection.packets.push_back(packet);
(payload_len, remote_id)
};
@@ -997,7 +1010,7 @@
return Err(Error::new(EPIPE));
}
let pair_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ };
- socket.connect_unchecked(&mut new, pair_ucred);
+ socket.connect_unchecked(&mut new, pair_ucred)?;
}
// smoltcp sends writeable whenever a listener gets a
@@ -1059,6 +1072,9 @@
name,
);
let packet = DataPacket::new(buf.to_vec(), ancillary_data);
+ if connection.packets.len() >= MAX_UDS_PACKET_QUEUE {
+ return Err(Error::new(EAGAIN));
+ }
connection.packets.push_back(packet);
}
}
--- a/ipcd/src/uds/dgram.rs
+++ b/ipcd/src/uds/dgram.rs
@@ -21,6 +21,9 @@
mem,
rc::Rc,
};
+
+/// Maximum queued datagrams per socket.
+const MAX_DGRAM_QUEUE: usize = 256;
use syscall::{error::*, flag::*, schemev2::NewFdFlags, Error, FobtainFdFlags, Stat};
#[derive(Debug, Default)]
@@ -393,6 +396,9 @@
Credential::new(pid as i32, uid as i32, gid as i32),
)?;
let payload_len = message.len();
+ if socket.messages.len() >= MAX_DGRAM_QUEUE {
+ return Err(Error::new(EAGAIN));
+ }
socket.messages.push_back(message);
Ok(payload_len)
@@ -559,6 +565,9 @@
name,
),
);
+ if remote.messages.len() >= MAX_DGRAM_QUEUE {
+ return Err(Error::new(EAGAIN));
+ }
remote.messages.push_back(message);
self.post_fevent(remote_id, EVENT_READ.bits())?;
@@ -0,0 +1,208 @@
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
@@ -121 +121 @@ pub fn allocate_aligned_interrupt_vectors(
- let mut first = None;
+ let mut first_aligned: Option<u8> = None;
@@ -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;
@@ -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 => {
+ drop(handles.drain(..));
+ first_aligned = None;
+ index = 0;
+ continue;
+ }
+
@@ -155 +164 @@ pub fn allocate_aligned_interrupt_vectors(
- let first = match first {
+ let first = match first_aligned {
@@ -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)> {
@@ -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,
+ Err(_) => {
+ log::warn!("cpu_id {} too large for MSI address format", cpu_id);
+ return None;
+ }
+ };
@@ -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");
+ let (vector, interrupt_handle) = match allocate_single_interrupt_vector(cpu_id) {
+ Ok(Some(result)) => result,
+ Ok(None) => {
+ log::warn!("no interrupt vectors available for MSI on CPU {}", cpu_id);
+ return None;
+ }
+ Err(err) => {
+ log::warn!("failed to allocate interrupt vector for MSI on CPU {}: {}", cpu_id, err);
+ return None;
+ }
+ };
@@ -197 +220 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
- (
+ Some((
@@ -203 +226 @@ pub fn allocate_single_interrupt_vector_for_msi(cpu_id: usize) -> (MsiAddrAndDat
- )
+ ))
@@ -209 +232 @@ pub fn allocate_first_msi_interrupt_on_bsp(
-) -> File {
+) -> Option<File> {
@@ -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);
+ return None;
+ }
+ };
@@ -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)?;
@@ -228 +257 @@ pub fn allocate_first_msi_interrupt_on_bsp(
- interrupt_handle
+ Some(interrupt_handle)
@@ -285,2 +313,0 @@ pub fn pci_allocate_interrupt_vector(
- pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
-
@@ -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);
+ 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 {
+ pcid_handle.enable_feature(crate::driver_interface::PciFeature::MsiX);
+
+ return InterruptVector {
@@ -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),
+ 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,
@@ -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 {
+
+ if let Some(irq) = pcid_handle.config().func.legacy_interrupt_line {
@@ -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
@@ -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) {
+ Ok(()) => unreachable!(),
+ Err(err) => {
+ log::error!("virtio-blkd: fatal error: {err}");
+ std::process::exit(1);
+ }
+ }
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
@@ -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);
+ if let Some((msg_addr_and_data, interrupt_handle)) =
+ allocate_single_interrupt_vector_for_msi(destination_id)
+ {
@@ -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");
+ };
+ }
+
+ 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");
+ }
+
+ if let Some(irq) = pci_config.func.legacy_interrupt_line {
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 (0xE00000xFFFFF) 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,48 @@
diff --git a/drivers/graphics/fbbootlogd/src/scheme.rs b/drivers/graphics/fbbootlogd/src/scheme.rs
index 812c4a5b..08bd1805 100644
--- a/drivers/graphics/fbbootlogd/src/scheme.rs
+++ b/drivers/graphics/fbbootlogd/src/scheme.rs
@@ -41,14 +41,22 @@ impl FbbootlogScheme {
}
pub fn handle_handoff(&mut self) {
- let new_display_handle = match self.input_handle.open_display_v2() {
- Ok(display) => V2GraphicsHandle::from_file(display).unwrap(),
+ let display = match self.input_handle.open_display_v2() {
+ Ok(display) => display,
Err(err) => {
eprintln!("fbbootlogd: No display present yet: {err}");
return;
}
};
+ let new_display_handle = match V2GraphicsHandle::from_file(display) {
+ Ok(handle) => handle,
+ Err(err) => {
+ eprintln!("fbbootlogd: failed to create graphics handle (DRM ioctl unsupported): {err}");
+ return;
+ }
+ };
+
match V2DisplayMap::new(new_display_handle) {
Ok(display_map) => self.display_map = Some(display_map),
Err(err) => {
diff --git a/drivers/graphics/fbcond/src/display.rs b/drivers/graphics/fbcond/src/display.rs
index eb09b97e..e8543583 100644
--- a/drivers/graphics/fbcond/src/display.rs
+++ b/drivers/graphics/fbcond/src/display.rs
@@ -31,7 +31,13 @@ impl Display {
return;
}
};
- let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
+ let new_display_handle = match V2GraphicsHandle::from_file(display_file) {
+ Ok(handle) => handle,
+ Err(err) => {
+ log::error!("fbcond: failed to create graphics handle (DRM ioctl unsupported): {err}");
+ return;
+ }
+ };
log::debug!("fbcond: Opened new display");
@@ -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,
@@ -0,0 +1,16 @@
--- a/drivers/pcid/src/main.rs
+++ b/drivers/pcid/src/main.rs
@@ -262,11 +262,10 @@
let access_fd = socket
.create_this_scheme_fd(0, access_id, syscall::O_RDWR, 0)
.expect("failed to issue this resource");
- let access_bytes = access_fd.to_ne_bytes();
if let Err(err) = register_pci.call_wo(
- &access_bytes,
- syscall::CallFlags::WRITE | syscall::CallFlags::FD,
&[],
+ syscall::CallFlags::WRITE | syscall::CallFlags::FD,
+ &[access_fd as u64],
) {
warn!("pcid: failed to send pci_fd to acpid (error: {}). Running without ACPI integration.", err);
}
@@ -0,0 +1,26 @@
diff --git a/drivers/rtcd/src/main.rs b/drivers/rtcd/src/main.rs
index 3e913780..41383ca3 100644
--- a/drivers/rtcd/src/main.rs
+++ b/drivers/rtcd/src/main.rs
@@ -1,4 +1,5 @@
use anyhow::{Context, Result};
+use std::io::Write;
// TODO: Do not use target architecture to distinguish these.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -17,7 +18,14 @@ fn main() -> Result<()> {
let time_s = self::x86::get_time();
let time_ns = u128::from(time_s) * 1_000_000_000;
- std::fs::write("/scheme/sys/update_time_offset", &time_ns.to_ne_bytes())
+ // Open the sys scheme resource directly without O_CREAT, since update_time_offset
+ // is a pre-existing kernel resource. Using std::fs::write (which sets O_CREAT) can
+ // trigger EEXIST from the kernel's named pipe subsystem.
+ let mut file = std::fs::OpenOptions::new()
+ .write(true)
+ .open("/scheme/sys/update_time_offset")
+ .context("failed to open time offset")?;
+ file.write_all(&time_ns.to_ne_bytes())
.context("failed to write to time offset")?;
}
// TODO: aarch64 is currently handled in the kernel
@@ -0,0 +1,51 @@
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 @@
#[derive(Clone)]
pub struct AmlPhysMemHandler {
page_cache: Arc<Mutex<AmlPageCache>>,
- pci_fd: Arc<Option<libredox::Fd>>,
+ pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>,
aml_mutexes: Arc<Mutex<FxHashMap<u32, Arc<AmlMutex>>>>,
next_mutex_handle: Arc<AtomicU32>,
}
@@ -163,16 +163,10 @@
/// Read from a physical address.
/// Generic parameter must be u8, u16, u32 or u64.
impl AmlPhysMemHandler {
- pub fn new(pci_fd_opt: Option<&libredox::Fd>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
- let pci_fd = if let Some(pci_fd) = pci_fd_opt {
- Some(libredox::Fd::new(pci_fd.raw()))
- } else {
- log::error!("pci_fd is not registered");
- None
- };
+ pub fn new(pci_fd: Arc<parking_lot::RwLock<Option<libredox::Fd>>>, page_cache: Arc<Mutex<AmlPageCache>>) -> Self {
Self {
page_cache,
- pci_fd: Arc::new(pci_fd),
+ pci_fd,
aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())),
next_mutex_handle: Arc::new(AtomicU32::new(1)),
}
@@ -218,7 +212,8 @@
fn read_pci(&self, addr: PciAddress, off: u16, value: &mut [u8]) {
let metadata = Self::pci_call_metadata(1, addr, off);
- match &*self.pci_fd {
+ let guard = self.pci_fd.read();
+ match guard.as_ref() {
Some(pci_fd) => match pci_fd.call_ro(value, syscall::CallFlags::empty(), &metadata) {
Ok(_) => {}
Err(err) => {
@@ -236,7 +231,8 @@
fn write_pci(&self, addr: PciAddress, off: u16, value: &[u8]) {
let metadata = Self::pci_call_metadata(2, addr, off);
- match &*self.pci_fd {
+ let guard = self.pci_fd.read();
+ match guard.as_ref() {
Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) {
Ok(_) => {}
Err(err) => {
@@ -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 {
@@ -0,0 +1,93 @@
--- /tmp/p18-baseline-dm-main.rs 2026-05-17 00:24:36.755915716 +0300
+++ local/recipes/system/driver-manager/source/src/main.rs 2026-05-17 06:05:41.569135039 +0300
@@ -3,6 +3,7 @@
mod hotplug;
mod scheme;
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
@@ -19,6 +20,19 @@
use config::DriverConfig;
use scheme::{DriverManagerScheme, notify_bind};
+/// Global flag set by SIGTERM handler to request graceful shutdown.
+static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
+
+extern "C" fn sigterm_handler(_sig: i32) {
+ SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
+}
+
+fn install_sigterm_handler() {
+ unsafe {
+ libc::signal(libc::SIGTERM, sigterm_handler as usize);
+ }
+}
+
struct StderrLogger;
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
@@ -306,6 +320,9 @@
log::set_logger(&StderrLogger).ok();
log::set_max_level(log::LevelFilter::Info);
+ // Install SIGTERM handler for graceful shutdown
+ install_sigterm_handler();
+
let args: Vec<String> = env::args().collect();
let initfs = args.iter().any(|a| a == "--initfs");
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
@@ -406,6 +423,15 @@
let max_retries = 30u32;
for retry in 1..=max_retries {
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
+ log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
+ graceful_shutdown();
+ process::exit(0);
+ }
+
+ // Check for crashed drivers during retry loop
+ reap_all_drivers(&driver_configs);
+
thread::sleep(Duration::from_millis(500));
let retry_events = match manager.lock() {
@@ -460,6 +486,35 @@
fn idle_forever() -> ! {
log::info!("driver-manager: entering persistent idle loop");
loop {
- thread::sleep(Duration::from_secs(3600));
+ thread::sleep(Duration::from_secs(5));
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
+ log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
+ graceful_shutdown();
+ process::exit(0);
+ }
+ // Periodically check for exited child drivers
+ reap_all_drivers(&[]);
}
}
+
+/// Poll all driver configs for exited children and log the results.
+fn reap_all_drivers(driver_configs: &[DriverConfig]) {
+ for dc in driver_configs {
+ let exited = dc.reap_exited_children();
+ for (device_key, driver_name, code) in &exited {
+ log::warn!(
+ "reaped crashed driver: {} for device {} (exit {})",
+ driver_name,
+ device_key,
+ code
+ );
+ }
+ }
+}
+
+fn graceful_shutdown() {
+ // The DeviceManager and spawned children are managed by DriverConfig instances
+ // which track their child processes. On shutdown, we log and exit cleanly.
+ // Child processes will be orphaned but the kernel reaps them.
+ log::info!("driver-manager: clean shutdown complete");
+}
@@ -0,0 +1,73 @@
--- /tmp/p18-baseline-dm-main.rs 2026-05-17 00:24:36.755915716 +0300
+++ local/recipes/system/driver-manager/source/src/main.rs 2026-05-17 00:26:51.974306098 +0300
@@ -3,6 +3,7 @@
mod hotplug;
mod scheme;
+use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
@@ -19,6 +20,19 @@
use config::DriverConfig;
use scheme::{DriverManagerScheme, notify_bind};
+/// Global flag set by SIGTERM handler to request graceful shutdown.
+static SHUTDOWN_REQUESTED: AtomicBool = AtomicBool::new(false);
+
+extern "C" fn sigterm_handler(_sig: i32) {
+ SHUTDOWN_REQUESTED.store(true, Ordering::SeqCst);
+}
+
+fn install_sigterm_handler() {
+ unsafe {
+ libc::signal(libc::SIGTERM, sigterm_handler as usize);
+ }
+}
+
struct StderrLogger;
const BOOT_TIMELINE_PATH: &str = "/tmp/redbear-boot-timeline.json";
@@ -306,6 +320,9 @@
log::set_logger(&StderrLogger).ok();
log::set_max_level(log::LevelFilter::Info);
+ // Install SIGTERM handler for graceful shutdown
+ install_sigterm_handler();
+
let args: Vec<String> = env::args().collect();
let initfs = args.iter().any(|a| a == "--initfs");
let hotplug_mode = args.iter().any(|a| a == "--hotplug");
@@ -406,6 +423,12 @@
let max_retries = 30u32;
for retry in 1..=max_retries {
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
+ log::info!("driver-manager: SIGTERM received during deferred retry, shutting down");
+ graceful_shutdown();
+ process::exit(0);
+ }
+
thread::sleep(Duration::from_millis(500));
let retry_events = match manager.lock() {
@@ -460,6 +483,18 @@
fn idle_forever() -> ! {
log::info!("driver-manager: entering persistent idle loop");
loop {
- thread::sleep(Duration::from_secs(3600));
+ thread::sleep(Duration::from_secs(1));
+ if SHUTDOWN_REQUESTED.load(Ordering::SeqCst) {
+ log::info!("driver-manager: SIGTERM received, performing graceful shutdown");
+ graceful_shutdown();
+ process::exit(0);
+ }
}
}
+
+fn graceful_shutdown() {
+ // The DeviceManager and spawned children are managed by DriverConfig instances
+ // which track their child processes. On shutdown, we log and exit cleanly.
+ // Child processes will be orphaned but the kernel reaps them.
+ log::info!("driver-manager: clean shutdown complete");
+}
@@ -0,0 +1,27 @@
--- a/src/scheme/irq.rs
+++ b/src/scheme/irq.rs
@@ -19,6 +19,9 @@
use super::{CallerCtx, HandleMap, OpenResult, SchemeExt, StrOrBytes};
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+use crate::arch::device::{ioapic, local_apic::ApicId};
+
+#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
use crate::arch::interrupt::{available_irqs_iter, irq::acknowledge, is_reserved, set_reserved};
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
use crate::dtb::irqchip::{acknowledge, available_irqs_iter, is_reserved, set_reserved, IRQ_CHIP};
@@ -480,6 +483,14 @@
if !cpus.contains(&(cpu_id as u8)) {
return Err(Error::new(EINVAL));
}
+ // Reprogram the IOAPIC redirection entry for x86 targets.
+ // Non-IOAPIC IRQs (e.g. MSI) will return false -> EIO.
+ #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
+ {
+ if !unsafe { ioapic::set_affinity(_handle_irq, ApicId::new(cpu_id)) } {
+ return Err(Error::new(EIO));
+ }
+ }
mask.store(cpu_id as usize, Ordering::Release);
Ok(size_of::<u32>())
}
+335
View File
@@ -0,0 +1,335 @@
--- /dev/null
+++ b/src/sync/mcs.rs
@@ -0,0 +1,96 @@
+//! MCS (Mellor-Crummey Scott) fair spinlock.
+//!
+//! Each waiter spins on its own local `locked` flag instead of a shared lock
+//! word, eliminating cache-line bouncing under contention. FIFO ordering
+//! guarantees fairness. O(1) cache-line transfers on unlock.
+
+use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
+use core::{hint, ptr};
+
+use crate::percpu::PercpuBlock;
+
+/// A node in the MCS lock queue.
+pub struct McsNode {
+ pub next: AtomicPtr<McsNode>,
+ pub locked: AtomicBool,
+}
+
+impl McsNode {
+ pub const fn new() -> Self {
+ Self {
+ next: AtomicPtr::new(ptr::null_mut()),
+ locked: AtomicBool::new(false),
+ }
+ }
+}
+
+/// Raw MCS spinlock primitive.
+pub struct McsRawLock {
+ tail: AtomicPtr<McsNode>,
+}
+
+impl McsRawLock {
+ pub const fn new() -> Self {
+ Self {
+ tail: AtomicPtr::new(ptr::null_mut()),
+ }
+ }
+
+ #[inline]
+ pub fn acquire(&self, node: &McsNode) -> bool {
+ node.next.store(ptr::null_mut(), Ordering::Relaxed);
+ node.locked.store(true, Ordering::Relaxed);
+ let prev = self.tail.swap((node as *const McsNode).cast_mut(), Ordering::AcqRel);
+ if prev.is_null() {
+ return false;
+ }
+ unsafe {
+ (*prev).next.store((node as *const McsNode).cast_mut(), Ordering::Release);
+ }
+ let percpu = PercpuBlock::current();
+ while node.locked.load(Ordering::Acquire) {
+ percpu.maybe_handle_tlb_shootdown();
+ hint::spin_loop();
+ }
+ true
+ }
+
+ #[inline]
+ pub fn release(&self, node: &McsNode) {
+ let next = node.next.load(Ordering::Acquire);
+ if next.is_null() {
+ if self
+ .tail
+ .compare_exchange(
+ (node as *const McsNode).cast_mut(),
+ ptr::null_mut(),
+ Ordering::AcqRel,
+ Ordering::Acquire,
+ )
+ .is_ok()
+ {
+ return;
+ }
+ while node.next.load(Ordering::Acquire).is_null() {
+ hint::spin_loop();
+ }
+ }
+ unsafe {
+ (*node.next.load(Ordering::Acquire)).locked.store(false, Ordering::Release);
+ }
+ }
+
+ #[inline]
+ pub fn try_acquire(&self, node: &McsNode) -> bool {
+ node.next.store(ptr::null_mut(), Ordering::Relaxed);
+ node.locked.store(true, Ordering::Relaxed);
+ self.tail
+ .compare_exchange(
+ ptr::null_mut(),
+ (node as *const McsNode).cast_mut(),
+ Ordering::AcqRel,
+ Ordering::Acquire,
+ )
+ .is_ok()
+ }
+}
--- a/src/sync/mod.rs
+++ b/src/sync/mod.rs
@@ -1,5 +1,6 @@
pub use self::{ordered::*, wait_condition::WaitCondition, wait_queue::WaitQueue};
+pub mod mcs;
pub mod ordered;
pub mod wait_condition;
pub mod wait_queue;
--- a/src/sync/ordered.rs
+++ b/src/sync/ordered.rs
@@ -52,7 +52,9 @@
//! *g1 = 12;
//! ```
use alloc::sync::Arc;
+use core::cell::UnsafeCell;
use core::marker::PhantomData;
+use core::ptr;
use crate::percpu::PercpuBlock;
@@ -732,3 +734,143 @@
/// This function can only be called if no lock is held by the calling thread/task
#[inline]
pub fn check_no_locks(_: LockToken<'_, L0>) {}
+
+// ---------------------------------------------------------------------------
+// MCS-based fair mutex (McsMutex)
+// ---------------------------------------------------------------------------
+
+/// A mutual exclusion lock using the MCS fair spinlock algorithm.
+///
+/// Unlike `Mutex<L, T>` which uses a simple spinlock (no fairness under
+/// contention), `McsMutex` uses Mellor-Crummey Scott queue-based spinning:
+///
+/// - Each waiter spins on its **own** local flag — no shared cache-line bouncing.
+/// - FIFO ordering prevents starvation.
+/// - O(1) cache-line transfers on unlock.
+///
+/// The MCS node is stored in [`crate::percpu::PercpuBlock::mcs_sched_node`], so
+/// this type is suitable for scheduler-internal locks where the holder is always
+/// the current CPU.
+pub struct McsMutex<L: Level, T> {
+ raw: crate::sync::mcs::McsRawLock,
+ data: UnsafeCell<T>,
+ _phantom: PhantomData<L>,
+}
+
+unsafe impl<L: Level, T: Send> Sync for McsMutex<L, T> {}
+unsafe impl<L: Level, T: Send> Send for McsMutex<L, T> {}
+
+impl<L: Level, T> McsMutex<L, T> {
+ pub const fn new(val: T) -> Self {
+ Self {
+ raw: crate::sync::mcs::McsRawLock::new(),
+ data: UnsafeCell::new(val),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<L: Level, T> McsMutex<L, T> {
+ pub fn lock<'a, LP: Lower<L> + 'a>(
+ &'a self,
+ lock_token: LockToken<'a, LP>,
+ ) -> McsMutexGuard<'a, L, T> {
+ let percpu = PercpuBlock::current();
+ let contended = self.raw.acquire(&percpu.mcs_sched_node);
+ if contended {
+ percpu
+ .mcs_contention_count
+ .set(percpu.mcs_contention_count.get() + 1);
+ }
+ McsMutexGuard {
+ lock: self,
+ lock_token: LockToken::downgraded(lock_token),
+ }
+ }
+
+ pub fn try_lock<'a, LP: Lower<L> + 'a>(
+ &'a self,
+ lock_token: LockToken<'a, LP>,
+ ) -> Option<McsMutexGuard<'a, L, T>> {
+ let percpu = PercpuBlock::current();
+ if self.raw.try_acquire(&percpu.mcs_sched_node) {
+ Some(McsMutexGuard {
+ lock: self,
+ lock_token: LockToken::downgraded(lock_token),
+ })
+ } else {
+ None
+ }
+ }
+}
+
+pub struct McsMutexGuard<'a, L: Level, T: 'a> {
+ lock: &'a McsMutex<L, T>,
+ lock_token: LockToken<'a, L>,
+}
+
+impl<'a, L: Level, T: 'a> McsMutexGuard<'a, L, T> {
+ pub fn token_split(&mut self) -> (&mut T, LockToken<'_, L>) {
+ unsafe { (&mut *self.lock.data.get(), self.lock_token.token()) }
+ }
+
+ pub fn into_split(self) -> (McsRawGuard<'a, L, T>, LockToken<'a, L>) {
+ let lock_ref = self.lock;
+ let token = unsafe { core::ptr::read(&self.lock_token) };
+ core::mem::forget(self);
+ (McsRawGuard { lock: lock_ref }, token)
+ }
+
+ pub fn from_split(raw: McsRawGuard<'a, L, T>, token: LockToken<'a, L>) -> Self {
+ let lock_ref = raw.lock;
+ core::mem::forget(raw);
+ Self {
+ lock: lock_ref,
+ lock_token: token,
+ }
+ }
+}
+
+impl<L: Level, T> core::ops::Deref for McsMutexGuard<'_, L, T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target {
+ unsafe { &*self.lock.data.get() }
+ }
+}
+
+impl<L: Level, T> core::ops::DerefMut for McsMutexGuard<'_, L, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { &mut *self.lock.data.get() }
+ }
+}
+
+impl<L: Level, T> Drop for McsMutexGuard<'_, L, T> {
+ fn drop(&mut self) {
+ let percpu = PercpuBlock::current();
+ self.lock.raw.release(&percpu.mcs_sched_node);
+ }
+}
+
+pub struct McsRawGuard<'a, L: Level, T: 'a> {
+ lock: &'a McsMutex<L, T>,
+}
+
+impl<L: Level, T> core::ops::Deref for McsRawGuard<'_, L, T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target {
+ unsafe { &*self.lock.data.get() }
+ }
+}
+
+impl<L: Level, T> core::ops::DerefMut for McsRawGuard<'_, L, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ unsafe { &mut *self.lock.data.get() }
+ }
+}
+
+impl<L: Level, T> Drop for McsRawGuard<'_, L, T> {
+ fn drop(&mut self) {
+ let percpu = PercpuBlock::current();
+ self.lock.raw.release(&percpu.mcs_sched_node);
+ }
+}
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -17,7 +17,7 @@
cpu_set::{LogicalCpuId, MAX_CPU_COUNT},
cpu_stats::{CpuStats, CpuStatsData},
ptrace::Session,
- sync::CleanLockToken,
+ sync::{mcs::McsNode, CleanLockToken},
syscall::debug::SyscallDebugInfo,
};
@@ -35,6 +35,12 @@
pub balance: Cell<[usize; 40]>,
pub last_queue: Cell<usize>,
+ /// Per-CPU MCS node for the scheduler run-queue lock (RUN_CONTEXTS).
+ pub mcs_sched_node: McsNode,
+
+ /// Counts how many times the scheduler MCS lock acquisition was contended.
+ pub mcs_contention_count: Cell<u64>,
+
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
// first to avoid cache invalidation.
pub profiling: Option<&'static crate::profiling::RingBuffer>,
@@ -215,6 +221,8 @@
wants_tlb_shootdown: AtomicBool::new(false),
balance: Cell::new([0; 40]),
last_queue: Cell::new(39),
+ mcs_sched_node: McsNode::new(),
+ mcs_contention_count: Cell::new(0),
ptrace_flags: Cell::new(PtraceFlags::empty()),
ptrace_session: RefCell::new(None),
inside_syscall: Cell::new(false),
--- a/src/context/mod.rs
+++ b/src/context/mod.rs
@@ -14,8 +14,8 @@
memory::{RmmA, RmmArch, TableKind},
percpu::PercpuBlock,
sync::{
- ArcRwLockWriteGuard, CleanLockToken, LockToken, Mutex, MutexGuard, RwLock, RwLockReadGuard,
- RwLockWriteGuard, L0, L1, L2, L4,
+ ArcRwLockWriteGuard, CleanLockToken, LockToken, McsMutex, McsMutexGuard, Mutex,
+ MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, L0, L1, L2, L4,
},
syscall::error::Result,
};
@@ -74,10 +74,12 @@
// the context file descriptors.
static CONTEXTS: RwLock<L2, BTreeSet<ContextRef>> = RwLock::new(BTreeSet::new());
-// Actual context store for the scheduler
-static RUN_CONTEXTS: Mutex<L1, RunContextData> = Mutex::new(RunContextData::new());
+// Actual context store for the scheduler — uses MCS fair spinlock to
+// eliminate cache-line bouncing under multi-CPU contention.
+static RUN_CONTEXTS: McsMutex<L1, RunContextData> = McsMutex::new(RunContextData::new());
-// Context that has been pushed out from RUN_CONTEXTS after being idle
+// Context that has been pushed out from RUN_CONTEXTS after being idle.
+// Uses regular Mutex (lower contention; wakeup_contexts uses try_lock).
static IDLE_CONTEXTS: Mutex<L2, VecDeque<WeakContextRef>> = Mutex::new(VecDeque::new());
pub struct RunContextData {
@@ -113,7 +115,7 @@
IDLE_CONTEXTS.try_lock(token)
}
-pub fn run_contexts(token: LockToken<'_, L0>) -> MutexGuard<'_, L1, RunContextData> {
+pub fn run_contexts(token: LockToken<'_, L0>) -> McsMutexGuard<'_, L1, RunContextData> {
RUN_CONTEXTS.lock(token)
}
@@ -0,0 +1,195 @@
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -5,9 +5,13 @@
use core::{
cell::{Cell, RefCell},
hint,
- sync::atomic::{AtomicBool, AtomicPtr, Ordering},
+ sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, AtomicU64, Ordering},
};
+/// Maximum number of pages to flush individually using INVLPG before falling
+/// back to a full TLB flush (CR3 reload).
+const TLB_RANGE_THRESHOLD: u32 = 32;
+
use rmm::Arch;
use syscall::PtraceFlags;
@@ -41,6 +45,23 @@
/// Counts how many times the scheduler MCS lock acquisition was contended.
pub mcs_contention_count: Cell<u64>,
+ /// TLB shootdown range: start virtual address (page-aligned).
+ /// Set to 0 for a full flush. Only valid when `wants_tlb_shootdown` is true.
+ pub tlb_flush_start: AtomicU64,
+ /// TLB shootdown range: number of pages to invalidate.
+ pub tlb_flush_count: AtomicU32,
+
+ /// Priority inheritance donation. When another CPU is blocked waiting on a
+ /// lock this CPU holds, the blocked CPU may donate its priority here.
+ /// `u32::MAX` means no donation; otherwise it's a priority level (0-39).
+ pub pi_donated_prio: AtomicU32,
+
+ /// Cached priority of the currently-running context on this CPU.
+ /// Set by the scheduler when selecting a new context. Read by the MCS
+ /// lock during priority donation — avoids acquiring the context RwLock
+ /// from the spin loop. Default 39 (lowest priority).
+ pub current_prio: Cell<usize>,
+
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
// first to avoid cache invalidation.
pub profiling: Option<&'static crate::profiling::RingBuffer>,
@@ -64,6 +85,15 @@
ALL_PERCPU_BLOCKS[id.get() as usize].store(block, Ordering::Release)
}
+/// Get a reference to another CPU's PercpuBlock by logical CPU ID.
+pub fn get_for_cpu(id: LogicalCpuId) -> Option<&'static PercpuBlock> {
+ unsafe {
+ ALL_PERCPU_BLOCKS[id.get() as usize]
+ .load(Ordering::Acquire)
+ .as_ref()
+ }
+}
+
pub fn get_all_stats() -> Vec<(LogicalCpuId, CpuStatsData)> {
let mut res = ALL_PERCPU_BLOCKS
.iter()
@@ -108,6 +138,9 @@
core::hint::spin_loop();
}
}
+ // Full flush — clear range info
+ percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
+ percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
} else {
@@ -138,20 +171,114 @@
hint::spin_loop();
}
}
+ // Full flush — clear range info
+ percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
+ percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
}
// Single broadcast IPI to all other CPUs using destination shorthand
crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
}
}
+
+/// Range-based TLB shootdown IPI. Only invalidates the specified virtual address
+/// range using INVLPG per page for ranges up to TLB_RANGE_THRESHOLD pages.
+/// Falls back to full flush for larger ranges.
+pub fn shootdown_tlb_ipi_range(target: Option<LogicalCpuId>, start: usize, count: usize) {
+ if cfg!(not(feature = "multi_core")) {
+ return;
+ }
+
+ let start_aligned = start as u64 & !0xFFF;
+ let count_u32 = count as u32;
+ let use_range = count_u32 > 0 && count_u32 <= TLB_RANGE_THRESHOLD;
+
+ let set_range = |percpublock: &PercpuBlock| {
+ if use_range {
+ percpublock.tlb_flush_start.store(start_aligned, Ordering::Release);
+ percpublock.tlb_flush_count.store(count_u32, Ordering::Release);
+ } else {
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
+ }
+ };
+
+ if let Some(target) = target {
+ let my_percpublock = PercpuBlock::current();
+ assert_ne!(target, my_percpublock.cpu_id);
+
+ let Some(percpublock) = (unsafe {
+ ALL_PERCPU_BLOCKS[target.get() as usize]
+ .load(Ordering::Acquire)
+ .as_ref()
+ }) else {
+ return;
+ };
+ #[expect(clippy::bool_comparison)]
+ while percpublock.wants_tlb_shootdown.swap(true, Ordering::Release) == true {
+ while percpublock.wants_tlb_shootdown.load(Ordering::Relaxed) == true {
+ my_percpublock.maybe_handle_tlb_shootdown();
+ hint::spin_loop();
+ }
+ }
+ set_range(percpublock);
+ crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
+ } else {
+ let my_percpublock = PercpuBlock::current();
+ for id in 0..crate::cpu_count() {
+ let target_id = LogicalCpuId::new(id);
+ if target_id == my_percpublock.cpu_id {
+ continue;
+ }
+ let Some(percpublock) = (unsafe {
+ ALL_PERCPU_BLOCKS[id as usize]
+ .load(Ordering::Acquire)
+ .as_ref()
+ }) else {
+ continue;
+ };
+ #[expect(clippy::bool_comparison)]
+ while percpublock.wants_tlb_shootdown.swap(true, Ordering::Release) == true {
+ while percpublock.wants_tlb_shootdown.load(Ordering::Relaxed) == true {
+ my_percpublock.maybe_handle_tlb_shootdown();
+ hint::spin_loop();
+ }
+ }
+ set_range(percpublock);
+ }
+ crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
+ }
+}
impl PercpuBlock {
+ /// Return the effective scheduling priority, accounting for priority inheritance.
+ /// Lower number = higher priority (0-39 range).
+ pub fn effective_prio(&self, context_prio: usize) -> usize {
+ let donated = self.pi_donated_prio.load(Ordering::Relaxed);
+ if donated < context_prio as u32 {
+ donated as usize
+ } else {
+ context_prio
+ }
+ }
+
pub fn maybe_handle_tlb_shootdown(&self) {
#[expect(clippy::bool_comparison)]
if self.wants_tlb_shootdown.swap(false, Ordering::Relaxed) == false {
return;
}
- // TODO: Finer-grained flush
- crate::memory::RmmA::invalidate_all();
+ let start = self.tlb_flush_start.load(Ordering::Acquire);
+ let count = self.tlb_flush_count.load(Ordering::Acquire);
+
+ if start != 0 && count > 0 && count <= TLB_RANGE_THRESHOLD {
+ // Range-based flush using INVLPG per page — cheaper than full CR3 reload.
+ for i in 0..count {
+ let addr = start + (i as u64) * 4096;
+ crate::memory::RmmA::invalidate(rmm::VirtualAddress::new(addr as usize));
+ }
+ } else {
+ // Full TLB flush (CR3 reload) for large ranges or global shootdowns.
+ crate::memory::RmmA::invalidate_all();
+ }
if let Some(addrsp) = &*self.current_addrsp.borrow() {
addrsp.tlb_ack.fetch_add(1, Ordering::Release);
@@ -223,6 +350,10 @@
last_queue: Cell::new(39),
mcs_sched_node: McsNode::new(),
mcs_contention_count: Cell::new(0),
+ tlb_flush_start: AtomicU64::new(0),
+ tlb_flush_count: AtomicU32::new(0),
+ pi_donated_prio: AtomicU32::new(u32::MAX),
+ current_prio: Cell::new(39),
ptrace_flags: Cell::new(PtraceFlags::empty()),
ptrace_session: RefCell::new(None),
inside_syscall: Cell::new(false),
@@ -0,0 +1,153 @@
--- a/src/sync/mcs.rs
+++ b/src/sync/mcs.rs
@@ -4,7 +4,7 @@
//! word, eliminating cache-line bouncing under contention. FIFO ordering
//! guarantees fairness. O(1) cache-line transfers on unlock.
-use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
+use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
use core::{hint, ptr};
use crate::percpu::PercpuBlock;
@@ -27,12 +27,16 @@
/// Raw MCS spinlock primitive.
pub struct McsRawLock {
tail: AtomicPtr<McsNode>,
+ /// CPU ID of the current lock holder (for priority inheritance).
+ /// `u32::MAX` means no holder.
+ holder_cpu: AtomicU32,
}
impl McsRawLock {
pub const fn new() -> Self {
Self {
tail: AtomicPtr::new(ptr::null_mut()),
+ holder_cpu: AtomicU32::new(u32::MAX),
}
}
@@ -42,21 +46,37 @@
node.locked.store(true, Ordering::Relaxed);
let prev = self.tail.swap((node as *const McsNode).cast_mut(), Ordering::AcqRel);
if prev.is_null() {
+ // Uncontended — record ourselves as holder
+ let cpu_id = PercpuBlock::current().cpu_id.get();
+ self.holder_cpu.store(cpu_id, Ordering::Release);
return false;
}
unsafe {
(*prev).next.store((node as *const McsNode).cast_mut(), Ordering::Release);
}
let percpu = PercpuBlock::current();
+ let mut donated = false;
while node.locked.load(Ordering::Acquire) {
percpu.maybe_handle_tlb_shootdown();
+ // Donate priority to the lock holder once per acquisition
+ if !donated {
+ self.maybe_donate_priority(percpu);
+ donated = true;
+ }
hint::spin_loop();
}
+ // We now hold the lock
+ self.holder_cpu.store(percpu.cpu_id.get(), Ordering::Release);
true
}
#[inline]
pub fn release(&self, node: &McsNode) {
+ // Clear priority inheritance donation — we no longer hold the lock
+ PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
+ // Clear holder CPU
+ self.holder_cpu.store(u32::MAX, Ordering::Release);
+
let next = node.next.load(Ordering::Acquire);
if next.is_null() {
if self
@@ -84,13 +104,43 @@
pub fn try_acquire(&self, node: &McsNode) -> bool {
node.next.store(ptr::null_mut(), Ordering::Relaxed);
node.locked.store(true, Ordering::Relaxed);
- self.tail
+ let ok = self
+ .tail
.compare_exchange(
ptr::null_mut(),
(node as *const McsNode).cast_mut(),
Ordering::AcqRel,
Ordering::Acquire,
)
- .is_ok()
+ .is_ok();
+ if ok {
+ let cpu_id = PercpuBlock::current().cpu_id.get();
+ self.holder_cpu.store(cpu_id, Ordering::Release);
+ }
+ ok
+ }
+
+ /// Donate current CPU's context priority to the lock holder's CPU.
+ /// Reads priority from PercpuBlock::current_prio (cached by the scheduler)
+ /// to avoid acquiring any lock in the MCS spin loop.
+ fn maybe_donate_priority(&self, my_percpu: &PercpuBlock) {
+ let holder_cpu_id = self.holder_cpu.load(Ordering::Relaxed);
+ if holder_cpu_id == u32::MAX {
+ return;
+ }
+ // Read our own priority from the per-CPU cache (set by scheduler,
+ // no lock required).
+ let my_prio = my_percpu.current_prio.get();
+ // Look up holder's PercpuBlock
+ let holder_percpu = crate::percpu::get_for_cpu(
+ crate::cpu_set::LogicalCpuId::new(holder_cpu_id),
+ );
+ if let Some(holder) = holder_percpu {
+ let current_donated = holder.pi_donated_prio.load(Ordering::Relaxed);
+ // Donate if our priority is higher (lower number)
+ if (my_prio as u32) < current_donated {
+ holder.pi_donated_prio.store(my_prio as u32, Ordering::Relaxed);
+ }
+ }
}
}
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -16,6 +16,7 @@
use core::{
cell::{Cell, RefCell},
mem,
+ sync::atomic::Ordering,
};
use syscall::PtraceFlags;
@@ -442,6 +443,8 @@
// Is this context runnable on this CPU?
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
if let UpdateResult::CanSwitch = sw {
+ // Cache the new context's priority for MCS lock priority donation.
+ percpu.current_prio.set(next_context_guard.prio);
next_context_guard_opt = Some(next_context_guard);
balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
break 'priority;
@@ -466,7 +469,10 @@
// Send the old process to the back of the line (if it is still runnable)
let prev_ctx = WeakContextRef(Arc::downgrade(&prev_context_lock));
if prev_context_guard.status.is_runnable() {
- let prio = prev_context_guard.prio;
+ let raw_prio = prev_context_guard.prio;
+ let prio = percpu.effective_prio(raw_prio);
+ // Clear PI donation — previous context is being re-queued
+ percpu.pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
contexts_list[prio].push_back(prev_ctx);
} else {
idle_contexts(token.token()).push_back(prev_ctx);
@@ -478,7 +484,8 @@
return Ok(Some(next_context_guard));
} else {
if !was_idle && !Arc::ptr_eq(&prev_context_lock, &idle_context) {
- // We switch into the idle context
+ // Switching to idle context — cache lowest priority
+ percpu.current_prio.set(39);
Ok(Some(unsafe { idle_context.write_arc() }))
} else {
// We found no other process to run.
@@ -0,0 +1,414 @@
diff --git a/src/main.rs b/src/main.rs
--- a/src/main.rs
+++ b/src/main.rs
@@ -70,6 +70,9 @@ mod log;
/// Memory management
mod memory;
+/// NUMA topology
+mod numa;
+
/// Panic
mod panic;
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
@@ -18,6 +18,29 @@
use super::{Madt, MadtEntry};
+use alloc::vec::Vec;
+
+/// Maximum number of APIC→CPU mappings we track for NUMA topology.
+const MAX_APIC_MAPPINGS: usize = 256;
+
+struct ApicMapping {
+ apic_id: u32,
+ cpu_id: LogicalCpuId,
+}
+
+const UNINIT_MAPPING: ApicMapping = ApicMapping { apic_id: u32::MAX, cpu_id: LogicalCpuId::new(0) };
+
+static mut APIC_MAPPINGS: [ApicMapping; MAX_APIC_MAPPINGS] = [UNINIT_MAPPING; MAX_APIC_MAPPINGS];
+static mut APIC_MAPPING_COUNT: usize = 0;
+
+unsafe fn record_apic_mapping(apic_id: u32, cpu_id: LogicalCpuId) {
+ let count = APIC_MAPPING_COUNT;
+ if count < MAX_APIC_MAPPINGS {
+ APIC_MAPPINGS[count] = ApicMapping { apic_id, cpu_id };
+ APIC_MAPPING_COUNT = count + 1;
+ }
+}
+
const AP_SPIN_LIMIT: u32 = 1_000_000;
const TRAMPOLINE: usize = 0x8000;
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
@@ -61,6 +82,10 @@ pub(super) fn init(madt: Madt) {
}
if cfg!(not(feature = "multi_core")) {
+ unsafe {
+ record_apic_mapping(me.get(), LogicalCpuId::new(0));
+ }
+ crate::numa::init_default();
return;
}
@@ -216,6 +241,17 @@ pub(super) fn init(madt: Madt) {
crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
+ // Record APIC→CPU mapping for NUMA topology.
+ unsafe {
+ record_apic_mapping(u32::from(ap_local_apic.id), cpu_id);
+ }
+ // Set NUMA node from SRAT data.
+ if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(u32::from(ap_local_apic.id)) {
+ percpu.numa_node.set(node);
+ }
+ }
+
RmmA::invalidate_all();
}
} else if let MadtEntry::LocalX2Apic(ap_x2apic) = madt_entry {
@@ -325,6 +361,18 @@ pub(super) fn init(madt: Madt) {
}
crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
+
+ // Record APIC→CPU mapping for NUMA topology.
+ unsafe {
+ record_apic_mapping(apic_id, cpu_id);
+ }
+ // Set NUMA node from SRAT data.
+ if let Some(percpu) = crate::percpu::get_for_cpu(cpu_id) {
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(apic_id) {
+ percpu.numa_node.set(node);
+ }
+ }
+
RmmA::invalidate_all();
}
} else if let MadtEntry::LocalApicNmi(nmi) = madt_entry {
@@ -342,6 +390,20 @@ pub(super) fn init(madt: Madt) {
}
}
+ // Initialize NUMA topology from APIC→CPU mappings and SRAT.
+ {
+ let mappings = unsafe { &APIC_MAPPINGS[..APIC_MAPPING_COUNT] };
+ let mappings_ref: Vec<(u32, LogicalCpuId)> = mappings
+ .iter()
+ .map(|m| (m.apic_id, m.cpu_id))
+ .collect();
+ crate::numa::init_from_srat(&mappings_ref);
+ }
+ // Set BSP's NUMA node from SRAT.
+ if let Some(node) = crate::acpi::srat::numa_node_for_apic(me.get()) {
+ crate::percpu::PercpuBlock::current().numa_node.set(node);
+ }
+
// Unmap trampoline
if let Some((_frame, _, flush)) = unsafe {
KernelMapper::lock_rw()
diff --git a/src/acpi/mod.rs b/src/acpi/mod.rs
--- a/src/acpi/mod.rs
+++ b/src/acpi/mod.rs
@@ -20,6 +20,8 @@ mod rxsdt;
pub mod sdt;
#[cfg(target_arch = "aarch64")]
mod spcr;
+pub mod slit;
+pub mod srat;
mod xsdt;
unsafe fn map_linearly(addr: PhysicalAddress, len: usize, mapper: &mut crate::memory::PageMapper) {
@@ -163,7 +165,14 @@ pub unsafe fn init(already_supplied_rsdp: Option<*const u8>) {
// TODO: Enumerate processors in userspace, and then provide an ACPI-independent interface
// to initialize enumerated processors to userspace?
+ // Parse SRAT BEFORE MADT so NUMA node mapping is available
+ // when APs are started and PercpuBlocks are created.
+ srat::init();
+
Madt::init();
+
+ // Parse SLIT after MADT for the NUMA distance matrix.
+ slit::init();
//TODO: support this on any arch
// SPCR must be initialized after MADT for interrupt controllers
#[cfg(target_arch = "aarch64")]
diff --git a/src/acpi/slit.rs b/src/acpi/slit.rs
--- /dev/null
+++ b/src/acpi/slit.rs
@@ -0,0 +1,45 @@
+//! SLIT (System Locality Information Table) parser.
+//!
+//! Parses the NUMA distance matrix for scheduler NUMA-aware work stealing.
+
+use super::sdt::Sdt;
+use crate::acpi::find_sdt;
+
+const MAX_NODES: usize = 8;
+
+static mut SLIT_MATRIX: [[u8; MAX_NODES]; MAX_NODES] = [[10u8; MAX_NODES]; MAX_NODES];
+static mut SLIT_NUM_NODES: usize = 0;
+static mut SLIT_AVAILABLE: bool = false;
+
+pub fn is_available() -> bool { unsafe { SLIT_AVAILABLE } }
+pub fn num_nodes() -> usize { unsafe { SLIT_NUM_NODES } }
+
+pub fn distance(from: u8, to: u8) -> u8 {
+ if !unsafe { SLIT_AVAILABLE } { return 10; }
+ let (from, to) = (from as usize, to as usize);
+ if from >= MAX_NODES || to >= MAX_NODES { return 10; }
+ unsafe { SLIT_MATRIX[from][to] }
+}
+
+pub fn same_socket(node1: u8, node2: u8) -> bool { distance(node1, node2) <= 20 }
+
+pub fn init() {
+ let sdt = match find_sdt("SLIT").as_slice() {
+ [] => return,
+ [x] => *x,
+ xs => { println!("SLIT: {} tables found, expected 1", xs.len()); return; }
+ };
+ if &sdt.signature != b"SLIT" { return; }
+ let data_addr = sdt.data_address();
+ let data_len = sdt.data_len();
+ if data_len < 8 { return; }
+ let num_nodes = unsafe { *(data_addr as *const u64) } as usize;
+ if num_nodes == 0 || num_nodes > MAX_NODES { println!("SLIT: {num_nodes} nodes (max {MAX_NODES}), ignoring"); return; }
+ let matrix_start = 8;
+ let matrix_size = num_nodes * num_nodes;
+ if data_len < matrix_start + matrix_size { println!("SLIT: matrix truncated ({data_len} < {})", matrix_start + matrix_size); return; }
+ let matrix = unsafe { &mut SLIT_MATRIX };
+ for i in 0..num_nodes { for j in 0..num_nodes { matrix[i][j] = unsafe { *((data_addr + matrix_start + i * num_nodes + j) as *const u8) }; } }
+ unsafe { SLIT_NUM_NODES = num_nodes; SLIT_AVAILABLE = true; }
+ debug!("SLIT: {} nodes, distance matrix loaded", num_nodes);
+}
diff --git a/src/acpi/srat.rs b/src/acpi/srat.rs
--- /dev/null
+++ b/src/acpi/srat.rs
@@ -0,0 +1,102 @@
+//! SRAT (System Resource Affinity Table) parser.
+//!
+//! Parses CPU-to-NUMA-node and memory-to-NUMA-node affinity information.
+//! Called before MADT init so that NUMA data is available during AP startup.
+
+use super::sdt::Sdt;
+use crate::acpi::find_sdt;
+
+const MAX_CPU_ENTRIES: usize = 256;
+const MAX_MEM_ENTRIES: usize = 64;
+
+#[derive(Clone, Copy)]
+struct SratCpuEntry { apic_id: u32, node: u8, enabled: bool }
+
+#[derive(Clone, Copy)]
+struct SratMemEntry { node: u8, base: u64, length: u64, enabled: bool }
+
+const CPU_NONE: SratCpuEntry = SratCpuEntry { apic_id: u32::MAX, node: 0, enabled: false };
+const MEM_NONE: SratMemEntry = SratMemEntry { node: 0, base: 0, length: 0, enabled: false };
+
+static mut SRAT_CPU_ENTRIES: [SratCpuEntry; MAX_CPU_ENTRIES] = [CPU_NONE; MAX_CPU_ENTRIES];
+static mut SRAT_MEM_ENTRIES: [SratMemEntry; MAX_MEM_ENTRIES] = [MEM_NONE; MAX_MEM_ENTRIES];
+static mut SRAT_CPU_COUNT: usize = 0;
+static mut SRAT_MEM_COUNT: usize = 0;
+static mut SRAT_AVAILABLE: bool = false;
+
+pub fn is_available() -> bool { unsafe { SRAT_AVAILABLE } }
+
+pub fn numa_node_for_apic(apic_id: u32) -> Option<u8> {
+ if !unsafe { SRAT_AVAILABLE } { return None; }
+ let count = unsafe { SRAT_CPU_COUNT };
+ let entries = unsafe { &SRAT_CPU_ENTRIES };
+ for i in 0..count {
+ if entries[i].apic_id == apic_id && entries[i].enabled { return Some(entries[i].node); }
+ }
+ None
+}
+
+pub fn numa_node_count() -> usize {
+ if !unsafe { SRAT_AVAILABLE } { return 1; }
+ let mut max_node: u8 = 0;
+ let count = unsafe { SRAT_CPU_COUNT };
+ let entries = unsafe { &SRAT_CPU_ENTRIES };
+ for i in 0..count { if entries[i].enabled && entries[i].node > max_node { max_node = entries[i].node; } }
+ (max_node as usize) + 1
+}
+
+#[repr(C, packed)]
+struct SratLocalApic { _proximity_lo: u8, apic_id: u8, flags: u32, _local_sapic_eid: u8, _proximity_hi: [u8; 3], _clock_domain: u32 }
+
+#[repr(C, packed)]
+struct SratMemoryAffinity { proximity_domain: u32, _reserved1: u16, base_address_lo: u32, base_address_hi: u32, length_lo: u32, length_hi: u32, _reserved2: u32, flags: u32, _reserved3: u64 }
+
+#[repr(C, packed)]
+struct SratLocalX2Apic { _reserved: u16, proximity_domain: u32, x2apic_id: u32, flags: u32, _clock_domain: u32, _reserved2: u32 }
+
+pub fn init() {
+ let sdt = match find_sdt("SRAT").as_slice() {
+ [] => return,
+ [x] => *x,
+ xs => { println!("SRAT: {} tables found, expected 1", xs.len()); return; }
+ };
+ if &sdt.signature != b"SRAT" { return; }
+ let data_addr = sdt.data_address();
+ let data_len = sdt.data_len();
+ if data_len < 12 { println!("SRAT: table too short ({data_len} bytes)"); return; }
+ let mut offset: usize = 12;
+ let cpu_entries = unsafe { &mut SRAT_CPU_ENTRIES };
+ let mem_entries = unsafe { &mut SRAT_MEM_ENTRIES };
+ let mut cpu_count: usize = 0;
+ let mut mem_count: usize = 0;
+ while offset + 2 <= data_len {
+ let entry_type = unsafe { *((data_addr + offset) as *const u8) };
+ let entry_len = unsafe { *((data_addr + offset + 1) as *const u8) } as usize;
+ if entry_len < 2 || offset + entry_len > data_len { break; }
+ let entry_data = data_addr + offset + 2;
+ match entry_type {
+ 0x0 if entry_len >= size_of::<SratLocalApic>() + 2 => {
+ let e = unsafe { &*(entry_data as *const SratLocalApic) };
+ let enabled = (e.flags & 1) == 1;
+ let node = (e._proximity_lo as u32) | ((e._proximity_hi[0] as u32) << 8) | ((e._proximity_hi[1] as u32) << 16) | ((e._proximity_hi[2] as u32) << 24);
+ if cpu_count < MAX_CPU_ENTRIES { cpu_entries[cpu_count] = SratCpuEntry { apic_id: e.apic_id as u32, node: node as u8, enabled }; cpu_count += 1; }
+ }
+ 0x1 if entry_len >= size_of::<SratMemoryAffinity>() + 2 => {
+ let e = unsafe { &*(entry_data as *const SratMemoryAffinity) };
+ let enabled = (e.flags & 1) == 1;
+ let base = (e.base_address_hi as u64) << 32 | e.base_address_lo as u64;
+ let length = (e.length_hi as u64) << 32 | e.length_lo as u64;
+ if mem_count < MAX_MEM_ENTRIES { mem_entries[mem_count] = SratMemEntry { node: e.proximity_domain as u8, base, length, enabled }; mem_count += 1; }
+ }
+ 0x2 if entry_len >= size_of::<SratLocalX2Apic>() + 2 => {
+ let e = unsafe { &*(entry_data as *const SratLocalX2Apic) };
+ let enabled = (e.flags & 1) == 1;
+ if cpu_count < MAX_CPU_ENTRIES { cpu_entries[cpu_count] = SratCpuEntry { apic_id: e.x2apic_id, node: e.proximity_domain as u8, enabled }; cpu_count += 1; }
+ }
+ _ => {}
+ }
+ offset += entry_len;
+ }
+ unsafe { SRAT_CPU_COUNT = cpu_count; SRAT_MEM_COUNT = mem_count; SRAT_AVAILABLE = true; }
+ debug!("SRAT: {} CPU entries, {} memory entries", cpu_count, mem_count);
+}
diff --git a/src/numa.rs b/src/numa.rs
--- a/src/numa.rs
+++ b/src/numa.rs
@@ -1,13 +1,15 @@
/// NUMA topology hints for the kernel scheduler.
-/// NUMA discovery (SRAT/SLIT parsing) is performed by a userspace daemon
-/// (numad) via /scheme/acpi/, then pushed to the kernel via scheme:numa.
-/// The kernel stores a lightweight copy for O(1) scheduling lookups.
+///
+/// NUMA discovery (SRAT/SLIT parsing) is performed during kernel ACPI init
+/// (`acpi::init()`). The kernel stores a lightweight copy for O(1) scheduling
+/// lookups. If no SRAT is found, `init_default()` creates a single-node topology.
+use crate::acpi::srat;
use crate::cpu_set::{LogicalCpuId, LogicalCpuSet};
use core::sync::atomic::{AtomicBool, Ordering};
const MAX_NUMA_NODES: usize = 8;
-#[derive(Clone, Debug)]
+#[derive(Debug)]
pub struct NumaHint {
pub node_id: u8,
pub cpus: LogicalCpuSet,
@@ -21,17 +23,12 @@
impl NumaTopology {
pub const fn new() -> Self {
const NONE: Option<NumaHint> = None;
- Self {
- nodes: [NONE; MAX_NUMA_NODES],
- initialized: AtomicBool::new(false),
- }
+ Self { nodes: [NONE; MAX_NUMA_NODES], initialized: AtomicBool::new(false) }
}
pub fn node_for_cpu(&self, cpu: LogicalCpuId) -> Option<u8> {
for node in self.nodes.iter().flatten() {
- if node.cpus.contains(cpu) {
- return Some(node.node_id);
- }
+ if node.cpus.contains(cpu) { return Some(node.node_id); }
}
None
}
@@ -43,20 +40,42 @@
static mut NUMA_TOPOLOGY: NumaTopology = NumaTopology::new();
-pub fn topology() -> &'static NumaTopology {
- unsafe { &NUMA_TOPOLOGY }
+pub fn topology() -> &'static NumaTopology { unsafe { &NUMA_TOPOLOGY } }
+
+/// Initialize NUMA topology from SRAT data parsed during ACPI init.
+pub fn init_from_srat(apic_ids: &[(u32, LogicalCpuId)]) {
+ let topo = topology();
+ if topo.initialized.swap(true, Ordering::AcqRel) { return; }
+ if !srat::is_available() { init_default_inner(); return; }
+ unsafe {
+ let topo_mut = &mut *core::ptr::addr_of_mut!(NUMA_TOPOLOGY);
+ for &(apic_id, cpu_id) in apic_ids {
+ if let Some(node) = srat::numa_node_for_apic(apic_id) {
+ let idx = node as usize;
+ if idx < MAX_NUMA_NODES {
+ topo_mut.nodes[idx].get_or_insert_with(|| NumaHint { node_id: node, cpus: LogicalCpuSet::empty() }).cpus.atomic_set(cpu_id);
+ }
+ }
+ }
+ if topo_mut.nodes.iter().all(|n| n.is_none()) {
+ topo_mut.nodes[0] = Some(NumaHint { node_id: 0, cpus: LogicalCpuSet::all() });
+ }
+ }
+ let node_count = topology().nodes.iter().filter(|n| n.is_some()).count();
+ debug!("NUMA: {node_count} node(s) from SRAT");
}
+/// Fallback: single-node topology.
pub fn init_default() {
let topo = topology();
- if topo.initialized.swap(true, Ordering::AcqRel) {
- return;
- }
+ if topo.initialized.swap(true, Ordering::AcqRel) { return; }
+ init_default_inner();
+}
+
+fn init_default_inner() {
unsafe {
let topo_mut = &mut *core::ptr::addr_of_mut!(NUMA_TOPOLOGY);
- topo_mut.nodes[0] = Some(NumaHint {
- node_id: 0,
- cpus: LogicalCpuSet::all(),
- });
+ topo_mut.nodes[0] = Some(NumaHint { node_id: 0, cpus: LogicalCpuSet::all() });
}
+ debug!("NUMA: single-node topology (no SRAT)");
}
diff --git a/src/percpu.rs b/src/percpu.rs
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -62,6 +62,10 @@ pub struct PercpuBlock {
/// from the spin loop. Default 39 (lowest priority).
pub current_prio: Cell<usize>,
+ /// NUMA proximity domain for this CPU. Set during ACPI init from SRAT.
+ /// `u8::MAX` means unknown (no SRAT or APIC ID not listed).
+ pub numa_node: Cell<u8>,
+
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
// first to avoid cache invalidation.
pub profiling: Option<&'static crate::profiling::RingBuffer>,
@@ -354,6 +358,7 @@ impl PercpuBlock {
tlb_flush_count: AtomicU32::new(0),
pi_donated_prio: AtomicU32::new(u32::MAX),
current_prio: Cell::new(39),
+ numa_node: Cell::new(u8::MAX),
ptrace_flags: Cell::new(PtraceFlags::empty()),
ptrace_session: RefCell::new(None),
inside_syscall: Cell::new(false),
@@ -0,0 +1,83 @@
--- a/src/acpi/madt/arch/x86.rs
+++ b/src/acpi/madt/arch/x86.rs
@@ -150,15 +150,16 @@
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
let stack_end = stack_start + (PAGE_SIZE << 4);
- let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
- if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
+ // Atomically allocate a CPU ID — fetch_add is SeqCst so that
+ // all later stores (PercpuBlock, NUMA node) are ordered after.
+ let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
+ if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
println!(
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
ap_local_apic.id
);
continue;
}
- let cpu_id = LogicalCpuId::new(next_cpu);
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
@@ -184,8 +185,10 @@
#[expect(clippy::fn_to_numeric_cast)]
ap_code.write(kstart_ap as u64);
- // TODO: Is this necessary (this fence)?
- core::arch::asm!("");
+ // Ensure all trampoline writes are visible to the AP before
+ // it starts executing. asm!("") is only a compiler barrier;
+ // fence(SeqCst) is a full hardware memory barrier.
+ core::sync::atomic::fence(Ordering::SeqCst);
};
AP_READY.store(false, Ordering::SeqCst);
@@ -241,8 +244,6 @@
continue;
}
- crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
-
// Record APIC→CPU mapping for NUMA topology.
unsafe {
record_apic_mapping(u32::from(ap_local_apic.id), cpu_id);
@@ -273,15 +274,16 @@
let stack_start = RmmA::phys_to_virt(alloc.base()).data();
let stack_end = stack_start + (PAGE_SIZE << 4);
- let next_cpu = crate::CPU_COUNT.load(Ordering::Relaxed);
- if next_cpu >= crate::cpu_set::MAX_CPU_COUNT {
+ // Atomically allocate a CPU ID — fetch_add is SeqCst so that
+ // all later stores (PercpuBlock, NUMA node) are ordered after.
+ let cpu_id = LogicalCpuId::new(crate::CPU_COUNT.fetch_add(1, Ordering::SeqCst));
+ if cpu_id.get() >= crate::cpu_set::MAX_CPU_COUNT {
println!(
"KERNEL AP: CPU {} exceeds logical CPU limit, skipping",
apic_id
);
continue;
}
- let cpu_id = LogicalCpuId::new(next_cpu);
let pcr_ptr = crate::arch::gdt::allocate_and_init_pcr(cpu_id, stack_end);
let idt_ptr = crate::arch::idt::allocate_and_init_idt(cpu_id);
@@ -304,7 +306,8 @@
ap_page_table.write(page_table_physaddr as u64);
#[expect(clippy::fn_to_numeric_cast)]
ap_code.write(kstart_ap as u64);
- core::arch::asm!("");
+ // Ensure all trampoline writes are visible to the AP.
+ core::sync::atomic::fence(Ordering::SeqCst);
}
AP_READY.store(false, Ordering::SeqCst);
@@ -362,8 +365,6 @@
continue;
}
- crate::CPU_COUNT.fetch_add(1, Ordering::Relaxed);
-
// Record APIC→CPU mapping for NUMA topology.
unsafe {
record_apic_mapping(apic_id, cpu_id);
@@ -0,0 +1,29 @@
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -142,9 +142,10 @@
core::hint::spin_loop();
}
}
- // Full flush — clear range info
- percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
- percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
+ // Full flush — clear range info (Release ordering ensures the flag
+ // swap and these stores are visible to the handler before the IPI).
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
} else {
@@ -175,9 +176,9 @@
hint::spin_loop();
}
}
- // Full flush — clear range info
- percpublock.tlb_flush_start.store(0, Ordering::Relaxed);
- percpublock.tlb_flush_count.store(0, Ordering::Relaxed);
+ // Full flush — clear range info (Release ordering)
+ percpublock.tlb_flush_start.store(0, Ordering::Release);
+ percpublock.tlb_flush_count.store(0, Ordering::Release);
}
// Single broadcast IPI to all other CPUs using destination shorthand
crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
@@ -0,0 +1,20 @@
--- a/src/sync/mcs.rs
+++ b/src/sync/mcs.rs
@@ -73,7 +73,7 @@
#[inline]
pub fn release(&self, node: &McsNode) {
// Clear priority inheritance donation — we no longer hold the lock
- PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Relaxed);
+ PercpuBlock::current().pi_donated_prio.store(u32::MAX, Ordering::Release);
// Clear holder CPU
self.holder_cpu.store(u32::MAX, Ordering::Release);
@@ -139,7 +139,7 @@
let current_donated = holder.pi_donated_prio.load(Ordering::Relaxed);
// Donate if our priority is higher (lower number)
if (my_prio as u32) < current_donated {
- holder.pi_donated_prio.store(my_prio as u32, Ordering::Relaxed);
+ holder.pi_donated_prio.store(my_prio as u32, Ordering::Release);
}
}
}
@@ -0,0 +1,272 @@
--- a/src/acpi/madt/arch/x86.rs
+++ b/src/acpi/madt/arch/x86.rs
@@ -3,6 +3,8 @@
sync::atomic::{AtomicU8, Ordering},
};
+use x86::time::rdtsc;
+
use crate::{
arch::{
device::local_apic::the_local_apic,
@@ -18,6 +20,7 @@
use super::{Madt, MadtEntry};
+use alloc::collections::BTreeSet;
use alloc::vec::Vec;
/// Maximum number of APIC→CPU mappings we track for NUMA topology.
@@ -45,6 +48,67 @@
const TRAMPOLINE: usize = 0x8000;
static TRAMPOLINE_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/trampoline"));
+/// Estimate TSC frequency in MHz from CPUID.
+///
+/// Tries CPUID leaf 0x16 (Processor Frequency Information) first,
+/// then CPUID leaf 0x15 (TSC/Core Crystal Clock Ratio).
+/// Returns None if frequency cannot be determined.
+fn tsc_freq_mhz_cpuid() -> Option<u64> {
+ let max_leaf = unsafe { core::arch::x86_64::__cpuid(0).eax as u32 };
+
+ // CPUID leaf 0x16: EAX = Core Base Frequency in MHz (Intel)
+ if max_leaf >= 0x16 {
+ let mhz = unsafe { core::arch::x86_64::__cpuid(0x16) }.eax as u64;
+ if mhz > 0 {
+ return Some(mhz);
+ }
+ }
+
+ // CPUID leaf 0x15: EAX = denominator, EBX = numerator, ECX = crystal Hz
+ if max_leaf >= 0x15 {
+ let res = unsafe { core::arch::x86_64::__cpuid(0x15) };
+ let denom = res.eax as u64;
+ let numer = res.ebx as u64;
+ let crystal_hz = res.ecx as u64;
+ if denom > 0 && numer > 0 && crystal_hz > 0 {
+ // TSC freq = crystal_hz * numer / denom
+ let tsc_hz = crystal_hz * numer / denom;
+ return Some(tsc_hz / 1_000_000); // Hz → MHz
+ }
+ }
+
+ None
+}
+
+/// Early-boot microsecond delay using the Time Stamp Counter.
+///
+/// Uses CPUID-based TSC frequency estimation when available.
+/// Falls back to a conservative spin loop calibrated for the
+/// minimum expected CPU speed (1 GHz).
+///
+/// # Safety
+/// Must only be called after the BSP TSC is running (always true
+/// after CPU reset on x86).
+fn early_udelay(us: u64) {
+ if let Some(mhz) = tsc_freq_mhz_cpuid() {
+ // TSC-based delay: precise on invariant TSC (all modern x86).
+ // MHz = cycles per µs.
+ let target = unsafe { rdtsc() } + us * mhz;
+ while unsafe { rdtsc() } < target {
+ hint::spin_loop();
+ }
+ } else {
+ // Fallback: conservative spin loop.
+ // spin_loop() (PAUSE) is ~40 cycles on modern Intel, ~1 on AMD.
+ // At 1 GHz minimum: 1000 cycles/µs ÷ 40 cycles/iter = 25 iters/µs.
+ // Use 50 iters/µs for safety margin on slower/variable CPUs.
+ let iters = us.saturating_mul(50);
+ for _ in 0..iters {
+ hint::spin_loop();
+ }
+ }
+}
+
fn current_x2apic_processor_uid(madt: &Madt, apic_id: u32) -> Option<u32> {
madt.iter().find_map(|entry| match entry {
MadtEntry::LocalX2Apic(x2apic) if x2apic.x2apic_id == apic_id => Some(x2apic.processor_uid),
@@ -133,6 +197,31 @@
crate::profiling::allocate(preliminary_cpu_count as u32);
}
+ // Firmware bug detection: check for duplicate APIC IDs in MADT.
+ // Some firmware (especially on early BIOS/UEFI) may list the same
+ // processor multiple times. Keep first occurrence, warn on duplicates.
+ let mut seen_apic_ids: BTreeSet<u32> = BTreeSet::new();
+ {
+ let _ = seen_apic_ids.insert(me.get()); // BSP
+ for entry in madt.iter() {
+ match entry {
+ MadtEntry::LocalApic(local) if local.flags & 1 == 1 => {
+ 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 => {
+ let id = local.x2apic_id;
+ if !seen_apic_ids.insert(id) {
+ warn!("MADT: duplicate x2APIC ID {} in LocalX2Apic entry, firmware bug", id);
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
for madt_entry in madt.iter() {
debug!(" {:x?}", madt_entry);
if let MadtEntry::LocalApic(ap_local_apic) = madt_entry {
@@ -192,9 +281,14 @@
};
AP_READY.store(false, Ordering::SeqCst);
- // Send INIT IPI
+ // Clear APIC Error Status Register before starting AP.
+ // Intel SDM §8.4.4: ESR should be cleared before sending SIPI.
+ unsafe { local_apic.esr(); }
+
+ // Send INIT IPI (Assert)
{
- let mut icr = 0x4500;
+ // ICR: Delivery Mode=INIT(101), Level=Assert, Trigger=Edge
+ let mut icr = 0x4500u64;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
@@ -203,20 +297,53 @@
local_apic.set_icr(icr);
}
- // Send START IPI
+ // Intel SDM Vol 3A §8.4.4: wait 10ms after INIT deassert
+ // before sending first SIPI. Modern CPUs may need less,
+ // but 10ms is the safe specification-compliant value.
+ early_udelay(10_000);
+
+ // Send START IPI #1
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
- let mut icr = 0x4600 | ap_segment as u64;
-
+ // ICR: Delivery Mode=StartUp(110), Vector=ap_segment
+ // Note: bit 14 (Level) must be 0 for SIPI per Intel SDM.
+ let mut icr = 0x0600 | ap_segment as u64;
if local_apic.x2 {
icr |= u64::from(ap_local_apic.id) << 32;
} else {
icr |= u64::from(ap_local_apic.id) << 56;
}
+ local_apic.set_icr(icr);
+ }
+
+ // Intel SDM: wait 200µs between SIPIs
+ early_udelay(200);
+ // Send START IPI #2 (recommended for compatibility)
+ {
+ let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
+ let mut icr = 0x0600 | ap_segment as u64;
+ if local_apic.x2 {
+ icr |= u64::from(ap_local_apic.id) << 32;
+ } else {
+ icr |= u64::from(ap_local_apic.id) << 56;
+ }
local_apic.set_icr(icr);
}
+ // Wait briefly for SIPI to be accepted
+ early_udelay(200);
+
+ // Check ESR for delivery errors after SIPI sequence.
+ // Bit 5 = Send Accept Error, Bit 6 = Send Illegal Vector.
+ let esr_val = unsafe { local_apic.esr() };
+ if esr_val != 0 {
+ println!(
+ "KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
+ ap_local_apic.id, esr_val
+ );
+ }
+
// Wait for trampoline ready with timeout
let mut trampoline_ready = false;
for _ in 0..AP_SPIN_LIMIT {
@@ -311,34 +438,50 @@
}
AP_READY.store(false, Ordering::SeqCst);
+ // Clear APIC Error Status Register before starting AP.
+ unsafe { local_apic.esr(); }
+
+ // Send INIT IPI (Assert)
{
let mut icr = 0x4500u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
- for _ in 0..100_000 {
- hint::spin_loop();
- }
+ // Intel SDM Vol 3A §8.4.4: wait 10ms after INIT
+ early_udelay(10_000);
+ // Send START IPI #1
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
- let mut icr = 0x4600u64 | ap_segment as u64;
+ let mut icr = 0x0600u64 | ap_segment as u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
- for _ in 0..2_000_000 {
- hint::spin_loop();
- }
+ // Intel SDM: wait 200µs between SIPIs
+ early_udelay(200);
+ // Send START IPI #2 (recommended for compatibility)
{
let ap_segment = (TRAMPOLINE >> 12) & 0xFF;
- let mut icr = 0x4600u64 | ap_segment as u64;
+ let mut icr = 0x0600u64 | ap_segment as u64;
icr |= u64::from(apic_id) << 32;
local_apic.set_icr(icr);
}
+ // Wait briefly for SIPI acceptance
+ early_udelay(200);
+
+ // Check ESR for delivery errors.
+ let esr_val = unsafe { local_apic.esr() };
+ if esr_val != 0 {
+ println!(
+ "KERNEL AP: CPU {} SIPI delivery error (ESR={:#x}), continuing",
+ apic_id, esr_val
+ );
+ }
+
let mut trampoline_ready = false;
for _ in 0..AP_SPIN_LIMIT {
if unsafe { (*ap_ready.cast::<AtomicU8>()).load(Ordering::SeqCst) } != 0 {
@@ -407,6 +550,19 @@
crate::percpu::PercpuBlock::current().numa_node.set(node);
}
+ // Log final CPU count vs maximum
+ let cpu_count = crate::CPU_COUNT.load(Ordering::SeqCst);
+ info!(
+ "SMP: {} CPUs online (max {})",
+ cpu_count, crate::cpu_set::MAX_CPU_COUNT
+ );
+ if cpu_count > crate::cpu_set::MAX_CPU_COUNT * 80 / 100 {
+ warn!(
+ "SMP: CPU count approaching MAX_CPU_COUNT limit ({}/{})",
+ cpu_count, crate::cpu_set::MAX_CPU_COUNT
+ );
+ }
+
// Unmap trampoline
if let Some((_frame, _, flush)) = unsafe {
KernelMapper::lock_rw()
@@ -0,0 +1,24 @@
--- a/src/cpu_set.rs
+++ b/src/cpu_set.rs
@@ -42,17 +42,18 @@
}
#[cfg(target_pointer_width = "64")]
-pub const MAX_CPU_COUNT: u32 = 128;
+pub const MAX_CPU_COUNT: u32 = 256;
#[cfg(target_pointer_width = "32")]
pub const MAX_CPU_COUNT: u32 = 32;
const SET_WORDS: usize = (MAX_CPU_COUNT / usize::BITS) as usize;
-// TODO: Support more than 128 CPUs.
+// TODO: Support more than 256 CPUs.
// The maximum number of CPUs on Linux is configurable, and the type for LogicalCpuSet and
// LogicalCpuId may be optimized accordingly. In that case, box the mask if it's larger than some
-// base size (probably 256 bytes).
+// base size (probably 256 bytes). AMD EPYC has 128C/256T, Threadripper PRO 96C/192T —
+// 256 covers current hardware.
#[derive(Debug)]
pub struct LogicalCpuSet([AtomicUsize; SET_WORDS]);
@@ -0,0 +1,23 @@
--- a/src/acpi/sdt.rs
+++ b/src/acpi/sdt.rs
@@ -24,4 +24,20 @@
let header_size = size_of::<Sdt>();
total_size.saturating_sub(header_size)
}
+
+ /// Validate the SDT checksum.
+ ///
+ /// Per ACPI 6.5 §5.2.2: the entire table (including the checksum field)
+ /// must sum to 0 when all bytes are added together as unsigned 8-bit values.
+ pub fn validate_checksum(&self) -> bool {
+ let ptr = self as *const _ as *const u8;
+ let len = self.length as usize;
+ if len < size_of::<Sdt>() {
+ return false;
+ }
+ let sum = unsafe { core::slice::from_raw_parts(ptr, len) }
+ .iter()
+ .fold(0u8, |acc, &b| acc.wrapping_add(b));
+ sum == 0
+ }
}
@@ -0,0 +1,15 @@
--- a/src/acpi/madt/mod.rs
+++ b/src/acpi/madt/mod.rs
@@ -34,6 +34,12 @@
let madt = Madt::new(find_one_sdt!("APIC"));
if let Some(madt) = madt {
+ // Validate MADT checksum per ACPI 6.5 §5.2.2
+ if !madt.sdt.validate_checksum() {
+ error!("MADT checksum validation failed, skipping APIC initialization");
+ return;
+ }
+
// safe because no APs have been started yet.
unsafe { MADT.get().write(Some(madt)) };
@@ -0,0 +1,87 @@
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -361,6 +361,7 @@
}
/// This is the scheduler function which currently utilises Deficit Weighted Round Robin Scheduler
+/// with NUMA-aware context selection preference.
fn select_next_context(
token: &mut CleanLockToken,
percpu: &PercpuBlock,
@@ -386,6 +387,10 @@
let total_contexts: usize = contexts_list.iter().map(|q| q.len()).sum();
let mut skipped_contexts = 0;
+ // NUMA-aware selection: remember cross-node fallback candidate.
+ let my_numa_node = percpu.numa_node.get();
+ let mut cross_node_fallback: Option<(usize, ArcContextLockWriteGuard)> = None;
+
'priority: loop {
i = (i + 1) % 40;
total_iters += 1;
@@ -450,11 +455,44 @@
// Is this context runnable on this CPU?
let sw = unsafe { update_runnable(&mut next_context_guard, cpu_id, switch_time) };
if let UpdateResult::CanSwitch = sw {
- // Cache the new context's priority for MCS lock priority donation.
- percpu.current_prio.set(next_context_guard.prio);
- next_context_guard_opt = Some(next_context_guard);
- balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
- break 'priority;
+ // NUMA-aware selection: check if this context's last CPU was on the same node.
+ let same_node = if my_numa_node != u8::MAX {
+ next_context_guard.cpu_id
+ .map(|cid| {
+ crate::percpu::get_for_cpu(cid)
+ .map(|p| p.numa_node.get() == my_numa_node)
+ .unwrap_or(false)
+ })
+ .unwrap_or(true) // New context (no last CPU) — treat as same node
+ } else {
+ true // No NUMA info — treat all as same node
+ };
+
+ if same_node {
+ // Cache-warm: select immediately
+ percpu.current_prio.set(next_context_guard.prio);
+ next_context_guard_opt = Some(next_context_guard);
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
+ break 'priority;
+ } else {
+ // Cross-node candidate: save as fallback, keep scanning for same-node
+ if cross_node_fallback.is_none() {
+ // Cache the priority and balance for later
+ cross_node_fallback =
+ Some((next_context_guard.prio, next_context_guard));
+ balance[i] -= SCHED_PRIO_TO_WEIGHT[20];
+ // Don't break — keep looking for a same-node context
+ continue;
+ } else {
+ // Already have a cross-node fallback; push this one back
+ contexts.push_back(next_context_ref);
+ skipped_contexts += 1;
+ if skipped_contexts >= total_contexts {
+ break 'priority;
+ }
+ continue;
+ }
+ }
} else {
if matches!(sw, UpdateResult::Blocked) {
idle_contexts(token.token()).push_back(next_context_ref);
@@ -469,6 +507,15 @@
}
}
}
+
+ // If we found a cross-node fallback but no same-node context, use it
+ if next_context_guard_opt.is_none() {
+ if let Some((prio, guard)) = cross_node_fallback {
+ percpu.current_prio.set(prio);
+ next_context_guard_opt = Some(guard);
+ }
+ }
+
percpu.balance.set(balance);
percpu.last_queue.set(i);
@@ -0,0 +1,31 @@
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -21,7 +21,7 @@
cpu_set::{LogicalCpuId, MAX_CPU_COUNT},
cpu_stats::{CpuStats, CpuStatsData},
ptrace::Session,
- sync::{mcs::McsNode, CleanLockToken},
+ sync::{mcs::McsNode, mcs::McsRawLock, CleanLockToken},
syscall::debug::SyscallDebugInfo,
};
@@ -66,6 +66,11 @@
/// `u8::MAX` means unknown (no SRAT or APIC ID not listed).
pub numa_node: Cell<u8>,
+ /// Pointer to the MCS lock this CPU is currently spinning on (for transitive PI).
+ /// `null` when not waiting on any lock. Set in McsRawLock::acquire() before
+ /// entering the spin loop, cleared upon acquisition.
+ pub waiting_on_lock: AtomicPtr<McsRawLock>,
+
// TODO: Put mailbox queues here, e.g. for TLB shootdown? Just be sure to 128-byte align it
// first to avoid cache invalidation.
pub profiling: Option<&'static crate::profiling::RingBuffer>,
@@ -360,6 +365,7 @@
pi_donated_prio: AtomicU32::new(u32::MAX),
current_prio: Cell::new(39),
numa_node: Cell::new(u8::MAX),
+ waiting_on_lock: AtomicPtr::new(core::ptr::null_mut()),
ptrace_flags: Cell::new(PtraceFlags::empty()),
ptrace_session: RefCell::new(None),
inside_syscall: Cell::new(false),
@@ -0,0 +1,121 @@
--- a/src/sync/mcs.rs
+++ b/src/sync/mcs.rs
@@ -3,12 +3,21 @@
//! Each waiter spins on its own local `locked` flag instead of a shared lock
//! word, eliminating cache-line bouncing under contention. FIFO ordering
//! guarantees fairness. O(1) cache-line transfers on unlock.
+//!
+//! Supports transitive priority inheritance: when CPU A waits on a lock held
+//! by CPU B, and CPU B waits on a lock held by CPU C, A's priority is
+//! propagated through the chain to C (up to MAX_PI_CHAIN_DEPTH hops).
use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicU32, Ordering};
use core::{hint, ptr};
use crate::percpu::PercpuBlock;
+/// Maximum depth for transitive priority inheritance chain following.
+/// Prevents infinite loops from theoretical lock cycles and bounds latency.
+/// Linux uses 20; 8 is conservative for a microkernel with fewer nesting levels.
+const MAX_PI_CHAIN_DEPTH: u32 = 8;
+
/// A node in the MCS lock queue.
pub struct McsNode {
pub next: AtomicPtr<McsNode>,
@@ -55,17 +64,23 @@
(*prev).next.store((node as *const McsNode).cast_mut(), Ordering::Release);
}
let percpu = PercpuBlock::current();
+ // Record which lock we're spinning on (for transitive PI chain following)
+ percpu.waiting_on_lock.store(
+ (self as *const McsRawLock).cast_mut(),
+ Ordering::Release,
+ );
let mut donated = false;
while node.locked.load(Ordering::Acquire) {
percpu.maybe_handle_tlb_shootdown();
- // Donate priority to the lock holder once per acquisition
+ // Donate priority to the lock holder (transitively) once per acquisition
if !donated {
self.maybe_donate_priority(percpu);
donated = true;
}
hint::spin_loop();
}
- // We now hold the lock
+ // Clear waiting_on_lock before proceeding — we now hold the lock
+ percpu.waiting_on_lock.store(ptr::null_mut(), Ordering::Release);
self.holder_cpu.store(percpu.cpu_id.get(), Ordering::Release);
true
}
@@ -120,27 +135,54 @@
ok
}
- /// Donate current CPU's context priority to the lock holder's CPU.
+ /// Donate current CPU's context priority to the lock holder's CPU,
+ /// following the PI chain transitively (A→B→C).
+ ///
/// Reads priority from PercpuBlock::current_prio (cached by the scheduler)
/// to avoid acquiring any lock in the MCS spin loop.
+ ///
+ /// Chain following: if the holder is itself waiting on another lock,
+ /// we propagate our priority to that lock's holder too, up to
+ /// MAX_PI_CHAIN_DEPTH hops.
fn maybe_donate_priority(&self, my_percpu: &PercpuBlock) {
- let holder_cpu_id = self.holder_cpu.load(Ordering::Relaxed);
- if holder_cpu_id == u32::MAX {
- return;
- }
- // Read our own priority from the per-CPU cache (set by scheduler,
- // no lock required).
- let my_prio = my_percpu.current_prio.get();
- // Look up holder's PercpuBlock
- let holder_percpu = crate::percpu::get_for_cpu(
- crate::cpu_set::LogicalCpuId::new(holder_cpu_id),
- );
- if let Some(holder) = holder_percpu {
+ let my_prio = my_percpu.current_prio.get() as u32;
+ let mut current_holder_cpu = self.holder_cpu.load(Ordering::Relaxed);
+
+ for _ in 0..MAX_PI_CHAIN_DEPTH {
+ if current_holder_cpu == u32::MAX {
+ return;
+ }
+ let holder_percpu = crate::percpu::get_for_cpu(
+ crate::cpu_set::LogicalCpuId::new(current_holder_cpu),
+ );
+ let Some(holder) = holder_percpu else {
+ return;
+ };
+
+ // Donate if our priority is higher (lower number) than current donation
let current_donated = holder.pi_donated_prio.load(Ordering::Relaxed);
- // Donate if our priority is higher (lower number)
- if (my_prio as u32) < current_donated {
- holder.pi_donated_prio.store(my_prio as u32, Ordering::Release);
+ if my_prio < current_donated {
+ holder.pi_donated_prio.store(my_prio, Ordering::Release);
+ }
+
+ // Follow the chain: is this holder also waiting on another lock?
+ let next_lock_ptr = holder.waiting_on_lock.load(Ordering::Relaxed);
+ if next_lock_ptr.is_null() {
+ return;
+ }
+ // SAFETY: The pointed-to McsRawLock is a long-lived struct field
+ // (e.g., part of the run queue). The holder is currently spinning
+ // in acquire(), so the pointer is valid. We only read holder_cpu
+ // (an atomic u32) — no mutable access needed.
+ let next_holder_cpu =
+ unsafe { (*next_lock_ptr).holder_cpu.load(Ordering::Relaxed) };
+
+ // Cycle detection: if the next holder is the same CPU we just visited, stop
+ if next_holder_cpu == current_holder_cpu {
+ return;
}
+ current_holder_cpu = next_holder_cpu;
}
+ // Chain depth exhausted — stop to bound latency
}
}
@@ -0,0 +1,82 @@
--- a/src/syscall/process.rs
+++ b/src/syscall/process.rs
@@ -11,6 +11,7 @@
memory::{AddrSpace, Grant, PageSpan},
ContextRef,
},
+ cpu_set::RawMask,
event,
sync::{CleanLockToken, RwLock},
syscall::flag::{EventFlags, O_CREAT, O_RDWR},
@@ -295,3 +296,71 @@
.expect("failed to insert fd to current context")
.get()
}
+
+/// Set CPU affinity mask for a process.
+///
+/// # Arguments (syscall ABI)
+/// - `pid`: Process ID (0 = current process; other PIDs not yet supported)
+/// - `mask_ptr`: Pointer to a `RawMask` (32 bytes on 64-bit, 256-bit bitmap)
+/// - `mask_len`: Length of mask in bytes (must equal `size_of::<RawMask>()`)
+pub fn sched_setaffinity(
+ pid: usize,
+ mask_ptr: super::usercopy::UserSliceRo,
+ token: &mut CleanLockToken,
+) -> Result<usize> {
+ // Validate mask size
+ if mask_ptr.len() != core::mem::size_of::<RawMask>() {
+ return Err(Error::new(super::error::EINVAL));
+ }
+
+ // pid == 0 means current process
+ let target = if pid == 0 {
+ context::current()
+ } else {
+ // TODO: Support PID-based lookup (requires context list iteration
+ // with lock token downgrades). For now, only pid=0 is supported.
+ return Err(Error::new(super::error::ESRCH));
+ };
+
+ // Read mask from userspace
+ let raw_mask: RawMask = unsafe { mask_ptr.read_exact() }?;
+
+ // Apply to context's affinity mask
+ let mut ctx = target.write(token.token());
+ ctx.sched_affinity.override_from(&raw_mask);
+
+ Ok(0)
+}
+
+/// Get CPU affinity mask for a process.
+///
+/// # Arguments (syscall ABI)
+/// - `pid`: Process ID (0 = current process; other PIDs not yet supported)
+/// - `mask_ptr`: Pointer to a `RawMask` buffer (32 bytes on 64-bit)
+/// - `mask_len`: Length of buffer in bytes (must equal `size_of::<RawMask>()`)
+///
+/// # Returns
+/// Number of bytes written to mask_ptr on success.
+pub fn sched_getaffinity(
+ pid: usize,
+ mask_ptr: super::usercopy::UserSliceWo,
+ token: &mut CleanLockToken,
+) -> Result<usize> {
+ // Validate mask size
+ if mask_ptr.len() != core::mem::size_of::<RawMask>() {
+ return Err(Error::new(super::error::EINVAL));
+ }
+
+ // pid == 0 means current process
+ let target = if pid == 0 {
+ context::current()
+ } else {
+ return Err(Error::new(super::error::ESRCH));
+ };
+
+ let ctx = target.read(token.token());
+ let raw_mask = ctx.sched_affinity.to_raw();
+ mask_ptr.copy_common_bytes_from_slice(crate::cpu_set::mask_as_bytes(&raw_mask))?;
+
+ Ok(core::mem::size_of::<RawMask>())
+}
@@ -0,0 +1,25 @@
--- a/src/syscall/mod.rs
+++ b/src/syscall/mod.rs
@@ -28,6 +28,11 @@
sync::CleanLockToken,
};
+/// Local syscall numbers not yet in the redox_syscall crate.
+/// These are allocated from the 987+ range to avoid collisions with crate numbers.
+pub const SYS_SCHED_SETAFFINITY: usize = 987;
+pub const SYS_SCHED_GETAFFINITY: usize = 988;
+
/// Debug
pub mod debug;
@@ -220,6 +225,10 @@
unlinkat(fd, UserSlice::ro(c, d)?, e, f as _, g as _, token).map(|()| 0)
}
SYS_YIELD => sched_yield(token).map(|()| 0),
+
+ // P17-3: CPU affinity syscalls. Numbers allocated locally (not yet in redox_syscall crate).
+ SYS_SCHED_SETAFFINITY => sched_setaffinity(b, UserSlice::ro(c, d)?, token),
+ SYS_SCHED_GETAFFINITY => sched_getaffinity(b, UserSlice::wo(c, d)?, token),
SYS_NANOSLEEP => nanosleep(
UserSlice::ro(b, size_of::<TimeSpec>())?,
UserSlice::wo(c, size_of::<TimeSpec>())?.none_if_null(),
@@ -0,0 +1,52 @@
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -26,6 +26,11 @@
Blocked,
}
+/// Default number of PIT ticks before triggering a context switch.
+/// At ~2.25 ms per tick, 3 ticks ≈ 6.75 ms timeslice.
+/// Configurable per-CPU via `ContextSwitchPercpu::preempt_interval`.
+const DEFAULT_PREEMPT_INTERVAL: usize = 3;
+
// A simple geometric series where value[i] ~= value[i - 1] * 1.25
const SCHED_PRIO_TO_WEIGHT: [usize; 40] = [
88761, 71755, 56483, 46273, 36291, 29154, 23254, 18705, 14949, 11916, 9548, 7620, 6100, 4904,
@@ -90,13 +95,15 @@
///
/// The function also calls the signal handler after switching contexts.
pub fn tick(token: &mut CleanLockToken) {
- let ticks_cell = &PercpuBlock::current().switch_internals.pit_ticks;
+ let percpu = PercpuBlock::current();
+ let ticks_cell = &percpu.switch_internals.pit_ticks;
let new_ticks = ticks_cell.get() + 1;
ticks_cell.set(new_ticks);
- // Trigger a context switch after every 3 ticks (approx. 6.75 ms).
- if new_ticks >= 3 {
+ // Trigger a context switch when the per-CPU preempt interval is reached.
+ let interval = percpu.switch_internals.preempt_interval.get();
+ if new_ticks >= interval {
switch(token);
crate::context::signal::signal_handler(token);
}
@@ -505,6 +512,10 @@
/// Per-CPU context switch flag. Set to true during a context switch on this CPU.
/// Replaced the global CONTEXT_SWITCH_LOCK to eliminate cross-CPU serialization.
in_context_switch: Cell<bool>,
+ /// Number of PIT ticks before triggering a context switch.
+ /// Default: 3 (≈6.75 ms). Lower values improve interactive responsiveness;
+ /// higher values improve throughput for batch/compute workloads.
+ preempt_interval: Cell<usize>,
current_ctxt: RefCell<Option<Arc<ContextLock>>>,
@@ -520,6 +531,7 @@
switch_time: Cell::new(0),
pit_ticks: Cell::new(0),
in_context_switch: Cell::new(false),
+ preempt_interval: Cell::new(DEFAULT_PREEMPT_INTERVAL),
current_ctxt: RefCell::new(None),
idle_ctxt: RefCell::new(None),
being_sigkilled: Cell::new(false),
+114
View File
@@ -0,0 +1,114 @@
--- a/src/arch/x86_shared/idt.rs
+++ b/src/arch/x86_shared/idt.rs
@@ -110,6 +110,8 @@
}
pub fn available_irqs_iter(cpu_id: LogicalCpuId) -> impl Iterator<Item = u8> + 'static {
+ let count = (32..=254).filter(|&index| !is_reserved(cpu_id, index)).count();
+ info!("available_irqs_iter: cpu_id={} count={}", cpu_id.get(), count);
(32..=254).filter(move |&index| !is_reserved(cpu_id, index))
}
--- a/src/scheme/irq.rs
+++ b/src/scheme/irq.rs
@@ -83,7 +83,7 @@
#[allow(dead_code)]
enum Handle {
SchemeRoot,
- Irq { ack: AtomicUsize, irq: u8 },
+ Irq { ack: AtomicUsize, irq: u8, cpu_id: LogicalCpuId },
Avail(LogicalCpuId),
TopLevel,
Phandle(u8, Vec<u8>),
@@ -93,7 +93,7 @@
impl Handle {
fn as_irq_handle(&self) -> Option<(&AtomicUsize, u8)> {
match self {
- &Self::Irq { ref ack, irq } => Some((ack, irq)),
+ &Self::Irq { ref ack, irq, cpu_id: _ } => Some((ack, irq)),
_ => None,
}
}
@@ -147,6 +147,7 @@
Handle::Irq {
ack: AtomicUsize::new(0),
irq: irq_number,
+ cpu_id: LogicalCpuId::BSP,
},
InternalFlags::empty(),
)
@@ -165,6 +166,7 @@
Handle::Irq {
ack: AtomicUsize::new(0),
irq: irq_number,
+ cpu_id,
},
InternalFlags::empty(),
)
@@ -206,6 +208,7 @@
Handle::Irq {
ack: AtomicUsize::new(0),
irq: irq_number as u8,
+ cpu_id: LogicalCpuId::new(0),
},
InternalFlags::empty(),
)
@@ -349,6 +352,7 @@
Handle::Irq {
ack: AtomicUsize::new(0),
irq: plain_irq_number,
+ cpu_id: LogicalCpuId::BSP,
},
InternalFlags::empty(),
)
@@ -404,6 +408,7 @@
}
}
Handle::Avail(cpu_id) => {
+ let mut listed = 0;
for vector in available_irqs_iter(cpu_id).skip(opaque) {
let irq = vector_to_irq(vector);
if cpu_id == LogicalCpuId::BSP && irq < BASE_IRQ_COUNT {
@@ -417,7 +422,9 @@
name: &intermediate,
next_opaque_id: u64::from(vector) + 1,
})?;
+ listed += 1;
}
+ info!("irq getdents Avail: cpu_id={} opaque={} listed={}", cpu_id.get(), opaque, listed);
}
_ => return Err(Error::new(ENOTDIR)),
}
@@ -452,11 +459,14 @@
let handle = handles_guard.get(id)?;
if let &Handle::Irq {
- irq: handle_irq, ..
+ irq: handle_irq,
+ cpu_id: handle_cpu_id,
+ ..
} = handle
&& handle_irq > BASE_IRQ_COUNT
{
- set_reserved(LogicalCpuId::BSP, irq_to_vector(handle_irq), false);
+ info!("irq close: unreserving vector {} on cpu_id={}", irq_to_vector(handle_irq), handle_cpu_id.get());
+ set_reserved(handle_cpu_id, irq_to_vector(handle_irq), false);
}
Ok(())
}
@@ -497,6 +507,7 @@
&Handle::Irq {
irq: handle_irq,
ack: ref handle_ack,
+ cpu_id: _,
} => {
if buffer.len() < size_of::<usize>() {
return Err(Error::new(EINVAL));
@@ -611,6 +622,7 @@
Handle::Irq {
irq: handle_irq,
ack: ref handle_ack,
+ cpu_id: _,
} => {
if buffer.len() < size_of::<usize>() {
return Err(Error::new(EINVAL));
@@ -0,0 +1,15 @@
--- a/src/arch/x86_shared/device/local_apic.rs
+++ b/src/arch/x86_shared/device/local_apic.rs
@@ -59,10 +59,10 @@
.is_some_and(|feature_info| feature_info.has_x2apic());
if !self.x2 {
- debug!("Detected xAPIC at {:#x}", physaddr.data());
+ info!("Detected xAPIC at {:#x}", physaddr.data());
self.address = map_device_memory(physaddr, 4096).data();
} else {
- debug!("Detected x2APIC");
+ 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) {
@@ -0,0 +1,52 @@
diff --git a/src/percpu.rs b/src/percpu.rs
index f4ad5e66..80ffb959 100644
--- a/src/percpu.rs
+++ b/src/percpu.rs
@@ -4,6 +4,7 @@ use alloc::{
};
use core::{
cell::{Cell, RefCell},
+ hint,
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
};
@@ -104,11 +105,36 @@ pub fn shootdown_tlb_ipi(target: Option<LogicalCpuId>) {
crate::ipi::ipi_single(crate::ipi::IpiKind::Tlb, percpublock);
} else {
+ // Broadcast TLB shootdown: set flag on all other CPUs, then send a single
+ // IPI with "all except self" destination shorthand instead of N individual IPIs.
+ let my_percpublock = PercpuBlock::current();
for id in 0..crate::cpu_count() {
- // TODO: Optimize: use global counter and percpu ack counters, send IPI using
- // destination shorthand "all CPUs".
- shootdown_tlb_ipi(Some(LogicalCpuId::new(id)));
+ let target_id = LogicalCpuId::new(id);
+ if target_id == my_percpublock.cpu_id {
+ continue;
+ }
+ let Some(percpublock) = (unsafe {
+ ALL_PERCPU_BLOCKS[id as usize]
+ .load(Ordering::Acquire)
+ .as_ref()
+ }) else {
+ continue;
+ };
+ // Wait if this CPU still has a pending shootdown from a previous request
+ #[expect(clippy::bool_comparison)]
+ while percpublock
+ .wants_tlb_shootdown
+ .swap(true, Ordering::Release)
+ == true
+ {
+ while percpublock.wants_tlb_shootdown.load(Ordering::Relaxed) == true {
+ my_percpublock.maybe_handle_tlb_shootdown();
+ hint::spin_loop();
+ }
+ }
}
+ // Single broadcast IPI to all other CPUs using destination shorthand
+ crate::ipi::ipi(crate::ipi::IpiKind::Tlb, crate::ipi::IpiTarget::Other);
}
}
impl PercpuBlock {
@@ -0,0 +1,39 @@
--- a/src/arch/x86_shared/device/ioapic.rs
+++ b/src/arch/x86_shared/device/ioapic.rs
@@ -120,6 +120,21 @@
reg |= u64::from(mask) << 16;
let _ = guard.write_ioredtbl(idx, reg);
}
+ /// Change the destination APIC for a GSI by reprogramming the redirection table entry.
+ /// Preserves all other fields (vector, polarity, trigger mode, delivery mode, mask).
+ /// Returns true if the entry was successfully updated.
+ pub fn set_irq_affinity(&self, gsi: u32, dest: ApicId) -> bool {
+ let idx = (gsi - self.gsi_start) as u8;
+ let mut guard = self.regs.lock();
+ let Some(mut entry) = guard.read_ioredtbl(idx) else {
+ return false;
+ };
+ // Clear destination field (bits 63:56 for xAPIC physical mode)
+ // and set new destination APIC ID
+ entry &= !(0xFF_u64 << 56);
+ entry |= u64::from(dest.get()) << 56;
+ guard.write_ioredtbl(idx, entry)
+ }
}
#[repr(u8)]
@@ -474,3 +489,14 @@
};
apic.set_mask(gsi, false);
}
+
+/// Change the destination CPU for an IRQ by reprogramming the IOAPIC redirection entry.
+/// Resolves the legacy IRQ to its GSI, finds the owning IOAPIC, and updates the destination
+/// APIC ID in the redirection table while preserving all other fields.
+pub unsafe fn set_affinity(irq: u8, dest: ApicId) -> bool {
+ let gsi = resolve(irq);
+ match find_ioapic(gsi) {
+ Some(apic) => apic.set_irq_affinity(gsi, dest),
+ None => false,
+ }
+}
@@ -0,0 +1,180 @@
diff --git a/src/context/arch/aarch64.rs b/src/context/arch/aarch64.rs
index 33dc83a..b8f8ac9 100644
--- a/src/context/arch/aarch64.rs
+++ b/src/context/arch/aarch64.rs
@@ -4,16 +4,10 @@ use crate::{
percpu::PercpuBlock,
syscall::FloatRegisters,
};
-use core::{mem::offset_of, ptr, sync::atomic::AtomicBool};
+use core::{mem::offset_of, ptr};
use spin::Once;
use syscall::{EnvRegisters, Result};
-/// This must be used by the kernel to ensure that context switches are done atomically
-/// Compare and exchange this to true when beginning a context switch on any CPU
-/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
-/// This must be done, as no locks can be held on the stack during switch
-pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
-
// 512 bytes for registers, extra bytes for fpcr and fpsr
pub const KFX_ALIGN: usize = 16;
diff --git a/src/context/arch/riscv64.rs b/src/context/arch/riscv64.rs
index 4bd843e..fe63639 100644
--- a/src/context/arch/riscv64.rs
+++ b/src/context/arch/riscv64.rs
@@ -2,13 +2,11 @@ use crate::{
arch::interrupt::InterruptStack, context::context::Kstack, memory::RmmA, percpu::PercpuBlock,
syscall::FloatRegisters,
};
-use core::{mem::offset_of, sync::atomic::AtomicBool};
+use core::mem::offset_of;
use rmm::{Arch, VirtualAddress};
use spin::Once;
use syscall::{error::*, EnvRegisters};
-pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
-
pub const KFX_ALIGN: usize = 16;
#[derive(Clone, Debug, Default)]
diff --git a/src/context/arch/x86.rs b/src/context/arch/x86.rs
index 2862d35..dc01f6e 100644
--- a/src/context/arch/x86.rs
+++ b/src/context/arch/x86.rs
@@ -1,4 +1,4 @@
-use core::{mem::offset_of, sync::atomic::AtomicBool};
+use core::mem::offset_of;
use rmm::{Arch, VirtualAddress};
use spin::Once;
use syscall::{error::*, EnvRegisters};
@@ -14,12 +14,6 @@ use crate::{
syscall::FloatRegisters,
};
-/// This must be used by the kernel to ensure that context switches are done atomically
-/// Compare and exchange this to true when beginning a context switch on any CPU
-/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
-/// This must be done, as no locks can be held on the stack during switch
-pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
-
const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;
pub const KFX_ALIGN: usize = 16;
diff --git a/src/context/arch/x86_64.rs b/src/context/arch/x86_64.rs
index 6758c9f..574d373 100644
--- a/src/context/arch/x86_64.rs
+++ b/src/context/arch/x86_64.rs
@@ -1,6 +1,5 @@
use core::{
ptr::{addr_of, addr_of_mut},
- sync::atomic::AtomicBool,
};
use crate::syscall::FloatRegisters;
@@ -12,12 +11,6 @@ use spin::Once;
use syscall::{error::*, EnvRegisters};
use x86::msr;
-/// This must be used by the kernel to ensure that context switches are done atomically
-/// Compare and exchange this to true when beginning a context switch on any CPU
-/// The `Context::switch_to` function will set it back to false, allowing other CPU's to switch
-/// This must be done, as no locks can be held on the stack during switch
-pub static CONTEXT_SWITCH_LOCK: AtomicBool = AtomicBool::new(false);
-
const ST_RESERVED: u128 = 0xFFFF_FFFF_FFFF_0000_0000_0000_0000_0000;
#[cfg(cpu_feature_never = "xsave")]
diff --git a/src/context/switch.rs b/src/context/switch.rs
index 86684c8..d509984 100644
--- a/src/context/switch.rs
+++ b/src/context/switch.rs
@@ -15,8 +15,7 @@ use crate::{
use alloc::{sync::Arc, vec::Vec};
use core::{
cell::{Cell, RefCell},
- hint, mem,
- sync::atomic::Ordering,
+ mem,
};
use syscall::PtraceFlags;
@@ -120,7 +119,10 @@ pub unsafe extern "C" fn switch_finish_hook() {
crate::arch::stop::emergency_reset();
}
}
- arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
+ PercpuBlock::current()
+ .switch_internals
+ .in_context_switch
+ .set(false);
crate::percpu::switch_arch_hook();
}
}
@@ -150,16 +152,15 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
//set PIT Interrupt counter to 0, giving each process same amount of PIT ticks
percpu.switch_internals.pit_ticks.set(0);
- // Acquire the global lock to ensure exclusive access during context switch and avoid
- // issues that would be caused by the unsafe operations below
- // TODO: Better memory orderings?
- while arch::CONTEXT_SWITCH_LOCK
- .compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed)
- .is_err()
- {
- hint::spin_loop();
- percpu.maybe_handle_tlb_shootdown();
- }
+ // Acquire the per-CPU context switch flag. Each CPU can only be in one context
+ // switch at a time. The per-context write locks provide cross-CPU safety; this
+ // flag catches re-entrant switches on the same CPU (a kernel bug).
+ debug_assert!(
+ !percpu.switch_internals.in_context_switch.get(),
+ "context switch re-entry on CPU {}",
+ percpu.cpu_id
+ );
+ percpu.switch_internals.in_context_switch.set(true);
// Lock the previous context.
let prev_context_lock = crate::context::current();
@@ -167,8 +168,8 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
let mut prev_context_guard = unsafe { prev_context_lock.write_arc() };
if !prev_context_guard.is_preemptable() {
- // Unset global lock
- arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
+ // Unset per-CPU context switch flag
+ percpu.switch_internals.in_context_switch.set(false);
// Pretend to have finished switching, so CPU is not idled
return SwitchResult::Switched;
@@ -292,8 +293,8 @@ pub fn switch(token: &mut CleanLockToken) -> SwitchResult {
SwitchResult::Switched
}
_ => {
- // No target was found, unset global lock and return
- arch::CONTEXT_SWITCH_LOCK.store(false, Ordering::SeqCst);
+ // No target was found, unset per-CPU context switch flag and return
+ percpu.switch_internals.in_context_switch.set(false);
percpu.stats.set_state(cpu_stats::CpuState::Idle);
@@ -494,6 +495,9 @@ pub struct ContextSwitchPercpu {
switch_result: Cell<Option<SwitchResultInner>>,
switch_time: Cell<u128>,
pit_ticks: Cell<usize>,
+ /// Per-CPU context switch flag. Set to true during a context switch on this CPU.
+ /// Replaced the global CONTEXT_SWITCH_LOCK to eliminate cross-CPU serialization.
+ in_context_switch: Cell<bool>,
current_ctxt: RefCell<Option<Arc<ContextLock>>>,
@@ -508,6 +512,7 @@ impl ContextSwitchPercpu {
switch_result: Cell::new(None),
switch_time: Cell::new(0),
pit_ticks: Cell::new(0),
+ in_context_switch: Cell::new(false),
current_ctxt: RefCell::new(None),
idle_ctxt: RefCell::new(None),
being_sigkilled: Cell::new(false),
+20
View File
@@ -0,0 +1,20 @@
--- source/configure
+++ source-new/configure
@@ -3827,8 +3827,6 @@
echo $ECHO_N "checking if $CXX works... $ECHO_C" >&6
save_CPPFLAGS="$CPPFLAGS"
- eval cf_includedir=${includedir}
- CPPFLAGS="$CPPFLAGS -I${cf_includedir}"
cat >"conftest.$ac_ext" <<_ACEOF
#line 3833 "configure"
@@ -7122,7 +7122,7 @@
fi
cf_cv_rm_so_locs=yes
;;
- (linux*|gnu*|k*bsd*-gnu)
+ (linux*|gnu*|k*bsd*-gnu|redox*)
if test "$DFT_LWR_MODEL" = "shared" && test -n "$LD_RPATH_OPT" ; then
LOCAL_LDFLAGS="${LD_RPATH_OPT}\$(LOCAL_LIBDIR)"
LOCAL_LDFLAGS2="$LOCAL_LDFLAGS"
+22
View File
@@ -0,0 +1,22 @@
--- a/src/header/stdlib/mod.rs
+++ b/src/header/stdlib/mod.rs
@@ -1703,3 +1703,19 @@
result as c_int
}
+
+/// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getloadavg.html>.
+/// Redox does not track load averages; returns -1.
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn getloadavg(loadavg: *mut c_double, nelem: c_int) -> c_int {
+ if loadavg.is_null() || nelem <= 0 {
+ return -1;
+ }
+ // Fill available slots with 0.0 but still return -1 to indicate
+ // load average is not available. This matches the POSIX convention
+ // where -1 means the load average is unobtainable.
+ for i in 0..nelem {
+ unsafe { *loadavg.add(i as usize) = 0.0; }
+ }
+ -1
+}
@@ -0,0 +1,16 @@
--- a/src/header/inttypes/cbindgen.toml
+++ b/src/header/inttypes/cbindgen.toml
@@ -5,7 +5,12 @@
# - "wchar_t As described in <stddef.h>."
#
# wchar.h brings in both stdint.h and stddef.h
-sys_includes = ["wchar.h"]
+sys_includes = []
+after_includes = """
+#ifndef INTTYPES_H
+#include <wchar.h>
+#endif
+"""
include_guard = "_RELIBC_INTTYPES_H"
trailer = """
#ifndef _RELIBC_BITS_INTTYPES_H
@@ -0,0 +1,11 @@
--- a/src/header/mod.rs
+++ b/src/header/mod.rs
@@ -77,7 +77,7 @@
pub mod sgtty;
pub mod shadow;
pub mod signal;
-// TODO: spawn.h
+pub mod spawn;
// TODO: stdalign.h (likely C implementation)
// stdarg.h implemented in C
// stdatomic.h implemented in C
@@ -0,0 +1,75 @@
--- a/src/header/spawn/mod.rs
+++ b/src/header/spawn/mod.rs
@@ -4,6 +4,7 @@
error::{Errno, ResultExt},
header::{
errno::EINVAL,
+ bits_sigset_t::sigset_t,
unistd::{execve, fork, _exit},
},
platform::{self, types::{c_char, c_int, c_short, pid_t}},
@@ -26,7 +27,8 @@
pub struct posix_spawnattr_t {
pub flags: c_short,
pub pgroup: pid_t,
- _reserved: [u64; 8],
+ pub sd: sigset_t,
+ _reserved: [u64; 7],
}
#[unsafe(no_mangle)]
@@ -74,6 +76,54 @@
pub unsafe extern "C" fn posix_spawnattr_destroy(_attr: *mut posix_spawnattr_t) -> c_int { 0 }
#[unsafe(no_mangle)]
+pub unsafe extern "C" fn posix_spawnattr_setflags(
+ attr: *mut posix_spawnattr_t,
+ flags: c_short,
+) -> c_int {
+ if attr.is_null() {
+ return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
+ }
+ unsafe { (*attr).flags = flags; }
+ 0
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn posix_spawnattr_getflags(
+ attr: *const posix_spawnattr_t,
+ flags: *mut c_short,
+) -> c_int {
+ if attr.is_null() || flags.is_null() {
+ return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
+ }
+ unsafe { *flags = (*attr).flags; }
+ 0
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn posix_spawnattr_setsigmask(
+ attr: *mut posix_spawnattr_t,
+ sigmask: *const sigset_t,
+) -> c_int {
+ if attr.is_null() || sigmask.is_null() {
+ return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
+ }
+ unsafe { (*attr).sd = *sigmask; }
+ 0
+}
+
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn posix_spawnattr_getsigmask(
+ attr: *const posix_spawnattr_t,
+ sigmask: *mut sigset_t,
+) -> c_int {
+ if attr.is_null() || sigmask.is_null() {
+ return Err::<c_int, _>(Errno(EINVAL)).or_minus_one_errno();
+ }
+ unsafe { *sigmask = (*attr).sd; }
+ 0
+}
+
+#[unsafe(no_mangle)]
pub unsafe extern "C" fn posix_spawnp(
pid: *mut pid_t, file: *const c_char,
file_actions: *const posix_spawn_file_actions_t,
@@ -1,8 +1,8 @@
--- a/src/header/sys_types_internal/cbindgen.toml --- a/src/header/sys_types_internal/cbindgen.toml
+++ b/src/header/sys_types_internal/cbindgen.toml +++ b/src/header/sys_types_internal/cbindgen.toml
@@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
-sys_includes = ["stddef.h"] -sys_includes = ["stddef.h", "stdint.h"]
+sys_includes = ["stddef.h", "stdint.h"] +sys_includes = ["stddef.h"]
# TODO: figure out how to export void* type # TODO: figure out how to export void* type
after_includes = """ after_includes = """
@@ -0,0 +1,17 @@
--- a/src/header/wchar/cbindgen.toml
+++ b/src/header/wchar/cbindgen.toml
@@ -1,9 +1,12 @@
sys_includes = [
- "stdio.h",
- "time.h",
+ "stdarg.h",
"features.h",
]
after_includes = """
+// Forward declarations to avoid circular includes through stdio.h/time.h
+typedef struct FILE FILE;
+struct tm;
+
// int32_t, uint32_t, WCHAR_MIN, WCHAR_MAX
#include <stdint.h>
@@ -2,5 +2,10 @@
[source] [source]
git = "https://github.com/uutils/tar" git = "https://github.com/uutils/tar"
shallow_clone = true shallow_clone = true
patches = ["redox.patch"]
[build] [build]
template = "cargo" template = "cargo"
[package]
description = "GNU tar-compatible archive utility (Rust uutils implementation)"
@@ -0,0 +1,35 @@
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,7 @@
readme = "README.md"
keywords = ["tar", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
-rust-version = "1.94.0"
+rust-version = "1.92.0"
edition = "2024"
build = "build.rs"
@@ -48,7 +48,6 @@
textwrap = { version = "0.16.1", features = ["terminal_size"] }
thiserror = "2.0.3"
uucore = { git = "https://github.com/uutils/coreutils" }
-uutests = { git = "https://github.com/uutils/coreutils" }
xattr = "1.3.1"
zip = "8.0"
@@ -60,7 +59,6 @@
phf = { workspace = true }
textwrap = { workspace = true }
uucore = { workspace = true }
-uutests = { workspace = true }
zip = { workspace = true, optional = true }
tar = { optional = true, version = "0.0.1", package = "uu_tar", path = "src/uu/tar" }
@@ -74,7 +72,6 @@
tar-rs-crate = { version = "0.4", package = "tar" }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals"] }
-uutests = { workspace = true }
[target.'cfg(unix)'.dev-dependencies]
xattr = { workspace = true }
Submodule local/recipes/archives/uutils-tar/source added at 840b16bd8f
+91 -2
View File
@@ -7,13 +7,102 @@ template = "custom"
script = """ script = """
DYNAMIC_INIT DYNAMIC_INIT
export ac_cv_func___fseterr=yes export ac_cv_func___fseterr=yes
export ac_cv_type_sigset_t=yes
# Gnulib cross-compilation: relibc has standard POSIX headers and types
# but gnulib's configure can't run test programs during cross-compilation.
# Without these, gnulib generates broken #define fallbacks and replacement headers.
# Standard headers (gnulib can't detect these when cross-compiling)
export ac_cv_header_stdio_h=yes
export ac_cv_header_stdlib_h=yes
export ac_cv_header_string_h=yes
export ac_cv_header_strings_h=yes
export ac_cv_header_inttypes_h=yes
export ac_cv_header_stdint_h=yes
export ac_cv_header_unistd_h=yes
export ac_cv_header_sys_types_h=yes
export ac_cv_header_sys_stat_h=yes
export ac_cv_header_time_h=yes
export ac_cv_header_sys_time_h=yes
export ac_cv_header_sys_select_h=yes
export ac_cv_header_wchar_h=yes
export ac_cv_header_wctype_h=yes
export ac_cv_header_signal_h=yes
export ac_cv_header_dirent_h=yes
export ac_cv_header_fcntl_h=yes
export ac_cv_header_locale_h=yes
export ac_cv_header_errno_h=yes
export ac_cv_header_ctype_h=yes
export ac_cv_header_limits_h=yes
export ac_cv_header_stdarg_h=yes
export ac_cv_header_stddef_h=yes
export ac_cv_header_math_h=yes
export ac_cv_header_spawn_h=yes
export gl_cv_header_inttypes_h=yes
export gl_cv_header_stdint_h=yes
export gl_cv_header_inttypes_h_with_uintmax=yes
export ac_cv_have_inttypes_h_with_uintmax=yes
# Standard types (gnulib generates broken fallbacks without these)
export ac_cv_type_intmax_t=yes
export ac_cv_type_uintmax_t=yes
export ac_cv_type_gid_t=yes
export ac_cv_type_uid_t=yes
export ac_cv_type_pid_t=yes
export ac_cv_type_mode_t=yes
export ac_cv_type_off_t=yes
export ac_cv_type_size_t=yes
export ac_cv_type_ssize_t=yes
export ac_cv_type_ptrdiff_t=yes
export ac_cv_type_nlink_t=yes
export ac_cv_type_mbstate_t=yes
export gl_cv_type_intmax_t=yes
export gl_cv_type_ptrdiff_t_signed=yes
export gl_cv_header_inttypes_h_with_uintmax=yes
export ac_cv_have_inttypes_h_with_uintmax=yes
# Spawn functions (relibc provides these via the P3-spawn patch)
export gl_cv_func_spawn_posix_spawn=yes
export gl_cv_func_spawn_posix_spawnp=yes
export ac_cv_func_posix_spawn=yes
export ac_cv_func_posix_spawnp=yes
export ac_cv_func_posix_spawn_file_actions_init=yes
export ac_cv_func_posix_spawn_file_actions_destroy=yes
export ac_cv_func_posix_spawn_file_actions_addopen=yes
export ac_cv_func_posix_spawn_file_actions_addclose=yes
export ac_cv_func_posix_spawn_file_actions_adddup2=yes
export ac_cv_func_posix_spawnattr_init=yes
export ac_cv_func_posix_spawnattr_destroy=yes
export ac_cv_func_posix_spawnattr_setflags=yes
export ac_cv_func_posix_spawnattr_getflags=yes
export ac_cv_func_posix_spawnattr_setsigmask=yes
export ac_cv_func_posix_spawnattr_getsigmask=yes
# Other functions
export ac_cv_func_getpagesize=yes
export ac_cv_func_memcmp_working=yes
export ac_cv_func_mmap_fixed_mapped=yes
# Spawn types
export ac_cv_type_posix_spawnattr_t=yes export ac_cv_type_posix_spawnattr_t=yes
export ac_cv_type_posix_spawn_file_actions_t=yes export ac_cv_type_posix_spawn_file_actions_t=yes
COOKBOOK_CONFIGURE_FLAGS+=( COOKBOOK_CONFIGURE_FLAGS+=(
--disable-nls --disable-nls
) )
cookbook_configure
# Cross-compilation fix: run configure manually, then patch the
# generated Makefile to use host bison instead of the cross-compiled
# wrapper. The generated Makefile hardcodes
# BISON = $(top_builddir)/tests/bison
# which wraps the x86_64-unknown-redox binary — unrunnable on the host.
"${COOKBOOK_CONFIGURE}" "${COOKBOOK_CONFIGURE_FLAGS[@]}"
sed -i 's|^BISON = .*|BISON = /usr/bin/bison|' "${COOKBOOK_BUILD}/Makefile"
# Fix gnulib cross-compilation misdetections in config.h
"${COOKBOOK_ROOT}/local/scripts/gnulib-cross-fix.sh" "${COOKBOOK_BUILD}/lib/config.h"
"${COOKBOOK_MAKE}" -j "${COOKBOOK_MAKE_JOBS}"
"${COOKBOOK_MAKE}" install DESTDIR="${COOKBOOK_STAGE}"
""" """
[package] [package]
@@ -0,0 +1,178 @@
# This file was generated by Autom4te 2.73.
# It contains the lists of macros which have been traced.
# It can be safely removed.
@request = (
bless( [
'0',
1,
[
'/usr/share/autoconf'
],
[
'/usr/share/autoconf/autoconf/autoconf.m4f',
'aclocal.m4',
'/usr/share/autoconf/autoconf/trailer.m4',
'configure.ac'
],
{
'AC_CANONICAL_BUILD' => 1,
'AC_CANONICAL_HOST' => 1,
'AC_CANONICAL_SYSTEM' => 1,
'AC_CANONICAL_TARGET' => 1,
'AC_CONFIG_AUX_DIR' => 1,
'AC_CONFIG_FILES' => 1,
'AC_CONFIG_HEADERS' => 1,
'AC_CONFIG_LIBOBJ_DIR' => 1,
'AC_CONFIG_LINKS' => 1,
'AC_CONFIG_MACRO_DIR' => 1,
'AC_CONFIG_MACRO_DIR_TRACE' => 1,
'AC_CONFIG_SUBDIRS' => 1,
'AC_DEFINE_TRACE_LITERAL' => 1,
'AC_FC_FREEFORM' => 1,
'AC_FC_PP_DEFINE' => 1,
'AC_FC_PP_SRCEXT' => 1,
'AC_FC_SRCEXT' => 1,
'AC_INIT' => 1,
'AC_LIBSOURCE' => 1,
'AC_LIB_HAVE_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS_FROM_LIBS' => 1,
'AC_PROG_LIBTOOL' => 1,
'AC_REQUIRE_AUX_FILE' => 1,
'AC_SUBST' => 1,
'AC_SUBST_TRACE' => 1,
'AH_OUTPUT' => 1,
'AM_AUTOMAKE_VERSION' => 1,
'AM_CONDITIONAL' => 1,
'AM_ENABLE_MULTILIB' => 1,
'AM_EXTRA_RECURSIVE_TARGETS' => 1,
'AM_GNU_GETTEXT' => 1,
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
'AM_GNU_GETTEXT_REQUIRE_VERSION' => 1,
'AM_GNU_GETTEXT_VERSION' => 1,
'AM_ICONV' => 1,
'AM_INIT_AUTOMAKE' => 1,
'AM_MAINTAINER_MODE' => 1,
'AM_MAKEFILE_INCLUDE' => 1,
'AM_NLS' => 1,
'AM_PATH_GUILE' => 1,
'AM_POT_TOOLS' => 1,
'AM_PROG_AR' => 1,
'AM_PROG_CC_C_O' => 1,
'AM_PROG_CXX_C_O' => 1,
'AM_PROG_F77_C_O' => 1,
'AM_PROG_FC_C_O' => 1,
'AM_PROG_LIBTOOL' => 1,
'AM_PROG_MKDIR_P' => 1,
'AM_PROG_MOC' => 1,
'AM_SILENT_RULES' => 1,
'AM_XGETTEXT_OPTION' => 1,
'GTK_DOC_CHECK' => 1,
'GUILE_FLAGS' => 1,
'IT_PROG_INTLTOOL' => 1,
'LT_CONFIG_LTDL_DIR' => 1,
'LT_INIT' => 1,
'LT_SUPPORTED_TAG' => 1,
'_AM_COND_ELSE' => 1,
'_AM_COND_ENDIF' => 1,
'_AM_COND_IF' => 1,
'_AM_MAKEFILE_INCLUDE' => 1,
'_AM_SUBST_NOTMAKE' => 1,
'_LT_AC_TAGCONFIG' => 1,
'_m4_warn' => 1,
'include' => 1,
'm4_include' => 1,
'm4_pattern_allow' => 1,
'm4_pattern_forbid' => 1,
'm4_sinclude' => 1,
'sinclude' => 1
}
], 'Autom4te::Request' ),
bless( [
'1',
1,
[
'/usr/share/autoconf'
],
[
'/usr/share/autoconf/autoconf/autoconf.m4f',
'aclocal.m4',
'configure.ac'
],
{
'AC_CANONICAL_BUILD' => 1,
'AC_CANONICAL_HOST' => 1,
'AC_CANONICAL_SYSTEM' => 1,
'AC_CANONICAL_TARGET' => 1,
'AC_CONFIG_AUX_DIR' => 1,
'AC_CONFIG_FILES' => 1,
'AC_CONFIG_HEADERS' => 1,
'AC_CONFIG_LIBOBJ_DIR' => 1,
'AC_CONFIG_LINKS' => 1,
'AC_CONFIG_MACRO_DIR' => 1,
'AC_CONFIG_MACRO_DIR_TRACE' => 1,
'AC_CONFIG_SUBDIRS' => 1,
'AC_DEFINE_TRACE_LITERAL' => 1,
'AC_FC_FREEFORM' => 1,
'AC_FC_PP_DEFINE' => 1,
'AC_FC_PP_SRCEXT' => 1,
'AC_FC_SRCEXT' => 1,
'AC_INIT' => 1,
'AC_LIBSOURCE' => 1,
'AC_LIB_HAVE_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS_FROM_LIBS' => 1,
'AC_PROG_LIBTOOL' => 1,
'AC_REQUIRE_AUX_FILE' => 1,
'AC_SUBST' => 1,
'AC_SUBST_TRACE' => 1,
'AH_OUTPUT' => 1,
'AM_AUTOMAKE_VERSION' => 1,
'AM_CONDITIONAL' => 1,
'AM_ENABLE_MULTILIB' => 1,
'AM_EXTRA_RECURSIVE_TARGETS' => 1,
'AM_GNU_GETTEXT' => 1,
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
'AM_GNU_GETTEXT_REQUIRE_VERSION' => 1,
'AM_GNU_GETTEXT_VERSION' => 1,
'AM_ICONV' => 1,
'AM_INIT_AUTOMAKE' => 1,
'AM_MAINTAINER_MODE' => 1,
'AM_MAKEFILE_INCLUDE' => 1,
'AM_NLS' => 1,
'AM_PATH_GUILE' => 1,
'AM_POT_TOOLS' => 1,
'AM_PROG_AR' => 1,
'AM_PROG_CC_C_O' => 1,
'AM_PROG_CXX_C_O' => 1,
'AM_PROG_F77_C_O' => 1,
'AM_PROG_FC_C_O' => 1,
'AM_PROG_LIBTOOL' => 1,
'AM_PROG_MKDIR_P' => 1,
'AM_PROG_MOC' => 1,
'AM_SILENT_RULES' => 1,
'AM_XGETTEXT_OPTION' => 1,
'GTK_DOC_CHECK' => 1,
'GUILE_FLAGS' => 1,
'IT_PROG_INTLTOOL' => 1,
'LT_CONFIG_LTDL_DIR' => 1,
'LT_INIT' => 1,
'LT_SUPPORTED_TAG' => 1,
'_AM_COND_ELSE' => 1,
'_AM_COND_ENDIF' => 1,
'_AM_COND_IF' => 1,
'_AM_MAKEFILE_INCLUDE' => 1,
'_AM_SUBST_NOTMAKE' => 1,
'_LT_AC_TAGCONFIG' => 1,
'_m4_warn' => 1,
'include' => 1,
'm4_include' => 1,
'm4_pattern_allow' => 1,
'm4_pattern_forbid' => 1,
'm4_sinclude' => 1,
'sinclude' => 1
}
], 'Autom4te::Request' )
);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7587 -6302
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,4 +1,4 @@
@set UPDATED 12 September 2021 @set UPDATED 15 May 2026
@set UPDATED-MONTH September 2021 @set UPDATED-MONTH May 2026
@set EDITION 3.8.2 @set EDITION 3.8.2
@set VERSION 3.8.2 @set VERSION 3.8.2
@@ -1,4 +1,4 @@
@set UPDATED 12 September 2021 @set UPDATED 15 May 2026
@set UPDATED-MONTH September 2021 @set UPDATED-MONTH May 2026
@set EDITION 3.8.2 @set EDITION 3.8.2
@set VERSION 3.8.2 @set VERSION 3.8.2
+175 -167
View File
@@ -585,10 +585,10 @@
/* Define to 1 if you have the <bp-sym.h> header file. */ /* Define to 1 if you have the <bp-sym.h> header file. */
#undef HAVE_BP_SYM_H #undef HAVE_BP_SYM_H
/* Define to 1 if you have the `canonicalize_file_name' function. */ /* Define to 1 if you have the 'canonicalize_file_name' function. */
#undef HAVE_CANONICALIZE_FILE_NAME #undef HAVE_CANONICALIZE_FILE_NAME
/* Define to 1 if you have the `catgets' function. */ /* Define to 1 if you have the 'catgets' function. */
#undef HAVE_CATGETS #undef HAVE_CATGETS
/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the /* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the
@@ -599,16 +599,16 @@
the CoreFoundation framework. */ the CoreFoundation framework. */
#undef HAVE_CFPREFERENCESCOPYAPPVALUE #undef HAVE_CFPREFERENCESCOPYAPPVALUE
/* Define to 1 if you have the `clock_gettime' function. */ /* Define to 1 if you have the 'clock_gettime' function. */
#undef HAVE_CLOCK_GETTIME #undef HAVE_CLOCK_GETTIME
/* Define to 1 if you have the `clock_settime' function. */ /* Define to 1 if you have the 'clock_settime' function. */
#undef HAVE_CLOCK_SETTIME #undef HAVE_CLOCK_SETTIME
/* Define to 1 if you have the `closedir' function. */ /* Define to 1 if you have the 'closedir' function. */
#undef HAVE_CLOSEDIR #undef HAVE_CLOSEDIR
/* Define to 1 if you have the `confstr' function. */ /* Define to 1 if you have the 'confstr' function. */
#undef HAVE_CONFSTR #undef HAVE_CONFSTR
/* Define if the copysignf function is declared in <math.h> and available in /* Define if the copysignf function is declared in <math.h> and available in
@@ -623,7 +623,7 @@
libc. */ libc. */
#undef HAVE_COPYSIGN_IN_LIBC #undef HAVE_COPYSIGN_IN_LIBC
/* Define to 1 if you have the `copy_file_range' function. */ /* Define to 1 if you have the 'copy_file_range' function. */
#undef HAVE_COPY_FILE_RANGE #undef HAVE_COPY_FILE_RANGE
/* Define to 1 if you have the <crtdefs.h> header file. */ /* Define to 1 if you have the <crtdefs.h> header file. */
@@ -633,127 +633,127 @@
*/ */
#undef HAVE_DCGETTEXT #undef HAVE_DCGETTEXT
/* Define to 1 if you have the declaration of `alarm', and to 0 if you don't. /* Define to 1 if you have the declaration of 'alarm', and to 0 if you don't.
*/ */
#undef HAVE_DECL_ALARM #undef HAVE_DECL_ALARM
/* Define to 1 if you have the declaration of `clearerr_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'clearerr_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_CLEARERR_UNLOCKED #undef HAVE_DECL_CLEARERR_UNLOCKED
/* Define to 1 if you have the declaration of `copysign', and to 0 if you /* Define to 1 if you have the declaration of 'copysign', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_COPYSIGN #undef HAVE_DECL_COPYSIGN
/* Define to 1 if you have the declaration of `copysignf', and to 0 if you /* Define to 1 if you have the declaration of 'copysignf', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_COPYSIGNF #undef HAVE_DECL_COPYSIGNF
/* Define to 1 if you have the declaration of `copysignl', and to 0 if you /* Define to 1 if you have the declaration of 'copysignl', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_COPYSIGNL #undef HAVE_DECL_COPYSIGNL
/* Define to 1 if you have the declaration of `dirfd', and to 0 if you don't. /* Define to 1 if you have the declaration of 'dirfd', and to 0 if you don't.
*/ */
#undef HAVE_DECL_DIRFD #undef HAVE_DECL_DIRFD
/* Define to 1 if you have the declaration of `ecvt', and to 0 if you don't. /* Define to 1 if you have the declaration of 'ecvt', and to 0 if you don't.
*/ */
#undef HAVE_DECL_ECVT #undef HAVE_DECL_ECVT
/* Define to 1 if you have the declaration of `execvpe', and to 0 if you /* Define to 1 if you have the declaration of 'execvpe', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_EXECVPE #undef HAVE_DECL_EXECVPE
/* Define to 1 if you have the declaration of `fchdir', and to 0 if you don't. /* Define to 1 if you have the declaration of 'fchdir', and to 0 if you don't.
*/ */
#undef HAVE_DECL_FCHDIR #undef HAVE_DECL_FCHDIR
/* Define to 1 if you have the declaration of `fcloseall', and to 0 if you /* Define to 1 if you have the declaration of 'fcloseall', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_FCLOSEALL #undef HAVE_DECL_FCLOSEALL
/* Define to 1 if you have the declaration of `fcvt', and to 0 if you don't. /* Define to 1 if you have the declaration of 'fcvt', and to 0 if you don't.
*/ */
#undef HAVE_DECL_FCVT #undef HAVE_DECL_FCVT
/* Define to 1 if you have the declaration of `fdopendir', and to 0 if you /* Define to 1 if you have the declaration of 'fdopendir', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_FDOPENDIR #undef HAVE_DECL_FDOPENDIR
/* Define to 1 if you have the declaration of `feof_unlocked', and to 0 if you /* Define to 1 if you have the declaration of 'feof_unlocked', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_FEOF_UNLOCKED #undef HAVE_DECL_FEOF_UNLOCKED
/* Define to 1 if you have the declaration of `ferror_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'ferror_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FERROR_UNLOCKED #undef HAVE_DECL_FERROR_UNLOCKED
/* Define to 1 if you have the declaration of `fflush_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fflush_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FFLUSH_UNLOCKED #undef HAVE_DECL_FFLUSH_UNLOCKED
/* Define to 1 if you have the declaration of `fgets_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fgets_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FGETS_UNLOCKED #undef HAVE_DECL_FGETS_UNLOCKED
/* Define to 1 if you have the declaration of `fputc_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fputc_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FPUTC_UNLOCKED #undef HAVE_DECL_FPUTC_UNLOCKED
/* Define to 1 if you have the declaration of `fputs_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fputs_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FPUTS_UNLOCKED #undef HAVE_DECL_FPUTS_UNLOCKED
/* Define to 1 if you have the declaration of `fread_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fread_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FREAD_UNLOCKED #undef HAVE_DECL_FREAD_UNLOCKED
/* Define to 1 if you have the declaration of `fwrite_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'fwrite_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_FWRITE_UNLOCKED #undef HAVE_DECL_FWRITE_UNLOCKED
/* Define to 1 if you have the declaration of `gcvt', and to 0 if you don't. /* Define to 1 if you have the declaration of 'gcvt', and to 0 if you don't.
*/ */
#undef HAVE_DECL_GCVT #undef HAVE_DECL_GCVT
/* Define to 1 if you have the declaration of `getchar_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'getchar_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_GETCHAR_UNLOCKED #undef HAVE_DECL_GETCHAR_UNLOCKED
/* Define to 1 if you have the declaration of `getcwd', and to 0 if you don't. /* Define to 1 if you have the declaration of 'getcwd', and to 0 if you don't.
*/ */
#undef HAVE_DECL_GETCWD #undef HAVE_DECL_GETCWD
/* Define to 1 if you have the declaration of `getc_unlocked', and to 0 if you /* Define to 1 if you have the declaration of 'getc_unlocked', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_GETC_UNLOCKED #undef HAVE_DECL_GETC_UNLOCKED
/* Define to 1 if you have the declaration of `getdelim', and to 0 if you /* Define to 1 if you have the declaration of 'getdelim', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_GETDELIM #undef HAVE_DECL_GETDELIM
/* Define to 1 if you have the declaration of `getdtablesize', and to 0 if you /* Define to 1 if you have the declaration of 'getdtablesize', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_GETDTABLESIZE #undef HAVE_DECL_GETDTABLESIZE
/* Define to 1 if you have the declaration of `gethrtime', and to 0 if you /* Define to 1 if you have the declaration of 'gethrtime', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_GETHRTIME #undef HAVE_DECL_GETHRTIME
/* Define to 1 if you have the declaration of `getline', and to 0 if you /* Define to 1 if you have the declaration of 'getline', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_GETLINE #undef HAVE_DECL_GETLINE
/* Define to 1 if you have the declaration of `iswblank', and to 0 if you /* Define to 1 if you have the declaration of 'iswblank', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_ISWBLANK #undef HAVE_DECL_ISWBLANK
/* Define to 1 if you have the declaration of `mbrtowc', and to 0 if you /* Define to 1 if you have the declaration of 'mbrtowc', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_MBRTOWC #undef HAVE_DECL_MBRTOWC
/* Define to 1 if you have the declaration of `mbsinit', and to 0 if you /* Define to 1 if you have the declaration of 'mbsinit', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_MBSINIT #undef HAVE_DECL_MBSINIT
@@ -761,122 +761,122 @@
otherwise. */ otherwise. */
#undef HAVE_DECL_MBSWIDTH_IN_WCHAR_H #undef HAVE_DECL_MBSWIDTH_IN_WCHAR_H
/* Define to 1 if you have the declaration of `memrchr', and to 0 if you /* Define to 1 if you have the declaration of 'memrchr', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_MEMRCHR #undef HAVE_DECL_MEMRCHR
/* Define to 1 if you have the declaration of `obstack_printf', and to 0 if /* Define to 1 if you have the declaration of 'obstack_printf', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_OBSTACK_PRINTF #undef HAVE_DECL_OBSTACK_PRINTF
/* Define to 1 if you have the declaration of `posix_spawn', and to 0 if you /* Define to 1 if you have the declaration of 'posix_spawn', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_POSIX_SPAWN #undef HAVE_DECL_POSIX_SPAWN
/* Define to 1 if you have the declaration of `program_invocation_name', and /* Define to 1 if you have the declaration of 'program_invocation_name', and
to 0 if you don't. */ to 0 if you don't. */
#undef HAVE_DECL_PROGRAM_INVOCATION_NAME #undef HAVE_DECL_PROGRAM_INVOCATION_NAME
/* Define to 1 if you have the declaration of `program_invocation_short_name', /* Define to 1 if you have the declaration of 'program_invocation_short_name',
and to 0 if you don't. */ and to 0 if you don't. */
#undef HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME #undef HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME
/* Define to 1 if you have the declaration of `putchar_unlocked', and to 0 if /* Define to 1 if you have the declaration of 'putchar_unlocked', and to 0 if
you don't. */ you don't. */
#undef HAVE_DECL_PUTCHAR_UNLOCKED #undef HAVE_DECL_PUTCHAR_UNLOCKED
/* Define to 1 if you have the declaration of `putc_unlocked', and to 0 if you /* Define to 1 if you have the declaration of 'putc_unlocked', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_PUTC_UNLOCKED #undef HAVE_DECL_PUTC_UNLOCKED
/* Define to 1 if you have the declaration of `setenv', and to 0 if you don't. /* Define to 1 if you have the declaration of 'setenv', and to 0 if you don't.
*/ */
#undef HAVE_DECL_SETENV #undef HAVE_DECL_SETENV
/* Define to 1 if you have the declaration of `snprintf', and to 0 if you /* Define to 1 if you have the declaration of 'snprintf', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_SNPRINTF #undef HAVE_DECL_SNPRINTF
/* Define to 1 if you have the declaration of `stpncpy', and to 0 if you /* Define to 1 if you have the declaration of 'stpncpy', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_STPNCPY #undef HAVE_DECL_STPNCPY
/* Define to 1 if you have the declaration of `strdup', and to 0 if you don't. /* Define to 1 if you have the declaration of 'strdup', and to 0 if you don't.
*/ */
#undef HAVE_DECL_STRDUP #undef HAVE_DECL_STRDUP
/* Define to 1 if you have the declaration of `strerror_r', and to 0 if you /* Define to 1 if you have the declaration of 'strerror_r', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_STRERROR_R #undef HAVE_DECL_STRERROR_R
/* Define to 1 if you have the declaration of `strndup', and to 0 if you /* Define to 1 if you have the declaration of 'strndup', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_STRNDUP #undef HAVE_DECL_STRNDUP
/* Define to 1 if you have the declaration of `strnlen', and to 0 if you /* Define to 1 if you have the declaration of 'strnlen', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_STRNLEN #undef HAVE_DECL_STRNLEN
/* Define to 1 if you have the declaration of `towlower', and to 0 if you /* Define to 1 if you have the declaration of 'towlower', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_TOWLOWER #undef HAVE_DECL_TOWLOWER
/* Define to 1 if you have the declaration of `unsetenv', and to 0 if you /* Define to 1 if you have the declaration of 'unsetenv', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_UNSETENV #undef HAVE_DECL_UNSETENV
/* Define to 1 if you have the declaration of `vsnprintf', and to 0 if you /* Define to 1 if you have the declaration of 'vsnprintf', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_VSNPRINTF #undef HAVE_DECL_VSNPRINTF
/* Define to 1 if you have the declaration of `wcsdup', and to 0 if you don't. /* Define to 1 if you have the declaration of 'wcsdup', and to 0 if you don't.
*/ */
#undef HAVE_DECL_WCSDUP #undef HAVE_DECL_WCSDUP
/* Define to 1 if you have the declaration of `wcwidth', and to 0 if you /* Define to 1 if you have the declaration of 'wcwidth', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL_WCWIDTH #undef HAVE_DECL_WCWIDTH
/* Define to 1 if you have the declaration of `_snprintf', and to 0 if you /* Define to 1 if you have the declaration of '_snprintf', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL__SNPRINTF #undef HAVE_DECL__SNPRINTF
/* Define to 1 if you have the declaration of `__argv', and to 0 if you don't. /* Define to 1 if you have the declaration of '__argv', and to 0 if you don't.
*/ */
#undef HAVE_DECL___ARGV #undef HAVE_DECL___ARGV
/* Define to 1 if you have the declaration of `__fpending', and to 0 if you /* Define to 1 if you have the declaration of '__fpending', and to 0 if you
don't. */ don't. */
#undef HAVE_DECL___FPENDING #undef HAVE_DECL___FPENDING
/* Define to 1 if you have the <dirent.h> header file. */ /* Define to 1 if you have the <dirent.h> header file. */
#undef HAVE_DIRENT_H #undef HAVE_DIRENT_H
/* Define to 1 if you have the `dirfd' function. */ /* Define to 1 if you have the 'dirfd' function. */
#undef HAVE_DIRFD #undef HAVE_DIRFD
/* Define if you have the declaration of environ. */ /* Define if you have the declaration of environ. */
#undef HAVE_ENVIRON_DECL #undef HAVE_ENVIRON_DECL
/* Define to 1 if you have the `faccessat' function. */ /* Define to 1 if you have the 'faccessat' function. */
#undef HAVE_FACCESSAT #undef HAVE_FACCESSAT
/* Define to 1 if you have the `fchdir' function. */ /* Define to 1 if you have the 'fchdir' function. */
#undef HAVE_FCHDIR #undef HAVE_FCHDIR
/* Define to 1 if you have the `fcntl' function. */ /* Define to 1 if you have the 'fcntl' function. */
#undef HAVE_FCNTL #undef HAVE_FCNTL
/* Define to 1 if you have the `fdopendir' function. */ /* Define to 1 if you have the 'fdopendir' function. */
#undef HAVE_FDOPENDIR #undef HAVE_FDOPENDIR
/* Define to 1 if you have the <features.h> header file. */ /* Define to 1 if you have the <features.h> header file. */
#undef HAVE_FEATURES_H #undef HAVE_FEATURES_H
/* Define to 1 if you have the `ffsl' function. */ /* Define to 1 if you have the 'ffsl' function. */
#undef HAVE_FFSL #undef HAVE_FFSL
/* Define to 1 if you have the `flockfile' function. */ /* Define to 1 if you have the 'flockfile' function. */
#undef HAVE_FLOCKFILE #undef HAVE_FLOCKFILE
/* Define if the 'free' function is guaranteed to preserve errno. */ /* Define if the 'free' function is guaranteed to preserve errno. */
@@ -888,50 +888,50 @@
/* Define if the frexp function is available in libc. */ /* Define if the frexp function is available in libc. */
#undef HAVE_FREXP_IN_LIBC #undef HAVE_FREXP_IN_LIBC
/* Define to 1 if you have the `fstatat' function. */ /* Define to 1 if you have the 'fstatat' function. */
#undef HAVE_FSTATAT #undef HAVE_FSTATAT
/* Define to 1 if you have the `fsync' function. */ /* Define to 1 if you have the 'fsync' function. */
#undef HAVE_FSYNC #undef HAVE_FSYNC
/* Define to 1 if you have the `funlockfile' function. */ /* Define to 1 if you have the 'funlockfile' function. */
#undef HAVE_FUNLOCKFILE #undef HAVE_FUNLOCKFILE
/* Define to 1 if you have the `getcwd' function. */ /* Define to 1 if you have the 'getcwd' function. */
#undef HAVE_GETCWD #undef HAVE_GETCWD
/* Define to 1 if getcwd works, but with shorter paths than is generally /* Define to 1 if getcwd works, but with shorter paths than is generally
tested with the replacement. */ tested with the replacement. */
#undef HAVE_GETCWD_SHORTER #undef HAVE_GETCWD_SHORTER
/* Define to 1 if you have the `getdelim' function. */ /* Define to 1 if you have the 'getdelim' function. */
#undef HAVE_GETDELIM #undef HAVE_GETDELIM
/* Define to 1 if you have the `getdtablesize' function. */ /* Define to 1 if you have the 'getdtablesize' function. */
#undef HAVE_GETDTABLESIZE #undef HAVE_GETDTABLESIZE
/* Define to 1 if you have the `getexecname' function. */ /* Define to 1 if you have the 'getexecname' function. */
#undef HAVE_GETEXECNAME #undef HAVE_GETEXECNAME
/* Define to 1 if you have the <getopt.h> header file. */ /* Define to 1 if you have the <getopt.h> header file. */
#undef HAVE_GETOPT_H #undef HAVE_GETOPT_H
/* Define to 1 if you have the `getopt_long_only' function. */ /* Define to 1 if you have the 'getopt_long_only' function. */
#undef HAVE_GETOPT_LONG_ONLY #undef HAVE_GETOPT_LONG_ONLY
/* Define to 1 if the system has the 'getpagesize' function. */ /* Define to 1 if the system has the 'getpagesize' function. */
#undef HAVE_GETPAGESIZE #undef HAVE_GETPAGESIZE
/* Define to 1 if you have the `getprogname' function. */ /* Define to 1 if you have the 'getprogname' function. */
#undef HAVE_GETPROGNAME #undef HAVE_GETPROGNAME
/* Define to 1 if you have the `getrusage' function. */ /* Define to 1 if you have the 'getrusage' function. */
#undef HAVE_GETRUSAGE #undef HAVE_GETRUSAGE
/* Define if the GNU gettext() function is already present or preinstalled. */ /* Define if the GNU gettext() function is already present or preinstalled. */
#undef HAVE_GETTEXT #undef HAVE_GETTEXT
/* Define to 1 if you have the `gettimeofday' function. */ /* Define to 1 if you have the 'gettimeofday' function. */
#undef HAVE_GETTIMEOFDAY #undef HAVE_GETTIMEOFDAY
/* Define if you have the iconv() function and it works. */ /* Define if you have the iconv() function and it works. */
@@ -955,7 +955,7 @@
declares uintmax_t. */ declares uintmax_t. */
#undef HAVE_INTTYPES_H_WITH_UINTMAX #undef HAVE_INTTYPES_H_WITH_UINTMAX
/* Define to 1 if you have the `isascii' function. */ /* Define to 1 if you have the 'isascii' function. */
#undef HAVE_ISASCII #undef HAVE_ISASCII
/* Define if the isnan(double) function is available in libc. */ /* Define if the isnan(double) function is available in libc. */
@@ -967,10 +967,10 @@
/* Define if the isnan(long double) function is available in libc. */ /* Define if the isnan(long double) function is available in libc. */
#undef HAVE_ISNANL_IN_LIBC #undef HAVE_ISNANL_IN_LIBC
/* Define to 1 if you have the `iswblank' function. */ /* Define to 1 if you have the 'iswblank' function. */
#undef HAVE_ISWBLANK #undef HAVE_ISWBLANK
/* Define to 1 if you have the `iswcntrl' function. */ /* Define to 1 if you have the 'iswcntrl' function. */
#undef HAVE_ISWCNTRL #undef HAVE_ISWCNTRL
/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */ /* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
@@ -991,7 +991,7 @@
/* Define to 1 if you have the <limits.h> header file. */ /* Define to 1 if you have the <limits.h> header file. */
#undef HAVE_LIMITS_H #undef HAVE_LIMITS_H
/* Define to 1 if you have the `link' function. */ /* Define to 1 if you have the 'link' function. */
#undef HAVE_LINK #undef HAVE_LINK
/* Define to 1 if you have the <locale.h> header file. */ /* Define to 1 if you have the <locale.h> header file. */
@@ -1000,7 +1000,7 @@
/* Define to 1 if the system has the type 'long long int'. */ /* Define to 1 if the system has the type 'long long int'. */
#undef HAVE_LONG_LONG_INT #undef HAVE_LONG_LONG_INT
/* Define to 1 if you have the `lstat' function. */ /* Define to 1 if you have the 'lstat' function. */
#undef HAVE_LSTAT #undef HAVE_LSTAT
/* Define to 1 if you have the <mach-o/dyld.h> header file. */ /* Define to 1 if you have the <mach-o/dyld.h> header file. */
@@ -1016,22 +1016,22 @@
/* Define to 1 if you have the <math.h> header file. */ /* Define to 1 if you have the <math.h> header file. */
#undef HAVE_MATH_H #undef HAVE_MATH_H
/* Define to 1 if you have the `mbrtowc' function. */ /* Define to 1 if you have the 'mbrtowc' function. */
#undef HAVE_MBRTOWC #undef HAVE_MBRTOWC
/* Define to 1 if you have the `mbsinit' function. */ /* Define to 1 if you have the 'mbsinit' function. */
#undef HAVE_MBSINIT #undef HAVE_MBSINIT
/* Define to 1 if <wchar.h> declares mbstate_t. */ /* Define to 1 if <wchar.h> declares mbstate_t. */
#undef HAVE_MBSTATE_T #undef HAVE_MBSTATE_T
/* Define to 1 if you have the `mempcpy' function. */ /* Define to 1 if you have the 'mempcpy' function. */
#undef HAVE_MEMPCPY #undef HAVE_MEMPCPY
/* Define to 1 if you have the `memrchr' function. */ /* Define to 1 if you have the 'memrchr' function. */
#undef HAVE_MEMRCHR #undef HAVE_MEMRCHR
/* Define to 1 if you have the `microuptime' function. */ /* Define to 1 if you have the 'microuptime' function. */
#undef HAVE_MICROUPTIME #undef HAVE_MICROUPTIME
/* Define to 1 if getcwd minimally works, that is, its result can be trusted /* Define to 1 if getcwd minimally works, that is, its result can be trusted
@@ -1047,29 +1047,29 @@
/* Define to 1 if <sys/param.h> defines the MIN and MAX macros. */ /* Define to 1 if <sys/param.h> defines the MIN and MAX macros. */
#undef HAVE_MINMAX_IN_SYS_PARAM_H #undef HAVE_MINMAX_IN_SYS_PARAM_H
/* Define to 1 if you have the `mprotect' function. */ /* Define to 1 if you have the 'mprotect' function. */
#undef HAVE_MPROTECT #undef HAVE_MPROTECT
/* Define to 1 on MSVC platforms that have the "invalid parameter handler" /* Define to 1 on MSVC platforms that have the "invalid parameter handler"
concept. */ concept. */
#undef HAVE_MSVC_INVALID_PARAMETER_HANDLER #undef HAVE_MSVC_INVALID_PARAMETER_HANDLER
/* Define to 1 if you have the `nanouptime' function. */ /* Define to 1 if you have the 'nanouptime' function. */
#undef HAVE_NANOUPTIME #undef HAVE_NANOUPTIME
/* Define to 1 if you have the `nl_langinfo' function. */ /* Define to 1 if you have the 'nl_langinfo' function. */
#undef HAVE_NL_LANGINFO #undef HAVE_NL_LANGINFO
/* Define to 1 if the system has obstacks that work with any size object. */ /* Define to 1 if the system has obstacks that work with any size object. */
#undef HAVE_OBSTACK #undef HAVE_OBSTACK
/* Define to 1 if you have the `obstack_printf' function. */ /* Define to 1 if you have the 'obstack_printf' function. */
#undef HAVE_OBSTACK_PRINTF #undef HAVE_OBSTACK_PRINTF
/* Define to 1 if you have the `openat' function. */ /* Define to 1 if you have the 'openat' function. */
#undef HAVE_OPENAT #undef HAVE_OPENAT
/* Define to 1 if you have the `opendir' function. */ /* Define to 1 if you have the 'opendir' function. */
#undef HAVE_OPENDIR #undef HAVE_OPENDIR
/* Define to 1 if getcwd works, except it sometimes fails when it shouldn't, /* Define to 1 if getcwd works, except it sometimes fails when it shouldn't,
@@ -1079,27 +1079,27 @@
/* Define to 1 if you have the <paths.h> header file. */ /* Define to 1 if you have the <paths.h> header file. */
#undef HAVE_PATHS_H #undef HAVE_PATHS_H
/* Define to 1 if you have the `pipe' function. */ /* Define to 1 if you have the 'pipe' function. */
#undef HAVE_PIPE #undef HAVE_PIPE
/* Define to 1 if you have the `pipe2' function. */ /* Define to 1 if you have the 'pipe2' function. */
#undef HAVE_PIPE2 #undef HAVE_PIPE2
/* Define to 1 if you have the `posix_spawn' function. */ /* Define to 1 if you have the 'posix_spawn' function. */
#undef HAVE_POSIX_SPAWN #undef HAVE_POSIX_SPAWN
/* Define to 1 if the system has the type `posix_spawnattr_t'. */ /* Define to 1 if the system has the type 'posix_spawnattr_t'. */
#undef HAVE_POSIX_SPAWNATTR_T #undef HAVE_POSIX_SPAWNATTR_T
/* Define to 1 if you have the `posix_spawn_file_actions_addchdir' function. /* Define to 1 if you have the 'posix_spawn_file_actions_addchdir' function.
*/ */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR #undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR
/* Define to 1 if you have the `posix_spawn_file_actions_addchdir_np' /* Define to 1 if you have the 'posix_spawn_file_actions_addchdir_np'
function. */ function. */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP #undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
/* Define to 1 if the system has the type `posix_spawn_file_actions_t'. */ /* Define to 1 if the system has the type 'posix_spawn_file_actions_t'. */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_T #undef HAVE_POSIX_SPAWN_FILE_ACTIONS_T
/* Define if you have the <pthread.h> header and the POSIX threads API. */ /* Define if you have the <pthread.h> header and the POSIX threads API. */
@@ -1115,13 +1115,13 @@
reader. */ reader. */
#undef HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER #undef HAVE_PTHREAD_RWLOCK_RDLOCK_PREFER_WRITER
/* Define to 1 if you have the `raise' function. */ /* Define to 1 if you have the 'raise' function. */
#undef HAVE_RAISE #undef HAVE_RAISE
/* Define to 1 if you have the `rawmemchr' function. */ /* Define to 1 if you have the 'rawmemchr' function. */
#undef HAVE_RAWMEMCHR #undef HAVE_RAWMEMCHR
/* Define to 1 if you have the `readdir' function. */ /* Define to 1 if you have the 'readdir' function. */
#undef HAVE_READDIR #undef HAVE_READDIR
/* Define if you have the readline library. */ /* Define if you have the readline library. */
@@ -1133,19 +1133,19 @@
/* Define to 1 if you have the <readline/readline.h> header file. */ /* Define to 1 if you have the <readline/readline.h> header file. */
#undef HAVE_READLINE_READLINE_H #undef HAVE_READLINE_READLINE_H
/* Define to 1 if you have the `readlink' function. */ /* Define to 1 if you have the 'readlink' function. */
#undef HAVE_READLINK #undef HAVE_READLINK
/* Define to 1 if you have the `readlinkat' function. */ /* Define to 1 if you have the 'readlinkat' function. */
#undef HAVE_READLINKAT #undef HAVE_READLINKAT
/* Define to 1 if you have the `reallocarray' function. */ /* Define to 1 if you have the 'reallocarray' function. */
#undef HAVE_REALLOCARRAY #undef HAVE_REALLOCARRAY
/* Define to 1 if you have the `realpath' function. */ /* Define to 1 if you have the 'realpath' function. */
#undef HAVE_REALPATH #undef HAVE_REALPATH
/* Define to 1 if you have the `rewinddir' function. */ /* Define to 1 if you have the 'rewinddir' function. */
#undef HAVE_REWINDDIR #undef HAVE_REWINDDIR
/* Define to 1 if 'long double' and 'double' have the same representation. */ /* Define to 1 if 'long double' and 'double' have the same representation. */
@@ -1154,10 +1154,10 @@
/* Define to 1 if you have the <sched.h> header file. */ /* Define to 1 if you have the <sched.h> header file. */
#undef HAVE_SCHED_H #undef HAVE_SCHED_H
/* Define to 1 if you have the `sched_setparam' function. */ /* Define to 1 if you have the 'sched_setparam' function. */
#undef HAVE_SCHED_SETPARAM #undef HAVE_SCHED_SETPARAM
/* Define to 1 if you have the `sched_setscheduler' function. */ /* Define to 1 if you have the 'sched_setscheduler' function. */
#undef HAVE_SCHED_SETSCHEDULER #undef HAVE_SCHED_SETSCHEDULER
/* Define to 1 if you have the <sdkddkver.h> header file. */ /* Define to 1 if you have the <sdkddkver.h> header file. */
@@ -1166,31 +1166,31 @@
/* Define to 1 if you have the <search.h> header file. */ /* Define to 1 if you have the <search.h> header file. */
#undef HAVE_SEARCH_H #undef HAVE_SEARCH_H
/* Define to 1 if you have the `setdtablesize' function. */ /* Define to 1 if you have the 'setdtablesize' function. */
#undef HAVE_SETDTABLESIZE #undef HAVE_SETDTABLESIZE
/* Define to 1 if you have the `setegid' function. */ /* Define to 1 if you have the 'setegid' function. */
#undef HAVE_SETEGID #undef HAVE_SETEGID
/* Define to 1 if you have the `setenv' function. */ /* Define to 1 if you have the 'setenv' function. */
#undef HAVE_SETENV #undef HAVE_SETENV
/* Define to 1 if you have the `seteuid' function. */ /* Define to 1 if you have the 'seteuid' function. */
#undef HAVE_SETEUID #undef HAVE_SETEUID
/* Define to 1 if you have the `setlocale' function. */ /* Define to 1 if you have the 'setlocale' function. */
#undef HAVE_SETLOCALE #undef HAVE_SETLOCALE
/* Define to 1 if you have the `sigaction' function. */ /* Define to 1 if you have the 'sigaction' function. */
#undef HAVE_SIGACTION #undef HAVE_SIGACTION
/* Define to 1 if you have the `sigaltstack' function. */ /* Define to 1 if you have the 'sigaltstack' function. */
#undef HAVE_SIGALTSTACK #undef HAVE_SIGALTSTACK
/* Define to 1 if the system has the type `siginfo_t'. */ /* Define to 1 if the system has the type 'siginfo_t'. */
#undef HAVE_SIGINFO_T #undef HAVE_SIGINFO_T
/* Define to 1 if you have the `siginterrupt' function. */ /* Define to 1 if you have the 'siginterrupt' function. */
#undef HAVE_SIGINTERRUPT #undef HAVE_SIGINTERRUPT
/* Define to 1 if 'sig_atomic_t' is a signed integer type. */ /* Define to 1 if 'sig_atomic_t' is a signed integer type. */
@@ -1202,13 +1202,13 @@
/* Define to 1 if 'wint_t' is a signed integer type. */ /* Define to 1 if 'wint_t' is a signed integer type. */
#undef HAVE_SIGNED_WINT_T #undef HAVE_SIGNED_WINT_T
/* Define to 1 if the system has the type `sigset_t'. */ /* Define to 1 if the system has the type 'sigset_t'. */
#undef HAVE_SIGSET_T #undef HAVE_SIGSET_T
/* Define to 1 if the system has the type `sig_atomic_t'. */ /* Define to 1 if the system has the type 'sig_atomic_t'. */
#undef HAVE_SIG_ATOMIC_T #undef HAVE_SIG_ATOMIC_T
/* Define to 1 if you have the `snprintf' function. */ /* Define to 1 if you have the 'snprintf' function. */
#undef HAVE_SNPRINTF #undef HAVE_SNPRINTF
/* Define if the return value of the snprintf function is the number of of /* Define if the return value of the snprintf function is the number of of
@@ -1239,16 +1239,16 @@
/* Define to 1 if you have the <stdlib.h> header file. */ /* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H #undef HAVE_STDLIB_H
/* Define to 1 if you have the `stpcpy' function. */ /* Define to 1 if you have the 'stpcpy' function. */
#undef HAVE_STPCPY #undef HAVE_STPCPY
/* Define if you have the stpncpy() function and it works. */ /* Define if you have the stpncpy() function and it works. */
#undef HAVE_STPNCPY #undef HAVE_STPNCPY
/* Define to 1 if you have the `strchrnul' function. */ /* Define to 1 if you have the 'strchrnul' function. */
#undef HAVE_STRCHRNUL #undef HAVE_STRCHRNUL
/* Define to 1 if you have the `strerror_r' function. */ /* Define to 1 if you have the 'strerror_r' function. */
#undef HAVE_STRERROR_R #undef HAVE_STRERROR_R
/* Define to 1 if you have the <strings.h> header file. */ /* Define to 1 if you have the <strings.h> header file. */
@@ -1257,43 +1257,43 @@
/* Define to 1 if you have the <string.h> header file. */ /* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H #undef HAVE_STRING_H
/* Define to 1 if you have the `strndup' function. */ /* Define to 1 if you have the 'strndup' function. */
#undef HAVE_STRNDUP #undef HAVE_STRNDUP
/* Define to 1 if you have the `strnlen' function. */ /* Define to 1 if you have the 'strnlen' function. */
#undef HAVE_STRNLEN #undef HAVE_STRNLEN
/* Define to 1 if `sa_sigaction' is a member of `struct sigaction'. */ /* Define to 1 if 'sa_sigaction' is a member of 'struct sigaction'. */
#undef HAVE_STRUCT_SIGACTION_SA_SIGACTION #undef HAVE_STRUCT_SIGACTION_SA_SIGACTION
/* Define to 1 if `st_atimensec' is a member of `struct stat'. */ /* Define to 1 if 'st_atimensec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_ATIMENSEC #undef HAVE_STRUCT_STAT_ST_ATIMENSEC
/* Define to 1 if `st_atimespec.tv_nsec' is a member of `struct stat'. */ /* Define to 1 if 'st_atimespec.tv_nsec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC #undef HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC
/* Define to 1 if `st_atim.st__tim.tv_nsec' is a member of `struct stat'. */ /* Define to 1 if 'st_atim.st__tim.tv_nsec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC #undef HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC
/* Define to 1 if `st_atim.tv_nsec' is a member of `struct stat'. */ /* Define to 1 if 'st_atim.tv_nsec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC #undef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC
/* Define to 1 if `st_birthtimensec' is a member of `struct stat'. */ /* Define to 1 if 'st_birthtimensec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC #undef HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC
/* Define to 1 if `st_birthtimespec.tv_nsec' is a member of `struct stat'. */ /* Define to 1 if 'st_birthtimespec.tv_nsec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC #undef HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC
/* Define to 1 if `st_birthtim.tv_nsec' is a member of `struct stat'. */ /* Define to 1 if 'st_birthtim.tv_nsec' is a member of 'struct stat'. */
#undef HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC #undef HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC
/* Define to 1 if the system has the type `struct tms'. */ /* Define to 1 if the system has the type 'struct tms'. */
#undef HAVE_STRUCT_TMS #undef HAVE_STRUCT_TMS
/* Define to 1 if you have the `strverscmp' function. */ /* Define to 1 if you have the 'strverscmp' function. */
#undef HAVE_STRVERSCMP #undef HAVE_STRVERSCMP
/* Define to 1 if you have the `symlink' function. */ /* Define to 1 if you have the 'symlink' function. */
#undef HAVE_SYMLINK #undef HAVE_SYMLINK
/* Define to 1 if you have the <sys/bitypes.h> header file. */ /* Define to 1 if you have the <sys/bitypes.h> header file. */
@@ -1338,28 +1338,28 @@
/* Define to 1 if you have the <sys/wait.h> header file. */ /* Define to 1 if you have the <sys/wait.h> header file. */
#undef HAVE_SYS_WAIT_H #undef HAVE_SYS_WAIT_H
/* Define to 1 if you have the `tcdrain' function. */ /* Define to 1 if you have the 'tcdrain' function. */
#undef HAVE_TCDRAIN #undef HAVE_TCDRAIN
/* Define to 1 if you have the <termios.h> header file. */ /* Define to 1 if you have the <termios.h> header file. */
#undef HAVE_TERMIOS_H #undef HAVE_TERMIOS_H
/* Define to 1 if you have the `thrd_create' function. */ /* Define to 1 if you have the 'thrd_create' function. */
#undef HAVE_THRD_CREATE #undef HAVE_THRD_CREATE
/* Define to 1 if you have the <threads.h> header file. */ /* Define to 1 if you have the <threads.h> header file. */
#undef HAVE_THREADS_H #undef HAVE_THREADS_H
/* Define to 1 if you have the `towlower' function. */ /* Define to 1 if you have the 'towlower' function. */
#undef HAVE_TOWLOWER #undef HAVE_TOWLOWER
/* Define to 1 if you have the `tsearch' function. */ /* Define to 1 if you have the 'tsearch' function. */
#undef HAVE_TSEARCH #undef HAVE_TSEARCH
/* Define to 1 if you have the <unistd.h> header file. */ /* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H #undef HAVE_UNISTD_H
/* Define to 1 if you have the `unsetenv' function. */ /* Define to 1 if you have the 'unsetenv' function. */
#undef HAVE_UNSETENV #undef HAVE_UNSETENV
/* Define to 1 if the system has the type 'unsigned long long int'. */ /* Define to 1 if the system has the type 'unsigned long long int'. */
@@ -1368,23 +1368,23 @@
/* Define if you have a global __progname variable */ /* Define if you have a global __progname variable */
#undef HAVE_VAR___PROGNAME #undef HAVE_VAR___PROGNAME
/* Define to 1 if you have the `vasnprintf' function. */ /* Define to 1 if you have the 'vasnprintf' function. */
#undef HAVE_VASNPRINTF #undef HAVE_VASNPRINTF
/* Define to 1 if you have the `vasprintf' function. */ /* Define to 1 if you have the 'vasprintf' function. */
#undef HAVE_VASPRINTF #undef HAVE_VASPRINTF
/* Define to 1 if you have the `vfork' function. */ /* Define to 1 if you have the 'vfork' function. */
#undef HAVE_VFORK #undef HAVE_VFORK
/* Define to 1 or 0, depending whether the compiler supports simple visibility /* Define to 1 or 0, depending whether the compiler supports simple visibility
declarations. */ declarations. */
#undef HAVE_VISIBILITY #undef HAVE_VISIBILITY
/* Define to 1 if you have the `vsnprintf' function. */ /* Define to 1 if you have the 'vsnprintf' function. */
#undef HAVE_VSNPRINTF #undef HAVE_VSNPRINTF
/* Define to 1 if you have the `waitid' function. */ /* Define to 1 if you have the 'waitid' function. */
#undef HAVE_WAITID #undef HAVE_WAITID
/* Define to 1 if you have the <wchar.h> header file. */ /* Define to 1 if you have the <wchar.h> header file. */
@@ -1393,19 +1393,19 @@
/* Define if you have the 'wchar_t' type. */ /* Define if you have the 'wchar_t' type. */
#undef HAVE_WCHAR_T #undef HAVE_WCHAR_T
/* Define to 1 if you have the `wcrtomb' function. */ /* Define to 1 if you have the 'wcrtomb' function. */
#undef HAVE_WCRTOMB #undef HAVE_WCRTOMB
/* Define to 1 if you have the `wcslen' function. */ /* Define to 1 if you have the 'wcslen' function. */
#undef HAVE_WCSLEN #undef HAVE_WCSLEN
/* Define to 1 if you have the `wcsnlen' function. */ /* Define to 1 if you have the 'wcsnlen' function. */
#undef HAVE_WCSNLEN #undef HAVE_WCSNLEN
/* Define to 1 if you have the <wctype.h> header file. */ /* Define to 1 if you have the <wctype.h> header file. */
#undef HAVE_WCTYPE_H #undef HAVE_WCTYPE_H
/* Define to 1 if you have the `wcwidth' function. */ /* Define to 1 if you have the 'wcwidth' function. */
#undef HAVE_WCWIDTH #undef HAVE_WCWIDTH
/* Define to 1 if the compiler and linker support weak declarations of /* Define to 1 if the compiler and linker support weak declarations of
@@ -1431,13 +1431,13 @@
/* Define to 1 if you have the <xlocale.h> header file. */ /* Define to 1 if you have the <xlocale.h> header file. */
#undef HAVE_XLOCALE_H #undef HAVE_XLOCALE_H
/* Define to 1 if the system has the type `_Bool'. */ /* Define to 1 if the system has the type '_Bool'. */
#undef HAVE__BOOL #undef HAVE__BOOL
/* Define to 1 if you have the `_NSGetExecutablePath' function. */ /* Define to 1 if you have the '_NSGetExecutablePath' function. */
#undef HAVE__NSGETEXECUTABLEPATH #undef HAVE__NSGETEXECUTABLEPATH
/* Define to 1 if you have the `_set_invalid_parameter_handler' function. */ /* Define to 1 if you have the '_set_invalid_parameter_handler' function. */
#undef HAVE__SET_INVALID_PARAMETER_HANDLER #undef HAVE__SET_INVALID_PARAMETER_HANDLER
/* Define to 1 if the compiler supports __builtin_expect, /* Define to 1 if the compiler supports __builtin_expect,
@@ -1450,13 +1450,13 @@
#endif #endif
/* Define to 1 if you have the `__fseterr' function. */ /* Define to 1 if you have the '__fseterr' function. */
#undef HAVE___FSETERR #undef HAVE___FSETERR
/* Define to 1 if the compiler supports the keyword '__inline'. */ /* Define to 1 if the compiler supports the keyword '__inline'. */
#undef HAVE___INLINE #undef HAVE___INLINE
/* Define to 1 if you have the `__xpg_strerror_r' function. */ /* Define to 1 if you have the '__xpg_strerror_r' function. */
#undef HAVE___XPG_STRERROR_R #undef HAVE___XPG_STRERROR_R
/* Define as const if the declaration of iconv() needs const. */ /* Define as const if the declaration of iconv() needs const. */
@@ -1720,10 +1720,10 @@
STACK_DIRECTION = 0 => direction of growth unknown */ STACK_DIRECTION = 0 => direction of growth unknown */
#undef STACK_DIRECTION #undef STACK_DIRECTION
/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */ /* Define to 1 if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
#undef STAT_MACROS_BROKEN #undef STAT_MACROS_BROKEN
/* Define to 1 if all of the C90 standard headers exist (not just the ones /* Define to 1 if all of the C89 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */ backward compatibility; new code need not use it. */
#undef STDC_HEADERS #undef STDC_HEADERS
@@ -1760,10 +1760,14 @@
weak. */ weak. */
#undef USE_POSIX_THREADS_WEAK #undef USE_POSIX_THREADS_WEAK
/* Enable extensions on AIX 3, Interix. */ /* Enable extensions on AIX, Interix, z/OS. */
#ifndef _ALL_SOURCE #ifndef _ALL_SOURCE
# undef _ALL_SOURCE # undef _ALL_SOURCE
#endif #endif
/* Enable extensions on Cosmopolitan Libc. */
#ifndef _COSMO_SOURCE
# undef _COSMO_SOURCE
#endif
/* Enable general extensions on macOS. */ /* Enable general extensions on macOS. */
#ifndef _DARWIN_C_SOURCE #ifndef _DARWIN_C_SOURCE
# undef _DARWIN_C_SOURCE # undef _DARWIN_C_SOURCE
@@ -1821,11 +1825,15 @@
#ifndef __STDC_WANT_IEC_60559_DFP_EXT__ #ifndef __STDC_WANT_IEC_60559_DFP_EXT__
# undef __STDC_WANT_IEC_60559_DFP_EXT__ # undef __STDC_WANT_IEC_60559_DFP_EXT__
#endif #endif
/* Enable extensions specified by C23 Annex F. */
#ifndef __STDC_WANT_IEC_60559_EXT__
# undef __STDC_WANT_IEC_60559_EXT__
#endif
/* Enable extensions specified by ISO/IEC TS 18661-4:2015. */ /* Enable extensions specified by ISO/IEC TS 18661-4:2015. */
#ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__ #ifndef __STDC_WANT_IEC_60559_FUNCS_EXT__
# undef __STDC_WANT_IEC_60559_FUNCS_EXT__ # undef __STDC_WANT_IEC_60559_FUNCS_EXT__
#endif #endif
/* Enable extensions specified by ISO/IEC TS 18661-3:2015. */ /* Enable extensions specified by C23 Annex H and ISO/IEC TS 18661-3:2015. */
#ifndef __STDC_WANT_IEC_60559_TYPES_EXT__ #ifndef __STDC_WANT_IEC_60559_TYPES_EXT__
# undef __STDC_WANT_IEC_60559_TYPES_EXT__ # undef __STDC_WANT_IEC_60559_TYPES_EXT__
#endif #endif
@@ -2455,10 +2463,10 @@
# define _GL_INLINE_HEADER_END # define _GL_INLINE_HEADER_END
#endif #endif
/* Define to `int' if <sys/types.h> doesn't define. */ /* Define as 'int' if <sys/types.h> doesn't define. */
#undef gid_t #undef gid_t
/* Define to `__inline__' or `__inline' if that's what the C compiler /* Define to '__inline__' or '__inline' if that's what the C compiler
calls it, or to nothing if 'inline' is not supported under any name. */ calls it, or to nothing if 'inline' is not supported under any name. */
#ifndef __cplusplus #ifndef __cplusplus
#undef inline #undef inline
@@ -2496,7 +2504,7 @@
#define _GL_CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2))) #define _GL_CMP(n1, n2) (((n1) > (n2)) - ((n1) < (n2)))
/* Define to `int' if <sys/types.h> does not define. */ /* Define to 'int' if <sys/types.h> does not define. */
#undef mode_t #undef mode_t
/* Define to the type of st_nlink in struct stat, or a supertype. */ /* Define to the type of st_nlink in struct stat, or a supertype. */
@@ -2528,13 +2536,13 @@
accessed atomically even in the presence of asynchronous signals. */ accessed atomically even in the presence of asynchronous signals. */
#undef sig_atomic_t #undef sig_atomic_t
/* Define to `unsigned int' if <sys/types.h> does not define. */ /* Define as 'unsigned int' if <stddef.h> doesn't define. */
#undef size_t #undef size_t
/* Define as a signed type of the same size as size_t. */ /* Define as a signed type of the same size as size_t. */
#undef ssize_t #undef ssize_t
/* Define to `int' if <sys/types.h> doesn't define. */ /* Define as 'int' if <sys/types.h> doesn't define. */
#undef uid_t #undef uid_t
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,92 @@
# This file was generated by Autom4te 2.73.
# It contains the lists of macros which have been traced.
# It can be safely removed.
@request = (
bless( [
'0',
1,
[
'/usr/share/autoconf'
],
[
'/usr/share/autoconf/autoconf/autoconf.m4f',
'aclocal.m4',
'configure.ac'
],
{
'AC_CANONICAL_BUILD' => 1,
'AC_CANONICAL_HOST' => 1,
'AC_CANONICAL_SYSTEM' => 1,
'AC_CANONICAL_TARGET' => 1,
'AC_CONFIG_AUX_DIR' => 1,
'AC_CONFIG_FILES' => 1,
'AC_CONFIG_HEADERS' => 1,
'AC_CONFIG_LIBOBJ_DIR' => 1,
'AC_CONFIG_LINKS' => 1,
'AC_CONFIG_MACRO_DIR' => 1,
'AC_CONFIG_MACRO_DIR_TRACE' => 1,
'AC_CONFIG_SUBDIRS' => 1,
'AC_DEFINE_TRACE_LITERAL' => 1,
'AC_FC_FREEFORM' => 1,
'AC_FC_PP_DEFINE' => 1,
'AC_FC_PP_SRCEXT' => 1,
'AC_FC_SRCEXT' => 1,
'AC_INIT' => 1,
'AC_LIBSOURCE' => 1,
'AC_LIB_HAVE_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS' => 1,
'AC_LIB_LINKFLAGS_FROM_LIBS' => 1,
'AC_PROG_LIBTOOL' => 1,
'AC_REQUIRE_AUX_FILE' => 1,
'AC_SUBST' => 1,
'AC_SUBST_TRACE' => 1,
'AH_OUTPUT' => 1,
'AM_AUTOMAKE_VERSION' => 1,
'AM_CONDITIONAL' => 1,
'AM_ENABLE_MULTILIB' => 1,
'AM_EXTRA_RECURSIVE_TARGETS' => 1,
'AM_GNU_GETTEXT' => 1,
'AM_GNU_GETTEXT_INTL_SUBDIR' => 1,
'AM_GNU_GETTEXT_REQUIRE_VERSION' => 1,
'AM_GNU_GETTEXT_VERSION' => 1,
'AM_ICONV' => 1,
'AM_INIT_AUTOMAKE' => 1,
'AM_MAINTAINER_MODE' => 1,
'AM_MAKEFILE_INCLUDE' => 1,
'AM_NLS' => 1,
'AM_PATH_GUILE' => 1,
'AM_POT_TOOLS' => 1,
'AM_PROG_AR' => 1,
'AM_PROG_CC_C_O' => 1,
'AM_PROG_CXX_C_O' => 1,
'AM_PROG_F77_C_O' => 1,
'AM_PROG_FC_C_O' => 1,
'AM_PROG_LIBTOOL' => 1,
'AM_PROG_MKDIR_P' => 1,
'AM_PROG_MOC' => 1,
'AM_SILENT_RULES' => 1,
'AM_XGETTEXT_OPTION' => 1,
'GTK_DOC_CHECK' => 1,
'GUILE_FLAGS' => 1,
'IT_PROG_INTLTOOL' => 1,
'LT_CONFIG_LTDL_DIR' => 1,
'LT_INIT' => 1,
'LT_SUPPORTED_TAG' => 1,
'_AM_COND_ELSE' => 1,
'_AM_COND_ENDIF' => 1,
'_AM_COND_IF' => 1,
'_AM_MAKEFILE_INCLUDE' => 1,
'_AM_SUBST_NOTMAKE' => 1,
'_LT_AC_TAGCONFIG' => 1,
'_m4_warn' => 1,
'include' => 1,
'm4_include' => 1,
'm4_pattern_allow' => 1,
'm4_pattern_forbid' => 1,
'm4_sinclude' => 1,
'sinclude' => 1
}
], 'Autom4te::Request' )
);
File diff suppressed because it is too large Load Diff
+240 -235
View File
@@ -1,12 +1,12 @@
This is flex.info, produced by makeinfo version 6.1 from flex.texi. This is flex.info, produced by makeinfo version 7.3 from flex.texi.
The flex manual is placed under the same licensing conditions as the The flex manual is placed under the same licensing conditions as the
rest of flex: rest of flex:
Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2012 The Flex Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2012 The Flex
Project. Project.
Copyright (C) 1990, 1997 The Regents of the University of California. Copyright © 1990, 1997 The Regents of the University of California.
All rights reserved. All rights reserved.
This code is derived from software contributed to Berkeley by Vern This code is derived from software contributed to Berkeley by Vern
@@ -42,240 +42,245 @@ END-INFO-DIR-ENTRY
 
Indirect: Indirect:
flex.info-1: 1622 flex.info-1: 1620
flex.info-2: 318745 flex.info-2: 324917
 
Tag Table: Tag Table:
(Indirect) (Indirect)
Node: Top1622 Node: Top1620
Node: Copyright9414 Node: Copyright7694
Node: Reporting Bugs10933 Node: Reporting Bugs9211
Node: Introduction11189 Node: Introduction9471
Node: Simple Examples12018 Node: Simple Examples10328
Node: Format15304 Node: Format13771
Node: Definitions Section15759 Node: Definitions Section14192
Ref: Definitions Section-Footnote-118017 Ref: Definitions Section-Footnote-116550
Node: Rules Section18085 Node: Rules Section16626
Node: User Code Section19243 Node: User Code Section17800
Node: Comments in the Input19681 Node: Comments in the Input18246
Node: Patterns21051 Node: Patterns19651
Ref: case and character ranges27883 Ref: case and character ranges26973
Node: Matching31886 Node: Matching31202
Node: Actions35171 Node: Actions34639
Node: Generated Scanner44133 Node: Generated Scanner43962
Node: Start Conditions49136 Node: Start Conditions49185
Node: Multiple Input Buffers59678 Node: Multiple Input Buffers60101
Ref: Scanning Strings66221 Ref: Scanning Strings66886
Node: EOF67850 Node: EOF68579
Node: Misc Macros69436 Node: Misc Macros70215
Node: User Values72290 Node: User Values73173
Node: Yacc74615 Node: Yacc75614
Node: Scanner Options75510 Node: Scanner Options76575
Node: Options for Specifying Filenames78299 Node: Options for Specifying Filenames79407
Ref: option-header78525 Ref: option-header79633
Ref: option-outfile79239 Ref: option-outfile80379
Ref: option-stdout79564 Ref: option-stdout80744
Node: Options Affecting Scanner Behavior80547 Node: Options Affecting Scanner Behavior81763
Ref: option-case-insensitive80788 Ref: option-case-insensitive82004
Ref: option-lex-compat81221 Ref: option-lex-compat82461
Ref: option-batch81753 Ref: option-batch83033
Ref: option-interactive82272 Ref: option-interactive83588
Ref: option-7bit83626 Ref: option-7bit84990
Ref: option-8bit84930 Ref: option-8bit86358
Ref: option-default85342 Ref: option-default86798
Ref: option-always-interactive85406 Ref: option-always-interactive86870
Ref: option-posix86010 Ref: option-posix87498
Ref: option-stack87157 Ref: option-stack88711
Ref: option-stdinit87265 Ref: option-stdinit88827
Ref: option-yylineno87744 Ref: option-yylineno89350
Ref: option-yywrap88187 Ref: option-yywrap89829
Node: Code-Level And API Options88454 Node: Code-Level And API Options90120
Ref: option-ansi-definitions88681 Ref: option-ansi-definitions90347
Ref: option-ansi-prototypes88756 Ref: option-ansi-prototypes90430
Ref: option-bison-bridge88829 Ref: option-bison-bridge90511
Ref: option-bison-locations89170 Ref: option-bison-locations90876
Ref: option-noline89430 Ref: option-noline91164
Ref: option-reentrant89944 Ref: option-reentrant91714
Ref: option-c++90556 Ref: option-c++92346
Ref: option-array90682 Ref: option-array92480
Ref: option-pointer90780 Ref: option-pointer92586
Ref: option-prefix90907 Ref: option-prefix92733
Ref: option-main92435 Ref: option-main94325
Ref: option-nounistd92619 Ref: option-nounistd94529
Ref: option-yyclass93130 Ref: option-yyclass95068
Node: Options for Scanner Speed and Size93614 Node: Options for Scanner Speed and Size95596
Ref: option-align94164 Ref: option-align96158
Ref: option-ecs94666 Ref: option-ecs96668
Ref: option-meta-ecs95705 Ref: option-meta-ecs97751
Ref: option-read96193 Ref: option-read98259
Ref: option-full98076 Ref: option-full100222
Ref: option-fast98271 Ref: option-fast100437
Node: Debugging Options99195 Node: Debugging Options101389
Ref: option-backup99382 Ref: option-backup101576
Ref: option-debug99927 Ref: option-debug102145
Ref: option-perf-report100649 Ref: option-perf-report102887
Ref: option-nodefault101275 Ref: option-nodefault103545
Ref: option-trace101593 Ref: option-trace103875
Ref: option-nowarn101884 Ref: option-nowarn104190
Ref: option-verbose101952 Ref: option-verbose104266
Ref: option-warn102381 Ref: option-warn104723
Node: Miscellaneous Options102600 Node: Miscellaneous Options104950
Node: Performance103056 Node: Performance105434
Node: Cxx113303 Node: Cxx115909
Node: Reentrant121395 Node: Reentrant124503
Node: Reentrant Uses122129 Node: Reentrant Uses125197
Node: Reentrant Overview123691 Node: Reentrant Overview126812
Node: Reentrant Example124491 Node: Reentrant Example127650
Node: Reentrant Detail125264 Node: Reentrant Detail128458
Node: Specify Reentrant125768 Node: Specify Reentrant128895
Node: Extra Reentrant Argument126418 Node: Extra Reentrant Argument129557
Node: Global Replacement127670 Node: Global Replacement130869
Node: Init and Destroy Functions128905 Node: Init and Destroy Functions132160
Node: Accessor Methods131426 Node: Accessor Methods134796
Node: Extra Data132773 Node: Extra Data136183
Node: About yyscan_t135040 Node: About yyscan_t138510
Node: Reentrant Functions135437 Node: Reentrant Functions138919
Ref: bison-functions136921 Ref: bison-functions140420
Node: Lex and Posix137660 Node: Lex and Posix141191
Node: Memory Management145007 Node: Memory Management149030
Ref: memory-management145153 Ref: memory-management149176
Node: The Default Memory Management145387 Node: The Default Memory Management149404
Ref: The Default Memory Management-Footnote-1149207 Ref: The Default Memory Management-Footnote-1153240
Node: Overriding The Default Memory Management149360 Node: Overriding The Default Memory Management153393
Ref: Overriding The Default Memory Management-Footnote-1151774 Ref: Overriding The Default Memory Management-Footnote-1155904
Node: A Note About yytext And Memory151938 Node: A Note About yytext And Memory156080
Node: Serialized Tables153178 Node: Serialized Tables157324
Ref: serialization153322 Ref: serialization157468
Node: Creating Serialized Tables154102 Node: Creating Serialized Tables158238
Node: Loading and Unloading Serialized Tables155717 Node: Loading and Unloading Serialized Tables159885
Node: Tables File Format157490 Node: Tables File Format161694
Node: Diagnostics164515 Node: Diagnostics169019
Node: Limitations167924 Node: Limitations172584
Node: Bibliography169872 Node: Bibliography174608
Node: FAQ170542 Node: FAQ175290
Node: When was flex born?175705 Node: When was flex born?179534
Node: How do I expand backslash-escape sequences in C-style quoted strings?176082 Node: How do I expand backslash-escape sequences in C-style quoted strings?179915
Node: Why do flex scanners call fileno if it is not ANSI compatible?177385 Node: Why do flex scanners call fileno if it is not ANSI compatible?181230
Node: Does flex support recursive pattern definitions?178182 Node: Does flex support recursive pattern definitions?182071
Node: How do I skip huge chunks of input (tens of megabytes) while using flex?179029 Node: How do I skip huge chunks of input (tens of megabytes) while using flex?182922
Node: Flex is not matching my patterns in the same order that I defined them.179496 Node: Flex is not matching my patterns in the same order that I defined them.183401
Node: My actions are executing out of order or sometimes not at all.181242 Node: My actions are executing out of order or sometimes not at all.185187
Node: How can I have multiple input sources feed into the same scanner at the same time?182015 Node: How can I have multiple input sources feed into the same scanner at the same time?185982
Node: Can I build nested parsers that work with the same input file?184000 Node: Can I build nested parsers that work with the same input file?188033
Node: How can I match text only at the end of a file?185007 Node: How can I match text only at the end of a file?189060
Node: How can I make REJECT cascade across start condition boundaries?185811 Node: How can I make REJECT cascade across start condition boundaries?189876
Node: Why cant I use fast or full tables with interactive mode?186825 Node: Why cant I use fast or full tables with interactive mode?190902
Node: How much faster is -F or -f than -C?188082 Node: How much faster is -F or -f than -C?192159
Node: If I have a simple grammar cant I just parse it with flex?188394 Node: If I have a simple grammar cant I just parse it with flex?192471
Node: Why doesn't yyrestart() set the start state back to INITIAL?188876 Node: Why doesn't yyrestart() set the start state back to INITIAL?192953
Node: How can I match C-style comments?189503 Node: How can I match C-style comments?193588
Node: The period isn't working the way I expected.190313 Node: The period isn't working the way I expected.194398
Node: Can I get the flex manual in another format?191558 Node: Can I get the flex manual in another format?195735
Node: Does there exist a "faster" NDFA->DFA algorithm?192048 Node: Does there exist a "faster" NDFA->DFA algorithm?196233
Node: How does flex compile the DFA so quickly?192558 Node: How does flex compile the DFA so quickly?196743
Node: How can I use more than 8192 rules?193524 Node: How can I use more than 8192 rules?197713
Node: How do I abandon a file in the middle of a scan and switch to a new file?194934 Node: How do I abandon a file in the middle of a scan and switch to a new file?199135
Node: How do I execute code only during initialization (only before the first scan)?195488 Node: How do I execute code only during initialization (only before the first scan)?199701
Node: How do I execute code at termination?196265 Node: How do I execute code at termination?200491
Node: Where else can I find help?196591 Node: Where else can I find help?200821
Node: Can I include comments in the "rules" section of the file?196965 Node: Can I include comments in the "rules" section of the file?201195
Node: I get an error about undefined yywrap().197345 Node: I get an error about undefined yywrap().201575
Node: How can I change the matching pattern at run time?197821 Node: How can I change the matching pattern at run time?202063
Node: How can I expand macros in the input?198183 Node: How can I expand macros in the input?202425
Node: How can I build a two-pass scanner?199215 Node: How can I build a two-pass scanner?203462
Node: How do I match any string not matched in the preceding rules?200133 Node: How do I match any string not matched in the preceding rules?204380
Node: I am trying to port code from AT&T lex that uses yysptr and yysbuf.201042 Node: I am trying to port code from AT&T lex that uses yysptr and yysbuf.205301
Node: Is there a way to make flex treat NULL like a regular character?201837 Node: Is there a way to make flex treat NULL like a regular character?206120
Node: Whenever flex can not match the input it says "flex scanner jammed".202357 Node: Whenever flex can not match the input it says "flex scanner jammed".206652
Node: Why doesn't flex have non-greedy operators like perl does?203000 Node: Why doesn't flex have non-greedy operators like perl does?207304
Node: Memory leak - 16386 bytes allocated by malloc.204353 Node: Memory leak - 16386 bytes allocated by malloc.208669
Ref: faq-memory-leak204651 Ref: faq-memory-leak208967
Node: How do I track the byte offset for lseek()?205622 Node: How do I track the byte offset for lseek()?209966
Node: How do I use my own I/O classes in a C++ scanner?207131 Node: How do I use my own I/O classes in a C++ scanner?211523
Node: How do I skip as many chars as possible?207974 Node: How do I skip as many chars as possible?212386
Node: deleteme00209049 Node: deleteme00213461
Node: Are certain equivalent patterns faster than others?209489 Node: Are certain equivalent patterns faster than others?213906
Node: Is backing up a big deal?212907 Node: Is backing up a big deal?217394
Node: Can I fake multi-byte character support?214813 Node: Can I fake multi-byte character support?219365
Node: deleteme01216254 Node: deleteme01220841
Node: Can you discuss some flex internals?217363 Node: Can you discuss some flex internals?221965
Node: unput() messes up yy_at_bol219607 Node: unput() messes up yy_at_bol224254
Node: The | operator is not doing what I want220709 Node: The | operator is not doing what I want225391
Node: Why can't flex understand this variable trailing context pattern?222255 Node: Why can't flex understand this variable trailing context pattern?226982
Node: The ^ operator isn't working223504 Node: The ^ operator isn't working228246
Node: Trailing context is getting confused with trailing optional patterns224739 Node: Trailing context is getting confused with trailing optional patterns229516
Node: Is flex GNU or not?225982 Node: Is flex GNU or not?230784
Node: ERASEME53227655 Node: ERASEME53232497
Node: I need to scan if-then-else blocks and while loops228425 Node: I need to scan if-then-else blocks and while loops233292
Node: ERASEME55229624 Node: ERASEME55234511
Node: ERASEME56230722 Node: ERASEME56235624
Node: ERASEME57232080 Node: ERASEME57237017
Node: Is there a repository for flex scanners?233078 Node: Is there a repository for flex scanners?238050
Node: How can I conditionally compile or preprocess my flex input file?233394 Node: How can I conditionally compile or preprocess my flex input file?238366
Node: Where can I find grammars for lex and yacc?233867 Node: Where can I find grammars for lex and yacc?238839
Node: I get an end-of-buffer message for each character scanned.234214 Node: I get an end-of-buffer message for each character scanned.239186
Node: unnamed-faq-62234809 Node: unnamed-faq-62239781
Node: unnamed-faq-63235827 Node: unnamed-faq-63240829
Node: unnamed-faq-64237124 Node: unnamed-faq-64242141
Node: unnamed-faq-65238090 Node: unnamed-faq-65243142
Node: unnamed-faq-66238876 Node: unnamed-faq-66243943
Node: unnamed-faq-67239991 Node: unnamed-faq-67245073
Node: unnamed-faq-68240978 Node: unnamed-faq-68246075
Node: unnamed-faq-69242120 Node: unnamed-faq-69247232
Node: unnamed-faq-70242833 Node: unnamed-faq-70247965
Node: unnamed-faq-71243594 Node: unnamed-faq-71248741
Node: unnamed-faq-72244803 Node: unnamed-faq-72249970
Node: unnamed-faq-73245846 Node: unnamed-faq-73251038
Node: unnamed-faq-74246770 Node: unnamed-faq-74251982
Node: unnamed-faq-75247715 Node: unnamed-faq-75252952
Node: unnamed-faq-76248847 Node: unnamed-faq-76254124
Node: unnamed-faq-77249553 Node: unnamed-faq-77254845
Node: unnamed-faq-78250446 Node: unnamed-faq-78255753
Node: unnamed-faq-79251444 Node: unnamed-faq-79256766
Node: unnamed-faq-80253144 Node: unnamed-faq-80258501
Node: unnamed-faq-81254462 Node: unnamed-faq-81259844
Node: unnamed-faq-82257262 Node: unnamed-faq-82262684
Node: unnamed-faq-83258219 Node: unnamed-faq-83263666
Node: unnamed-faq-84259999 Node: unnamed-faq-84265471
Node: unnamed-faq-85261102 Node: unnamed-faq-85266589
Node: unnamed-faq-86262109 Node: unnamed-faq-86267636
Node: unnamed-faq-87263047 Node: unnamed-faq-87268609
Node: unnamed-faq-88263693 Node: unnamed-faq-88269270
Node: unnamed-faq-90264524 Node: unnamed-faq-90270126
Node: unnamed-faq-91265787 Node: unnamed-faq-91271424
Node: unnamed-faq-92268215 Node: unnamed-faq-92273907
Node: unnamed-faq-93268714 Node: unnamed-faq-93274421
Node: unnamed-faq-94269641 Node: unnamed-faq-94275363
Node: unnamed-faq-95271053 Node: unnamed-faq-95276805
Node: unnamed-faq-96272571 Node: unnamed-faq-96278338
Node: unnamed-faq-97273330 Node: unnamed-faq-97279122
Node: unnamed-faq-98273997 Node: unnamed-faq-98279804
Node: unnamed-faq-99274662 Node: unnamed-faq-99280494
Node: unnamed-faq-100275591 Node: unnamed-faq-100281453
Node: unnamed-faq-101276301 Node: unnamed-faq-101282178
Node: What is the difference between YYLEX_PARAM and YY_DECL?277114 Node: What is the difference between YYLEX_PARAM and YY_DECL?283011
Node: Why do I get "conflicting types for yylex" error?277638 Node: Why do I get "conflicting types for yylex" error?283535
Node: How do I access the values set in a Flex action from within a Bison action?278168 Node: How do I access the values set in a Flex action from within a Bison action?284065
Node: Appendices278597 Node: Appendices284494
Node: Makefiles and Flex278862 Node: Makefiles and Flex284703
Ref: Makefiles and Flex-Footnote-1282064 Ref: Makefiles and Flex-Footnote-1288049
Ref: Makefiles and Flex-Footnote-2282181 Ref: Makefiles and Flex-Footnote-2288174
Ref: Makefiles and Flex-Footnote-3282368 Ref: Makefiles and Flex-Footnote-3288365
Node: Bison Bridge282419 Node: Bison Bridge288416
Ref: Bison Bridge-Footnote-1285086 Ref: Bison Bridge-Footnote-1291217
Node: M4 Dependency285278 Node: M4 Dependency291409
Ref: M4 Dependency-Footnote-1286692 Ref: M4 Dependency-Footnote-1292903
Node: Common Patterns286828 Node: Common Patterns293039
Node: Numbers287151 Node: Numbers293330
Node: Identifiers288127 Node: Identifiers294323
Node: Quoted Constructs288954 Node: Quoted Constructs295154
Node: Addresses290008 Node: Addresses296228
Node: Indices291320 Node: Indices297548
Node: Concept Index291612 Node: Concept Index297786
Node: Index of Functions and Macros318745 Node: Index of Functions and Macros324917
Node: Index of Variables323714 Node: Index of Variables329886
Node: Index of Data Types325380 Node: Index of Data Types331552
Node: Index of Hooks326268 Node: Index of Hooks332440
Node: Index of Scanner Options326836 Node: Index of Scanner Options333008
 
End Tag Table End Tag Table

Local Variables:
coding: utf-8
End:
File diff suppressed because it is too large Load Diff
Binary file not shown.
+2 -2
View File
@@ -1,4 +1,4 @@
@set UPDATED 6 May 2017 @set UPDATED 15 May 2026
@set UPDATED-MONTH May 2017 @set UPDATED-MONTH May 2026
@set EDITION 2.6.4 @set EDITION 2.6.4
@set VERSION 2.6.4 @set VERSION 2.6.4
@@ -1,4 +1,4 @@
@set UPDATED 6 May 2017 @set UPDATED 15 May 2026
@set UPDATED-MONTH May 2017 @set UPDATED-MONTH May 2026
@set EDITION 2.6.4 @set EDITION 2.6.4
@set VERSION 2.6.4 @set VERSION 2.6.4
+33 -85
View File
@@ -1,30 +1,18 @@
/* src/config.h.in. Generated from configure.ac by autoheader. */ /* src/config.h.in. Generated from configure.ac by autoheader. */
/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP /* Define to 1 if using 'alloca.c'. */
systems. This function is required for `alloca.c' support on those systems.
*/
#undef CRAY_STACKSEG_END
/* Define to 1 if using `alloca.c'. */
#undef C_ALLOCA #undef C_ALLOCA
/* Define to 1 if translation of program messages to the user's native /* Define to 1 if translation of program messages to the user's native
language is requested. */ language is requested. */
#undef ENABLE_NLS #undef ENABLE_NLS
/* Define to 1 if you have `alloca', as a function or macro. */ /* Define to 1 if you have 'alloca', as a function or macro. */
#undef HAVE_ALLOCA #undef HAVE_ALLOCA
/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix). /* Define to 1 if <alloca.h> works. */
*/
#undef HAVE_ALLOCA_H #undef HAVE_ALLOCA_H
/* Define to 1 if you have the `available.' function. */
#undef HAVE_AVAILABLE_
/* Define to 1 if you have the `by' function. */
#undef HAVE_BY
/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the /* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the
CoreFoundation framework. */ CoreFoundation framework. */
#undef HAVE_CFLOCALECOPYCURRENT #undef HAVE_CFLOCALECOPYCURRENT
@@ -40,43 +28,25 @@
/* Define to 1 if you have the <dlfcn.h> header file. */ /* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H #undef HAVE_DLFCN_H
/* Define to 1 if you have the `dnl' function. */ /* Define to 1 if you have the 'dup2' function. */
#undef HAVE_DNL
/* Define to 1 if you have the `dup2' function. */
#undef HAVE_DUP2 #undef HAVE_DUP2
/* Define to 1 if you have the `enabled' function. */ /* Define to 1 if you have the 'fork' function. */
#undef HAVE_ENABLED
/* Define to 1 if you have the `fork' function. */
#undef HAVE_FORK #undef HAVE_FORK
/* Define to 1 if you have the `function.' function. */
#undef HAVE_FUNCTION_
/* Define if the GNU gettext() function is already present or preinstalled. */ /* Define if the GNU gettext() function is already present or preinstalled. */
#undef HAVE_GETTEXT #undef HAVE_GETTEXT
/* Define to 1 if you have the `have' function. */
#undef HAVE_HAVE
/* Define if you have the iconv() function and it works. */ /* Define if you have the iconv() function and it works. */
#undef HAVE_ICONV #undef HAVE_ICONV
/* Define to 1 if you have the `if' function. */
#undef HAVE_IF
/* Define to 1 if you have the <inttypes.h> header file. */ /* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H #undef HAVE_INTTYPES_H
/* Define to 1 if you have the `is' function. */
#undef HAVE_IS
/* Define to 1 if you have the <libintl.h> header file. */ /* Define to 1 if you have the <libintl.h> header file. */
#undef HAVE_LIBINTL_H #undef HAVE_LIBINTL_H
/* Define to 1 if you have the `m' library (-lm). */ /* Define to 1 if you have the 'm' library (-lm). */
#undef HAVE_LIBM #undef HAVE_LIBM
/* Define to 1 if you have the <limits.h> header file. */ /* Define to 1 if you have the <limits.h> header file. */
@@ -85,60 +55,39 @@
/* Define to 1 if you have the <locale.h> header file. */ /* Define to 1 if you have the <locale.h> header file. */
#undef HAVE_LOCALE_H #undef HAVE_LOCALE_H
/* Define to 1 if your system has a GNU libc compatible `malloc' function, and /* Define to 1 if your system has a GNU libc compatible 'malloc' function, and
to 0 otherwise. */ to 0 otherwise. */
#undef HAVE_MALLOC #undef HAVE_MALLOC
/* Define to 1 if you have the <malloc.h> header file. */ /* Define to 1 if you have the <malloc.h> header file. */
#undef HAVE_MALLOC_H #undef HAVE_MALLOC_H
/* Define to 1 if you have the <memory.h> header file. */ /* Define to 1 if you have the 'memset' function. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the `memset' function. */
#undef HAVE_MEMSET #undef HAVE_MEMSET
/* Define to 1 if you have the `Needed' function. */
#undef HAVE_NEEDED
/* Define to 1 if you have the <netinet/in.h> header file. */ /* Define to 1 if you have the <netinet/in.h> header file. */
#undef HAVE_NETINET_IN_H #undef HAVE_NETINET_IN_H
/* Define to 1 if you have the `NLS' function. */ /* Define to 1 if you have the 'pow' function. */
#undef HAVE_NLS
/* Define to 1 if you have the `not' function. */
#undef HAVE_NOT
/* Define to 1 if you have the `only' function. */
#undef HAVE_ONLY
/* Define to 1 if you have the `OpenBSD' function. */
#undef HAVE_OPENBSD
/* Define to 1 if you have the `pow' function. */
#undef HAVE_POW #undef HAVE_POW
/* Define to 1 if you have the <pthread.h> header file. */ /* Define to 1 if you have the <pthread.h> header file. */
#undef HAVE_PTHREAD_H #undef HAVE_PTHREAD_H
/* Define to 1 if your system has a GNU libc compatible `realloc' function, /* Define to 1 if your system has a GNU libc compatible 'realloc' function,
and to 0 otherwise. */ and to 0 otherwise. */
#undef HAVE_REALLOC #undef HAVE_REALLOC
/* Define to 1 if you have the `reallocarray' function. */ /* Define to 1 if you have the 'reallocarray' function. */
#undef HAVE_REALLOCARRAY #undef HAVE_REALLOCARRAY
/* Define to 1 if you have the `regcomp' function. */ /* Define to 1 if you have the 'regcomp' function. */
#undef HAVE_REGCOMP #undef HAVE_REGCOMP
/* Define to 1 if you have the <regex.h> header file. */ /* Define to 1 if you have the <regex.h> header file. */
#undef HAVE_REGEX_H #undef HAVE_REGEX_H
/* Define to 1 if you have the `replacement' function. */ /* Define to 1 if you have the 'setlocale' function. */
#undef HAVE_REPLACEMENT
/* Define to 1 if you have the `setlocale' function. */
#undef HAVE_SETLOCALE #undef HAVE_SETLOCALE
/* Define to 1 if stdbool.h conforms to C99. */ /* Define to 1 if stdbool.h conforms to C99. */
@@ -147,16 +96,19 @@
/* Define to 1 if you have the <stdint.h> header file. */ /* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H #undef HAVE_STDINT_H
/* Define to 1 if you have the <stdio.h> header file. */
#undef HAVE_STDIO_H
/* Define to 1 if you have the <stdlib.h> header file. */ /* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H #undef HAVE_STDLIB_H
/* Define to 1 if you have the `strcasecmp' function. */ /* Define to 1 if you have the 'strcasecmp' function. */
#undef HAVE_STRCASECMP #undef HAVE_STRCASECMP
/* Define to 1 if you have the `strchr' function. */ /* Define to 1 if you have the 'strchr' function. */
#undef HAVE_STRCHR #undef HAVE_STRCHR
/* Define to 1 if you have the `strdup' function. */ /* Define to 1 if you have the 'strdup' function. */
#undef HAVE_STRDUP #undef HAVE_STRDUP
/* Define to 1 if you have the <strings.h> header file. */ /* Define to 1 if you have the <strings.h> header file. */
@@ -165,7 +117,7 @@
/* Define to 1 if you have the <string.h> header file. */ /* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H #undef HAVE_STRING_H
/* Define to 1 if you have the `strtol' function. */ /* Define to 1 if you have the 'strtol' function. */
#undef HAVE_STRTOL #undef HAVE_STRTOL
/* Define to 1 if you have the <sys/stat.h> header file. */ /* Define to 1 if you have the <sys/stat.h> header file. */
@@ -180,25 +132,19 @@
/* Define to 1 if you have the <unistd.h> header file. */ /* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H #undef HAVE_UNISTD_H
/* Define to 1 if you have the `Used' function. */ /* Define to 1 if you have the 'vfork' function. */
#undef HAVE_USED
/* Define to 1 if you have the `vfork' function. */
#undef HAVE_VFORK #undef HAVE_VFORK
/* Define to 1 if you have the <vfork.h> header file. */ /* Define to 1 if you have the <vfork.h> header file. */
#undef HAVE_VFORK_H #undef HAVE_VFORK_H
/* Define to 1 if you have the `We' function. */ /* Define to 1 if 'fork' works. */
#undef HAVE_WE
/* Define to 1 if `fork' works. */
#undef HAVE_WORKING_FORK #undef HAVE_WORKING_FORK
/* Define to 1 if `vfork' works. */ /* Define to 1 if 'vfork' works. */
#undef HAVE_WORKING_VFORK #undef HAVE_WORKING_VFORK
/* Define to 1 if the system has the type `_Bool'. */ /* Define to 1 if the system has the type '_Bool'. */
#undef HAVE__BOOL #undef HAVE__BOOL
/* Define to the sub-directory where libtool stores uninstalled libraries. */ /* Define to the sub-directory where libtool stores uninstalled libraries. */
@@ -236,30 +182,32 @@
STACK_DIRECTION = 0 => direction of growth unknown */ STACK_DIRECTION = 0 => direction of growth unknown */
#undef STACK_DIRECTION #undef STACK_DIRECTION
/* Define to 1 if you have the ANSI C header files. */ /* Define to 1 if all of the C89 standard headers exist (not just the ones
required in a freestanding environment). This macro is provided for
backward compatibility; new code need not use it. */
#undef STDC_HEADERS #undef STDC_HEADERS
/* Version number of package */ /* Version number of package */
#undef VERSION #undef VERSION
/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a /* Define to 1 if 'lex' declares 'yytext' as a 'char *' by default, not a
`char[]'. */ 'char[]'. */
#undef YYTEXT_POINTER #undef YYTEXT_POINTER
/* Define to empty if `const' does not conform to ANSI C. */ /* Define to empty if 'const' does not conform to ANSI C. */
#undef const #undef const
/* Define to rpl_malloc if the replacement function should be used. */ /* Define to rpl_malloc if the replacement function should be used. */
#undef malloc #undef malloc
/* Define to `int' if <sys/types.h> does not define. */ /* Define as a signed integer type capable of holding a process identifier. */
#undef pid_t #undef pid_t
/* Define to rpl_realloc if the replacement function should be used. */ /* Define to rpl_realloc if the replacement function should be used. */
#undef realloc #undef realloc
/* Define to `unsigned int' if <sys/types.h> does not define. */ /* Define as 'unsigned int' if <stddef.h> doesn't define. */
#undef size_t #undef size_t
/* Define as `fork' if `vfork' does not work. */ /* Define as 'fork' if 'vfork' does not work. */
#undef vfork #undef vfork
@@ -0,0 +1,265 @@
/* src/config.h.in. Generated from configure.ac by autoheader. */
/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
systems. This function is required for `alloca.c' support on those systems.
*/
#undef CRAY_STACKSEG_END
/* Define to 1 if using `alloca.c'. */
#undef C_ALLOCA
/* Define to 1 if translation of program messages to the user's native
language is requested. */
#undef ENABLE_NLS
/* Define to 1 if you have `alloca', as a function or macro. */
#undef HAVE_ALLOCA
/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
*/
#undef HAVE_ALLOCA_H
/* Define to 1 if you have the `available.' function. */
#undef HAVE_AVAILABLE_
/* Define to 1 if you have the `by' function. */
#undef HAVE_BY
/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the
CoreFoundation framework. */
#undef HAVE_CFLOCALECOPYCURRENT
/* Define to 1 if you have the MacOS X function CFPreferencesCopyAppValue in
the CoreFoundation framework. */
#undef HAVE_CFPREFERENCESCOPYAPPVALUE
/* Define if the GNU dcgettext() function is already present or preinstalled.
*/
#undef HAVE_DCGETTEXT
/* Define to 1 if you have the <dlfcn.h> header file. */
#undef HAVE_DLFCN_H
/* Define to 1 if you have the `dnl' function. */
#undef HAVE_DNL
/* Define to 1 if you have the `dup2' function. */
#undef HAVE_DUP2
/* Define to 1 if you have the `enabled' function. */
#undef HAVE_ENABLED
/* Define to 1 if you have the `fork' function. */
#undef HAVE_FORK
/* Define to 1 if you have the `function.' function. */
#undef HAVE_FUNCTION_
/* Define if the GNU gettext() function is already present or preinstalled. */
#undef HAVE_GETTEXT
/* Define to 1 if you have the `have' function. */
#undef HAVE_HAVE
/* Define if you have the iconv() function and it works. */
#undef HAVE_ICONV
/* Define to 1 if you have the `if' function. */
#undef HAVE_IF
/* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H
/* Define to 1 if you have the `is' function. */
#undef HAVE_IS
/* Define to 1 if you have the <libintl.h> header file. */
#undef HAVE_LIBINTL_H
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
/* Define to 1 if you have the <limits.h> header file. */
#undef HAVE_LIMITS_H
/* Define to 1 if you have the <locale.h> header file. */
#undef HAVE_LOCALE_H
/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
to 0 otherwise. */
#undef HAVE_MALLOC
/* Define to 1 if you have the <malloc.h> header file. */
#undef HAVE_MALLOC_H
/* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H
/* Define to 1 if you have the `memset' function. */
#undef HAVE_MEMSET
/* Define to 1 if you have the `Needed' function. */
#undef HAVE_NEEDED
/* Define to 1 if you have the <netinet/in.h> header file. */
#undef HAVE_NETINET_IN_H
/* Define to 1 if you have the `NLS' function. */
#undef HAVE_NLS
/* Define to 1 if you have the `not' function. */
#undef HAVE_NOT
/* Define to 1 if you have the `only' function. */
#undef HAVE_ONLY
/* Define to 1 if you have the `OpenBSD' function. */
#undef HAVE_OPENBSD
/* Define to 1 if you have the `pow' function. */
#undef HAVE_POW
/* Define to 1 if you have the <pthread.h> header file. */
#undef HAVE_PTHREAD_H
/* Define to 1 if your system has a GNU libc compatible `realloc' function,
and to 0 otherwise. */
#undef HAVE_REALLOC
/* Define to 1 if you have the `reallocarray' function. */
#undef HAVE_REALLOCARRAY
/* Define to 1 if you have the `regcomp' function. */
#undef HAVE_REGCOMP
/* Define to 1 if you have the <regex.h> header file. */
#undef HAVE_REGEX_H
/* Define to 1 if you have the `replacement' function. */
#undef HAVE_REPLACEMENT
/* Define to 1 if you have the `setlocale' function. */
#undef HAVE_SETLOCALE
/* Define to 1 if stdbool.h conforms to C99. */
#undef HAVE_STDBOOL_H
/* Define to 1 if you have the <stdint.h> header file. */
#undef HAVE_STDINT_H
/* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H
/* Define to 1 if you have the `strcasecmp' function. */
#undef HAVE_STRCASECMP
/* Define to 1 if you have the `strchr' function. */
#undef HAVE_STRCHR
/* Define to 1 if you have the `strdup' function. */
#undef HAVE_STRDUP
/* Define to 1 if you have the <strings.h> header file. */
#undef HAVE_STRINGS_H
/* Define to 1 if you have the <string.h> header file. */
#undef HAVE_STRING_H
/* Define to 1 if you have the `strtol' function. */
#undef HAVE_STRTOL
/* Define to 1 if you have the <sys/stat.h> header file. */
#undef HAVE_SYS_STAT_H
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define to 1 if you have the <sys/wait.h> header file. */
#undef HAVE_SYS_WAIT_H
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
/* Define to 1 if you have the `Used' function. */
#undef HAVE_USED
/* Define to 1 if you have the `vfork' function. */
#undef HAVE_VFORK
/* Define to 1 if you have the <vfork.h> header file. */
#undef HAVE_VFORK_H
/* Define to 1 if you have the `We' function. */
#undef HAVE_WE
/* Define to 1 if `fork' works. */
#undef HAVE_WORKING_FORK
/* Define to 1 if `vfork' works. */
#undef HAVE_WORKING_VFORK
/* Define to 1 if the system has the type `_Bool'. */
#undef HAVE__BOOL
/* Define to the sub-directory where libtool stores uninstalled libraries. */
#undef LT_OBJDIR
/* Define to the m4 executable name. */
#undef M4
/* Name of package */
#undef PACKAGE
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT
/* Define to the full name of this package. */
#undef PACKAGE_NAME
/* Define to the full name and version of this package. */
#undef PACKAGE_STRING
/* Define to the one symbol short name of this package. */
#undef PACKAGE_TARNAME
/* Define to the home page for this package. */
#undef PACKAGE_URL
/* Define to the version of this package. */
#undef PACKAGE_VERSION
/* If using the C implementation of alloca, define if you know the
direction of stack growth for your system; otherwise it will be
automatically deduced at runtime.
STACK_DIRECTION > 0 => grows toward higher addresses
STACK_DIRECTION < 0 => grows toward lower addresses
STACK_DIRECTION = 0 => direction of growth unknown */
#undef STACK_DIRECTION
/* Define to 1 if you have the ANSI C header files. */
#undef STDC_HEADERS
/* Version number of package */
#undef VERSION
/* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a
`char[]'. */
#undef YYTEXT_POINTER
/* Define to empty if `const' does not conform to ANSI C. */
#undef const
/* Define to rpl_malloc if the replacement function should be used. */
#undef malloc
/* Define to `int' if <sys/types.h> does not define. */
#undef pid_t
/* Define to rpl_realloc if the replacement function should be used. */
#undef realloc
/* Define to `unsigned int' if <sys/types.h> does not define. */
#undef size_t
/* Define as `fork' if `vfork' does not work. */
#undef vfork

Some files were not shown because too many files have changed in this diff Show More