19 Commits

Author SHA1 Message Date
vasilito 1eed99c54f fix: change gpiod, i2cd, pcid-spawner to oneshot_async; add pcid-spawner override
All three were blocking the boot scheduler in /usr phase on live-mini
because their daemons never notify readiness without hardware present.

- 00_gpiod.service: scheme -> oneshot_async
- 00_i2cd.service: scheme -> oneshot_async
- 00_pcid-spawner.service: new override, oneshot -> oneshot_async
- base: update submodule to 4a1d1f4 (scheduler counter)
2026-07-03 10:43:31 +03:00
vasilito f47ee4fb18 fix: change blocking oneshot services to oneshot_async, add init scheduler tracing
- pcid-spawner: oneshot -> oneshot_async (blocked boot in /usr phase)
- inputd: oneshot -> oneshot_async (unnecessary blocking during initfs)
- base: update submodule to b1a6bd8 (serial debug tracing for scheduler)
- run_mini1.sh: reduce QEMU memory 12G -> 2G for faster CI testing
2026-07-03 08:56:39 +03:00
vasilito 2bed64a6c5 base: bump submodule pointer for acpid processor data readers
acpid now evaluates _CST/_PSS/_PSD/_CPC via AML interpreter and
returns formatted text for /scheme/acpi/processor/CPU{n}/<method>.
2026-07-02 23:59:18 +03:00
vasilito 5d4d3dbce1 kernel: Tier 3 — C-state tracking and CPU topology 2026-07-02 23:27:46 +03:00
vasilito f0364a4e4a kernel: fix LAPIC spurious interrupt vector 0x00 → 0xFF 2026-07-02 23:09:36 +03:00
vasilito 890be982a6 docs: enforce canonical build command across all docs
Replace all non-canonical build invocations (bare 'make all/live
CONFIG_NAME=', 'scripts/build-iso.sh', 'scripts/run.sh') with the
canonical './local/scripts/build-redbear.sh' wrapper.

Updated: AGENTS.md, local/AGENTS.md, README.md, docs/README.md,
docs/06-BUILD-SYSTEM-SETUP.md, and 6 active local/docs plan files.
Archived docs and frozen boot-logs left as-is (historical evidence).
2026-07-02 22:54:47 +03:00
vasilito afad19ff1a kernel: fix early-boot excp_handler panic on bare metal
Updates kernel submodule to 573b3e6 which replaces context::current()
with context::try_current() in excp_handler(), preventing the cascading
'not inside of context' panic when a page fault occurs during BSP's
start() before context::init() runs.
2026-07-02 22:34:40 +03:00
vasilito eb53e8190a redbear-power: multi-threaded collector + Tier 1/2 display enhancements
Tier 1 (display Phase 0 infrastructure):
- sched.rs: read /scheme/sys/stat and /scheme/sys/sched for per-CPU
  scheduler stats (switches, steals, queue_depth, IRQs)
- msr.rs: HWP capabilities/requests, RAPL domain energy readings
- process.rs: derive_sched_policy() for SCHED column
- render.rs: System tab shows RAPL power, scheduler stats; Per-CPU
  table has IRQs/steals columns; HWP expansion rows
- acpi.rs: ACPI throttle status reading

Multi-threaded collector (exercises Phase 0 threading):
- collector.rs: parallel collect() using thread::scope + Barrier,
  exercises pthread_create, futex barriers, sched_setaffinity
- app.rs: sequential per-CPU loop replaced with parallel collector
- render.rs: System tab shows Collector stats (threads, pinned, barrier)

Tier 2 kernel wiring (submodule pointer updates):
- kernel: per-CPU sched stats (/scheme/sys/sched), NUMA-aware
  scheduling, numa::init_default() at boot
- relibc: robust mutex cleanup in exit_current_thread()
2026-07-02 21:41:25 +03:00
vasilito 6d13dee2a6 redbear-power(0.2.5): fix missing hwp field in CpuRow initializer
Pre-existing bug: CpuRow struct gained an hwp field but the initializer
at line 319 was not updated.
2026-07-02 19:22:19 +03:00
vasilito a43cca122d qtwayland(0.2.5): add PCH disable + SBOM disable flags 2026-07-02 19:13:10 +03:00
vasilito fc0c1e4576 qtshadertools(0.2.5): wire ShaderToolsMacros into Config.cmake
The cmake-generated Qt6ShaderToolsConfig.cmake has an empty
extra_cmake_include list. Patch it to include Qt6ShaderToolsMacros.cmake
so qt_internal_add_shaders is defined for downstream modules.
2026-07-02 18:38:45 +03:00
vasilito 83f47db352 qtshadertools(0.2.5): install Qt6ShaderToolsMacros.cmake
cmake install skipped this 405-line macros file that defines
qt_internal_add_shaders/qt6_add_shaders. Without it, downstream Qt
modules (qtdeclarative) fail at cmake configure with 'Unknown CMake
command qt_internal_add_shaders'.
2026-07-02 18:34:21 +03:00
vasilito f877419c43 qt modules(0.2.5): disable PCH for cross-compile
Add CMAKE_DISABLE_PRECOMPILE_HEADERS=ON to qtdeclarative, qtwayland,
and qt6-sensors — same cross-compiler PCH issue as qtshadertools.
2026-07-02 18:31:00 +03:00
vasilito ac0712e8b9 qtshadertools(0.2.5): disable PCH (cross-compiler silent crash)
Redox cross-compiler fails silently on PCH generation. Disable
precompiled headers for the cross-compile step.
2026-07-02 18:19:26 +03:00
vasilito d097261be3 qtshadertools(0.2.5): disable SBOM generation (cross-compile)
Qt 6.11 SBOM install fails referencing qtbase SBOM docs during
cross-compile. SBOM is metadata only — no impact on libs/binaries.
2026-07-02 18:13:38 +03:00
vasilito 8ce2ec6b21 pam-redbear(0.2.5): regenerate Cargo.lock after version bump
redbear-login-protocol path dependency bumped to 0.2.5 but Cargo.lock
was stale. Regenerated to pick up the new version.
2026-07-02 18:08:23 +03:00
vasilito 5e68844868 qtshadertools(0.2.5): fix TOML parse - use shell heredocs for cmake pkg gen
Python f-string triple-quotes conflicted with TOML script delimiter.
Rewrote cmake package generation using shell heredocs instead.
2026-07-02 18:08:22 +03:00
vasilito 217ca485b7 qtshadertools(0.2.5): generate Qt6ShaderToolsTools cmake package
Qt6's standalone module build installs qsb binary but does not generate
the Qt6ShaderToolsTools cmake wrapper package needed by cross-compile.
Added a Python generator that creates the minimal cmake package files
(Config, Targets, Targets-release, Precheck, AdditionalTargetInfo,
VersionlessTargets, Dependencies, ConfigVersion) following the exact
pattern of Qt6WaylandScannerTools from qtbase host build.
2026-07-02 18:01:51 +03:00
vasilito 5b42305568 qtshadertools(0.2.5): add host build step for qsb tool
qtshadertools cross-compile needs the host 'qsb' (Qt Shader Builder)
tool, just like qtbase needs host moc/rcc/uic. Added a native cmake
build step that compiles qsb from the 6.11.1 source and installs it
into the shared qt-host-build prefix.

Also regenerate pam-redbear Cargo.lock (stale after 0.2.5 version bump
of redbear-login-protocol path dependency).
2026-07-02 17:51:45 +03:00
30 changed files with 1085 additions and 113 deletions
+21 -23
View File
@@ -113,23 +113,25 @@ echo 'PODMAN_BUILD?=1' > .config # Podman container build
# archive and ignores the local forks. Remove it if present:
grep -v REDBEAR_RELEASE .config > .config.tmp && mv .config.tmp .config
# Build Red Bear OS
# Build Red Bear OS — CANONICAL build command is build-redbear.sh
# Supported compile targets:
# redbear-full desktop/graphics target (harddrive.img or live ISO)
# redbear-mini text-only console/recovery target (harddrive.img or live ISO)
# redbear-full desktop/graphics target (live ISO)
# redbear-mini text-only console/recovery target (live ISO)
# redbear-grub text-only with GRUB boot manager (live ISO)
# Desktop/graphics target: redbear-full
# Text-only targets: redbear-mini, redbear-grub
./local/scripts/build-redbear.sh redbear-mini # Recommended (dev mode, local forks)
./local/scripts/build-redbear.sh redbear-mini # Recommended (dev mode, offline)
./local/scripts/build-redbear.sh --upstream redbear-mini # With online fetch (fast iteration)
make all CONFIG_NAME=redbear-mini # Text-only target → harddrive.img
make all CONFIG_NAME=redbear-full # Desktop/graphics target → harddrive.img
make live CONFIG_NAME=redbear-full # Full desktop live ISO
make live CONFIG_NAME=redbear-mini # Text-only mini live ISO
make live CONFIG_NAME=redbear-grub # Text-only mini live ISO with GRUB
CI=1 make all CONFIG_NAME=redbear-mini # CI mode (disables TUI, for non-interactive)
./local/scripts/build-redbear.sh redbear-full # Desktop/graphics target
./local/scripts/build-redbear.sh redbear-grub # Text-only + GRUB
./local/scripts/build-redbear.sh --no-cache redbear-mini # Force clean rebuild
# IMPORTANT: For local fork development, use:
# build-redbear.sh handles: .config parsing, prefix staleness detection,
# local-over-WIP policy, version sync, pre-cooking critical packages,
# source fingerprint tracking, and ultimately calls `make live`.
# Output: build/<arch>/<config>.iso
# For local fork development:
# export REDBEAR_ALLOW_PROTECTED_FETCH=1 # base/kernel/relibc are protected recipes
# ./local/scripts/build-redbear.sh --upstream redbear-mini
# Without --upstream, the build is in offline mode and re-extracts from
@@ -138,7 +140,6 @@ CI=1 make all CONFIG_NAME=redbear-mini # CI mode (disables TUI, for non-inter
# Run
make qemu # Boot in QEMU
make qemu QEMUFLAGS="-m 4G" # With more RAM
make live # Build live ISO for real bare metal
# QEMU with network access (required for testing):
qemu-system-x86_64 -cdrom build/x86_64/redbear-mini.iso \
@@ -426,12 +427,7 @@ Red Bear OS is a **fork with frozen sources**. The cookbook tool defaults to
`COOKBOOK_OFFLINE=true` (changed from upstream Redox's `false`). Builds use archived
sources from `sources/redbear-0.1.0/` — no network access during compilation.
To allow online fetching for non-protected development recipes:
```bash
COOKBOOK_OFFLINE=false make all CONFIG_NAME=redbear-full
```
Or use the `--upstream` flag of `build-redbear.sh`:
To allow online fetching for non-protected development recipes, use the `--upstream` flag:
```bash
./local/scripts/build-redbear.sh --upstream redbear-mini
```
@@ -514,7 +510,7 @@ After ANY change to patches or `recipe.toml`:
3. Fetch: `repo --allow-protected fetch <recipe>`
4. Build: `repo cook <recipe>`
5. Verify no `FAILED`, `[ATOMIC] patch application rolled back`, or `.rej` files
6. Full image: `make all CONFIG_NAME=<target>`
6. Full image: `./local/scripts/build-redbear.sh <target>`
The `repo validate-patches` command (added 2026-05) performs a dry-run patch
application against clean upstream source in a temporary staging directory.
@@ -707,10 +703,12 @@ base) gains new commits, the prefix must be rebuilt:
touch relibc && make prefix # Rebuild relibc in the cross-toolchain
```
`build-redbear.sh` includes a preflight check that warns when fork commits are newer than
prefix artifacts. **A stale prefix is the #1 cause of "undefined reference" link errors**
after fork changes (e.g., adding `__freadahead` to relibc's `ext.rs` without rebuilding the
prefix).
`build-redbear.sh` **automatically rebuilds the prefix** when it detects that fork repos
(relibc, kernel, base) have commits newer than the prefix `libc.a`. This happens before any
recipe build begins. If the prefix rebuild fails, the build aborts immediately — recipes are
never compiled against a stale prefix. **A stale prefix is the #1 cause of "undefined reference"
link errors** after fork changes (e.g., adding `__freadahead` to relibc's `ext.rs` without
rebuilding the prefix).
### relibc Header Circular Includes (Fixed)
+5 -6
View File
@@ -72,18 +72,17 @@ git clone https://vasilito:${REDBEAR_GITEA_TOKEN}@gitea.redbearos.org/vasilito/R
make qemu
```
> **Build script:** `local/scripts/build-redbear.sh` is the canonical entry point. Bare
> `make all` works but bypasses the `.config` checking and `REDBEAR_ALLOW_PROTECTED_FETCH=1`
> gates that `build-redbear.sh` enforces. See `AGENTS.md` § Build Commands for full details.
> **Build script:** `local/scripts/build-redbear.sh` is the canonical and only
> supported build entry point. It handles `.config` parsing, prefix staleness
> detection, `REDBEAR_ALLOW_PROTECTED_FETCH=1`, pre-cooking critical packages,
> and source fingerprint tracking. Direct `make` invocations bypass these gates
> and should not be used. See `AGENTS.md` § Build Commands for full details.
### Public Scripts
| Script | Purpose |
|--------|---------|
| `local/scripts/build-redbear.sh` | **Canonical** build wrapper for redbear-mini/full/grub |
| `scripts/run.sh` | Build and run in QEMU (`-b` to build, `-c <config>` for target) |
| `scripts/build-iso.sh` | Build a live ISO for bare-metal boot |
| `scripts/build-all-isos.sh` | Build all live ISO targets |
| `scripts/network-boot.sh` | PXE network boot helper |
| `scripts/dual-boot.sh` | Dual-boot installation helper |
+1 -1
View File
@@ -519,7 +519,7 @@ requires_weak = [
[service]
cmd = "pcid-spawner"
type = "oneshot"
type = "oneshot_async"
"""
# Firmware fallback chain configs
+14 -3
View File
@@ -197,7 +197,7 @@ requires_weak = [
[service]
cmd = "gpiod"
type = { scheme = "gpio" }
type = "oneshot_async"
"""
[[files]]
@@ -211,7 +211,7 @@ requires_weak = [
[service]
cmd = "i2cd"
type = { scheme = "i2c" }
type = "oneshot_async"
"""
[[files]]
@@ -492,7 +492,7 @@ requires_weak = ["00_base.target"]
[service]
cmd = "inputd"
args = ["-A", "2"]
type = "oneshot"
type = "oneshot_async"
"""
[[files]]
@@ -532,3 +532,14 @@ cmd = "getty"
args = ["/scheme/debug/no-preserve", "-J"]
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/00_pcid-spawner.service"
data = """
[unit]
description = "PCI driver spawner (non-blocking on live-mini)"
[service]
cmd = "pcid-spawner"
type = "oneshot_async"
"""
+10 -18
View File
@@ -121,15 +121,12 @@ desktop-capable target.
These produce images such as `build/x86_64/harddrive.img` or `build/x86_64/redbear-mini.iso`.
### Bare `make all` (Legacy / Advanced)
### Bare `make all` (Not Supported)
```bash
make all CONFIG_NAME=redbear-mini
```
Bare `make all` works but bypasses the policy gates (`.config` checking,
`REDBEAR_ALLOW_PROTECTED_FETCH=1`, etc.) that `build-redbear.sh` enforces. Prefer the wrapper
unless you specifically need to bypass those gates.
Direct `make` invocations bypass the policy gates (`.config` checking,
`REDBEAR_ALLOW_PROTECTED_FETCH=1`, prefix staleness detection, local-over-WIP
enforcement, pre-cooking, source fingerprint tracking) that `build-redbear.sh`
enforces. **Always use `build-redbear.sh`.**
### Export External Toolchain
@@ -152,25 +149,20 @@ make export-toolchain TARGET=x86_64-unknown-redox \
### Build with Specific Config
```bash
# Preferred Red Bear wrapper:
# Canonical Red Bear wrapper (produces live ISO):
./local/scripts/build-redbear.sh redbear-mini
./local/scripts/build-redbear.sh redbear-full
./local/scripts/build-redbear.sh redbear-grub
# Direct make is still valid when needed:
make all CONFIG_NAME=redbear-full
```
For tracked Red Bear work, prefer these three compile targets over older historical names.
### Build a Live ISO
### Live ISO
```bash
make live CONFIG_NAME=redbear-full
# Produces: build/x86_64/redbear-live.iso
```
`build-redbear.sh` already produces a live ISO (it calls `make live` internally).
Output: `build/<arch>/<config>.iso`
Live `.iso` outputs are for real bare-metal boot, install, recovery, and demo workflows. They are not the VM/QEMU execution surface; for virtualization, use `make qemu` and the `harddrive.img` path instead.
Live `.iso` outputs are for real bare-metal boot, install, recovery, and demo workflows.
### Rebuild After Changes
+3 -3
View File
@@ -100,7 +100,7 @@ This summary is only a quick orientation layer. For canonical current-state deta
- and the active subsystem plans under `local/docs/` for detailed current workstreams.
- **Compile targets**: the supported compile targets are `redbear-mini`, `redbear-full`, and `redbear-grub`
- **Live ISO policy**: live `.iso` outputs (`make live`) are for real bare-metal boot/install/recovery workflows, not the VM/QEMU execution surface.
- **Live ISO policy**: live `.iso` outputs (`build-redbear.sh`) are for real bare-metal boot/install/recovery workflows, not the VM/QEMU execution surface.
- **Wayland**: libwayland + wayland-protocols built. A bounded greeter/compositor-backed login proof now passes, but broader compositor/runtime stability remains incomplete.
- **Qt6**: qtbase 6.11.0 (Core+Gui+Widgets+DBus+Wayland), qtdeclarative, qtsvg, qtwayland ALL BUILT
- **D-Bus**: 1.16.2 built for Redox. Qt6DBus enabled.
@@ -134,8 +134,8 @@ cargo install just cbindgen
# 3. Configure for native build (no Podman)
echo 'PODMAN_BUILD?=0' > .config
# 4. Build (downloads cross-toolchain, then compiles)
make all
# 4. Build (canonical command — produces live ISO)
./local/scripts/build-redbear.sh redbear-mini
# 5. Run in QEMU
make qemu
+16 -25
View File
@@ -345,12 +345,11 @@ history and force-push the rewritten history on a feature branch before merging.
Build flow:
```
make all CONFIG_NAME=redbear-full
mk/config.mk resolves to the active desktop/graphics compile target
→ Desktop/graphics are available only on redbear-full
./local/scripts/build-redbear.sh <config>
.config parsing, prefix staleness detection, local-over-WIP policy
→ repo cook builds all packages from local sources (offline by default)
→ mk/disk.mk creates harddrive.img with Red Bear branding
REDBEAR_RELEASE=0.1.0 ensures immutable, archived sources
→ mk/disk.mk creates <config>.iso (live ISO) with Red Bear branding
Output: build/<arch>/<config>.iso
```
Release flow:
@@ -367,8 +366,8 @@ Release flow:
## ACTIVE COMPILE TARGETS
The supported compile targets are exactly three. All three work for both `make all` (harddrive.img)
and `make live` (ISO):
The supported compile targets are exactly three. `build-redbear.sh` produces
a live ISO for each:
- `redbear-full` — Desktop/graphics-enabled target (Wayland + KDE + GPU drivers)
- `redbear-mini` — Text-only console/recovery/install target
@@ -674,8 +673,10 @@ The prefix provides the cross-compiler sysroot used by ALL recipe builds. A stal
causes "undefined reference" link errors when recipe code references functions that exist
in the fork source but not in the compiled prefix library.
`build-redbear.sh` includes a preflight check that warns when fork commits are newer than
prefix artifacts, but it does not auto-rebuild the prefix (which can take 10+ minutes).
`build-redbear.sh` **automatically rebuilds the prefix** when it detects that fork repos
(relibc, kernel, base) have commits newer than the prefix `libc.a`. This happens before any
recipe build begins. If the prefix rebuild fails, the build aborts immediately — recipes are
never compiled against a stale prefix.
**Historical example:** relibc commit `047e7c0` added `__freadahead()` to `ext.rs`, but
the prefix `libc.a` was built before that commit. m4's gnulib expected `__freadahead` to
@@ -769,25 +770,15 @@ redox-master/ ← git pull updates mainline Redox
## HOW TO BUILD RED BEAR OS
```bash
# Build targets (all three work for both `make all` and `make live`)
# CANONICAL build command — produces a live ISO for bare metal
./local/scripts/build-redbear.sh redbear-full # Desktop/graphics target
./local/scripts/build-redbear.sh redbear-mini # Text-only console/recovery target
./local/scripts/build-redbear.sh redbear-grub # Text-only with GRUB boot manager
# Or manually:
make all CONFIG_NAME=redbear-full # Desktop/graphics → harddrive.img
make all CONFIG_NAME=redbear-mini # Text-only → harddrive.img
make all CONFIG_NAME=redbear-grub # Text-only + GRUB → harddrive.img
# Live ISO (for real bare metal)
make live CONFIG_NAME=redbear-full # Full desktop live ISO
make live CONFIG_NAME=redbear-mini # Text-only mini live ISO
make live CONFIG_NAME=redbear-grub # Text-only mini live ISO with GRUB
# Or using the helper:
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-grub # Text-only + GRUB
# Options:
# --upstream Allow online recipe source fetch (fast iteration with local forks)
# --no-cache Force clean rebuild, discarding cached packages
# Output: build/<arch>/<config>.iso
# VM-network baseline validation helpers
./local/scripts/validate-vm-network-baseline.sh
@@ -885,7 +876,7 @@ redbear-netctl --help
# GRUB boot manager (installer-native):
make r.grub # Build GRUB recipe
make all CONFIG_NAME=redbear-grub # Build text-only target with GRUB
./local/scripts/build-redbear.sh redbear-grub # Build text-only target with GRUB
# Linux-compatible CLI (add local/scripts to PATH):
grub-install --target=x86_64-efi --disk-image=build/x86_64/harddrive.img
grub-mkconfig -o local/recipes/core/grub/grub.cfg
+5 -5
View File
@@ -254,7 +254,7 @@ Every bump re-introduces drift. Per AGENTS.md §Patch Governance: "DO NOT remove
## 5. Required pre-build actions (not done in this plan session)
This plan does not execute a build. The following actions are required *before* a `make all CONFIG_NAME=redbear-full` can succeed:
This plan does not execute a build. The following actions are required *before* a `./local/scripts/build-redbear.sh redbear-full` can succeed:
1. **Re-pull every Qt subrecipe** to point at `qt-everywhere-src-6.10.3.tar.xz`. Re-blake3.
2. **Re-pull every KF6 subrecipe** to point at `kf6-<project>-v6.27.0` tarball. Re-blake3.
@@ -296,8 +296,8 @@ The `0.2.5` branch will be **frozen** (no further recipe.toml bumps) when **all*
- [ ] `recipes/libs/libxkbcommon` pin to 1.9.2.
- [ ] `recipes/libs/mesa` decision recorded: 24.0.8 (fork) or 26.1.4 (upstream rebase).
- [ ] `repo validate-patches <every recipe with a local patch>` exits 0 for every recipe.
- [ ] `make all CONFIG_NAME=redbear-full` reaches the disk-image stage (filesystem.img + harddrive.img produced).
- [ ] `make live CONFIG_NAME=redbear-full` produces `build/x86_64/redbear-full.iso`.
- [ ] `./local/scripts/build-redbear.sh redbear-full` reaches the disk-image stage (filesystem.img + harddrive.img produced).
- [ ] `./local/scripts/build-redbear.sh redbear-full` produces `build/x86_64/redbear-full.iso`.
- [ ] `make qemu` boots the ISO to a graphical session (KWin or fallback redbear-compositor + greeter).
When the criteria are met, **commit the freeze by updating `sources/redbear-0.2.5/` archive** and tagging the branch tip.
@@ -407,7 +407,7 @@ Note: kde utility versioning convention changed; `konsole` now uses the `v26.04.
- **SDDM 0.21.0:** already at upstream latest.
- **kf6-attica, kf6-prison, kf6-kirigami, etc:** all targeted at v6.27.0 (real upstream latest) but see above.
### 9.5 Things to do before `make all CONFIG_NAME=redbear-full` can succeed
### 9.5 Things to do before `./local/scripts/build-redbear.sh redbear-full` can succeed
In order:
1. Per-recipe: rebase `local/patches/<recipe>/*.patch` against the new upstream source. Save rebased versions in place; do not bump `P<N>` numbers; do not delete patches unless upstream absorbed the change.
@@ -415,5 +415,5 @@ In order:
3. `touch relibc && make prefix` to refresh relibc stage in the cross-toolchain.
4. `repo validate-patches <recipe>` for each.
5. Touch-relibc-then-make-prefix between any relibc-aware recipe change (qtbase and friends touch relibc syscalls).
6. Re-run `make all CONFIG_NAME=redbear-full` and address new breakage as it surfaces.
6. Re-run `./local/scripts/build-redbear.sh redbear-full` and address new breakage as it surfaces.
7. Address KF6 6.27.0 bump (multi-day; multi-week with 38 patch rebases).
+1 -1
View File
@@ -476,7 +476,7 @@ build artifacts). Updated `BLAKE3SUMS` with the new checksum.
### Acceptance
- [x] `repo validate-patches relibc` passes all 25 patches
- [x] `make all CONFIG_NAME=redbear-full` completes successfully
- [x] `./local/scripts/build-redbear.sh redbear-full` completes successfully
- [x] QEMU boots to login prompt with virtio-gpu (1280×800) and vesad console (1280×720)
- [x] All protected recipes use only archived sources
- [x] `diff -ruN` patches apply correctly after normalization
+1 -1
View File
@@ -710,7 +710,7 @@ local/patches/relibc/P4-initgroups.patch
|-------|---------|
| Kernel compiles | `make r.kernel` |
| relibc compiles | `make r.relibc` |
| Full OS builds | `make all CONFIG_NAME=redbear-full` |
| Full OS builds | `./local/scripts/build-redbear.sh redbear-full` |
### 8.2 Runtime Evidence
@@ -682,7 +682,7 @@ Phase 0 (Patch Recovery) ← BLOCKING FOR ALL OTHERS
| Kernel compiles | `make r.kernel` |
| relibc compiles | `make r.relibc` |
| Prefix rebuilt | `touch relibc kernel && make prefix` |
| Full OS builds | `make all CONFIG_NAME=redbear-mini` |
| Full OS builds | `./local/scripts/build-redbear.sh redbear-mini` |
### 6.2 Runtime Evidence (QEMU)
+1 -1
View File
@@ -78,7 +78,7 @@ After ANY change to the patches list or patch files:
2. Full rebuild: `REDBEAR_ALLOW_PROTECTED_FETCH=1 CI=1 make r.base`
3. Verify NO "FAILED" or "rejects" in output
4. Verify all expected binaries in stage: `ls stage/usr/bin/ stage/usr/lib/drivers/`
5. Full image build: `CI=1 make all CONFIG_NAME=redbear-full`
5. Full image build: `./local/scripts/build-redbear.sh redbear-full`
## Known Issues
+1 -1
View File
@@ -53,5 +53,5 @@ integrity, submodule hygiene, firmware presence warning) that bare `make all` /
## QEMU boot
```bash
make qemu CONFIG_NAME=redbear-mini # Boot the latest built image in QEMU
make qemu # Boot the latest built image in QEMU
```
+115
View File
@@ -0,0 +1,115 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "pam"
version = "0.2.5"
dependencies = [
"redbear-login-protocol",
"serde_json",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redbear-login-protocol"
version = "0.2.5"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+1
View File
@@ -33,6 +33,7 @@ cmake "${COOKBOOK_SOURCE}" \
-DBUILD_EXAMPLES=OFF \
-DFEATURE_sensorfw=OFF \
-DQT_GENERATE_SBOM=OFF \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-Wno-dev
cmake --build . -j${COOKBOOK_MAKE_JOBS}
@@ -51,6 +51,7 @@ if [ ! -d "${HOST_BUILD}/lib/cmake/Qt6Svg" ]; then
-DQT_BUILD_EXAMPLES=OFF \
-DQT_BUILD_TESTS=OFF \
-DQT_GENERATE_SBOM=OFF \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-Wno-dev
cmake --build "${HOST_QTSVG_BUILD}" -j"${COOKBOOK_MAKE_JOBS}"
cmake --install "${HOST_QTSVG_BUILD}" --prefix "${HOST_BUILD}"
@@ -86,6 +87,7 @@ if [ ! -f "${HOST_BUILD}/bin/qmlcachegen" ] || [ ! -f "${HOST_BUILD}/bin/qmlaots
-DQT_BUILD_EXAMPLES=OFF \
-DQT_BUILD_TESTS=OFF \
-DQT_GENERATE_SBOM=OFF \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-Wno-dev
cmake --build "${DECL_HOST}" --target qmllint qmlimportscanner qmltyperegistrar qmlaotstats svgtoqml -j"${COOKBOOK_MAKE_JOBS}" || true
# Generate jsroot.qmltypes needed by qmlcachegen using the host-built qmltyperegistrar.
@@ -232,6 +234,7 @@ cmake "${COOKBOOK_SOURCE}" \
-DQT_BUILD_EXAMPLES=OFF \
-DQT_BUILD_TESTS=OFF \
-DQT_GENERATE_SBOM=OFF \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-DQT_FEATURE_qml_jit=OFF \
-DQT_FEATURE_ssl=OFF \
-DQT_FEATURE_network=OFF \
+232 -2
View File
@@ -16,16 +16,231 @@ script = """
DYNAMIC_INIT
HOST_BUILD="${COOKBOOK_ROOT}/build/qt-host-build"
HOST_QST_BUILD="${COOKBOOK_ROOT}/build/qtshadertools-host-build"
HOST_QST_STAMP="${HOST_BUILD}/.redbear-host-qsb-6.11.1"
HOST_PATH="/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl"
source "${COOKBOOK_ROOT}/local/scripts/lib/qt-sysroot.sh"
redbear_qt_link_sysroot_dirs "${COOKBOOK_SYSROOT}" plugins mkspecs metatypes modules
# Ensure the strtold compat library is available for linking
if [ -f "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" ]; then
mkdir -p "${COOKBOOK_SYSROOT}/usr/lib"
cp -f "${COOKBOOK_SYSROOT}/lib/libredbear-qt-strtold-compat.so" "${COOKBOOK_SYSROOT}/usr/lib/" 2>/dev/null || true
fi
# ============================================================
# Step 1: Build qtshadertools host tools (qsb) on the host
# ============================================================
if [ ! -f "${HOST_BUILD}/bin/qsb" ] || [ ! -f "${HOST_QST_STAMP}" ]; then
echo "=== Building qtshadertools host tools (qsb) ==="
rm -rf "${HOST_QST_BUILD}"
env -i \
HOME="${HOME}" \
PATH="${HOST_PATH}" \
cmake -S "${COOKBOOK_SOURCE}" -B "${HOST_QST_BUILD}" \
-GNinja \
-DCMAKE_C_COMPILER=/usr/bin/cc \
-DCMAKE_CXX_COMPILER=/usr/bin/c++ \
-DCMAKE_ASM_COMPILER=/usr/bin/cc \
-DCMAKE_AR=/usr/bin/ar \
-DCMAKE_RANLIB=/usr/bin/ranlib \
-DCMAKE_STRIP=/usr/bin/strip \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${HOST_BUILD}" \
-DQT_HOST_PATH="${HOST_BUILD}" \
-DQT_BUILD_TOOLS_BY_DEFAULT=ON \
-DBUILD_TESTING=OFF \
-DBUILD_EXAMPLES=OFF \
-Wno-dev
(
cd "${HOST_QST_BUILD}"
env -i HOME="${HOME}" PATH="${HOST_PATH}" cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
)
(
cd "${HOST_QST_BUILD}"
env -i HOME="${HOME}" PATH="${HOST_PATH}" cmake --install . --prefix "${HOST_BUILD}"
)
printf '%s\n' "6.11.1" > "${HOST_QST_STAMP}"
fi
# ============================================================
# Step 1b: Generate Qt6ShaderToolsTools cmake package
# Qt6's standalone module build does not generate this tools wrapper.
# Cross-compile needs find_package(Qt6ShaderToolsTools) -> Qt6::qsb.
# Pattern follows Qt6WaylandScannerTools from qtbase host build.
# ============================================================
QST_CMAKE_DIR="${HOST_BUILD}/lib/cmake/Qt6ShaderToolsTools"
if [ ! -f "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfig.cmake" ]; then
mkdir -p "${QST_CMAKE_DIR}"
QSB_PATH="${HOST_BUILD}/bin/qsb"
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfig.cmake" << 'EOF_CFG'
get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../" ABSOLUTE)
macro(set_and_check _var _file)
set(${_var} "${_file}")
if(NOT EXISTS "${_file}")
message(FATAL_ERROR "File or directory ${_file} referenced by variable ${_var} does not exist !")
endif()
endmacro()
macro(check_required_components _NAME)
foreach(comp ${${_NAME}_FIND_COMPONENTS})
if(NOT ${_NAME}_${comp}_FOUND)
if(${_NAME}_FIND_REQUIRED_${comp})
set(${_NAME}_FOUND FALSE)
endif()
endif()
endforeach()
endmacro()
cmake_minimum_required(VERSION 3.16...3.21)
include(CMakeFindDependencyMacro)
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsDependencies.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsDependencies.cmake")
endif()
if(NOT DEFINED "Qt6ShaderToolsTools_FOUND")
set("Qt6ShaderToolsTools_FOUND" TRUE)
endif()
set(__qt_ShaderToolsTools_should_include_targets_code "TRUE")
if(__qt_ShaderToolsTools_should_include_targets_code)
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargetsPrecheck.cmake")
endif()
if(NOT __qt_ShaderToolsTools_skip_include_targets_file
AND Qt6ShaderToolsTools_FOUND
AND __qt_ShaderToolsTools_should_include_targets_code)
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsAdditionalTargetInfo.cmake")
if(NOT QT_NO_CREATE_VERSIONLESS_TARGETS)
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsVersionlessTargets.cmake")
endif()
set(__qt_ShaderToolsTools_targets_file_included ON)
endif()
if(__qt_ShaderToolsTools_targets_file_included AND Qt6ShaderToolsTools_FOUND)
__qt_internal_promote_target_to_global(Qt6::qsb)
endif()
set(Qt6ShaderToolsTools_TARGETS "Qt6::qsb")
EOF_CFG
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargets.cmake" << 'EOF_TGT'
cmake_policy(PUSH)
cmake_policy(VERSION 2.8.3...4.1)
set(CMAKE_IMPORT_FILE_VERSION 1)
set(_targets_defined "")
set(_targets_not_defined "")
set(_expected_targets "")
foreach(_expected IN ITEMS Qt6::qsb)
list(APPEND _expected_targets "${_expected}")
if(TARGET "${_expected}")
list(APPEND _targets_defined "${_expected}")
else()
list(APPEND _targets_not_defined "${_expected}")
endif()
endforeach()
unset(_expected)
if(_targets_defined STREQUAL _expected_targets)
cmake_policy(POP)
return()
endif()
unset(_targets_defined)
unset(_targets_not_defined)
unset(_expected_targets)
get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
if(_IMPORT_PREFIX STREQUAL "/")
set(_IMPORT_PREFIX "")
endif()
add_executable(Qt6::qsb IMPORTED)
set_target_properties(Qt6::qsb PROPERTIES
COMPATIBLE_INTERFACE_STRING "QT_MAJOR_VERSION"
INTERFACE_QT_MAJOR_VERSION "6"
)
file(GLOB _cfg_files "${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsTargets-*.cmake")
foreach(_cfg IN LISTS _cfg_files)
include("${_cfg}")
endforeach()
unset(_cfg)
unset(_cfg_files)
unset(_IMPORT_PREFIX)
unset(CMAKE_IMPORT_FILE_VERSION)
cmake_policy(POP)
EOF_TGT
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargets-release.cmake" << EOF_REL
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(Qt6::qsb PROPERTIES
IMPORTED_LOCATION_RELEASE "${QSB_PATH}"
)
set(_cmake_import_check_targets "Qt6::qsb")
set(_cmake_import_check_files_for_Qt6::qsb "${QSB_PATH}")
EOF_REL
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsTargetsPrecheck.cmake" << 'EOF_PRE'
_qt_internal_should_include_targets(
TARGETS qsb
NAMESPACE Qt6::
OUT_VAR_SHOULD_SKIP __qt_ShaderToolsTools_skip_include_targets_file
)
EOF_PRE
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsAdditionalTargetInfo.cmake" << 'EOF_ADD'
if(NOT DEFINED QT_DEFAULT_IMPORT_CONFIGURATION)
set(QT_DEFAULT_IMPORT_CONFIGURATION RELEASE)
endif()
get_target_property(_qt_loc Qt6::qsb IMPORTED_LOCATION_RELEASE)
get_target_property(_qt_loc_def Qt6::qsb IMPORTED_LOCATION_${QT_DEFAULT_IMPORT_CONFIGURATION})
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
if(_qt_loc)
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION_RELWITHDEBINFO "${_qt_loc}")
endif()
set_property(TARGET Qt6::qsb APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
if(_qt_loc)
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION_MINSIZEREL "${_qt_loc}")
endif()
if(_qt_loc_def)
set_property(TARGET Qt6::qsb PROPERTY IMPORTED_LOCATION "${_qt_loc_def}")
endif()
EOF_ADD
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsVersionlessTargets.cmake" << 'EOF_VER'
foreach(__qt_tool qsb)
if(NOT TARGET Qt::${__qt_tool} AND TARGET Qt6::${__qt_tool})
add_executable(Qt::${__qt_tool} IMPORTED GLOBAL)
foreach(__qt_cfg IMPORTED_LOCATION IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_RELWITHDEBINFO IMPORTED_LOCATION_MINSIZEREL IMPORTED_LOCATION_DEBUG)
get_target_property(__qt_loc Qt6::${__qt_tool} ${__qt_cfg})
if(__qt_loc AND EXISTS "${__qt_loc}")
break()
endif()
endforeach()
set_target_properties(Qt::${__qt_tool} PROPERTIES IMPORTED_LOCATION "${__qt_loc}")
endif()
endforeach()
EOF_VER
printf 'set(Qt6ShaderToolsTools_FOUND TRUE)\n' \
> "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsDependencies.cmake"
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfigVersionImpl.cmake" << 'EOF_VIMPL'
set(PACKAGE_VERSION "6.11.1")
if(PACKAGE_FIND_VERSION VERSION_LESS_EQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_COMPATIBLE TRUE)
else()
set(PACKAGE_VERSION_COMPATIBLE FALSE)
endif()
if(NOT PACKAGE_VERSION_COMPATIBLE)
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
EOF_VIMPL
cat > "${QST_CMAKE_DIR}/Qt6ShaderToolsToolsConfigVersion.cmake" << 'EOF_VER2'
include("${CMAKE_CURRENT_LIST_DIR}/Qt6ShaderToolsToolsConfigVersionImpl.cmake")
EOF_VER2
echo "Generated Qt6ShaderToolsTools cmake package"
fi
# ============================================================
# Step 2: Cross-compile qtshadertools for Redox
# ============================================================
redbear_qt_reset_cmake_cache_dir
cmake "${COOKBOOK_SOURCE}" \
@@ -34,7 +249,9 @@ cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}" \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-DQT_BUILD_TOOLS_BY_DEFAULT=OFF \
-DQT_GENERATE_SBOM=OFF \
-DBUILD_SHARED_LIBS=ON \
-DBUILD_TESTING=OFF \
-DBUILD_EXAMPLES=OFF \
@@ -61,7 +278,20 @@ PY
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
# Supplemental: copy libraries and plugins to ensure symlinks present
cp -f "${COOKBOOK_SOURCE}/tools/qsb/Qt6ShaderToolsMacros.cmake" \
"${COOKBOOK_STAGE}/usr/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsMacros.cmake" 2>/dev/null || true
cp -f "${COOKBOOK_SOURCE}/tools/qsb/Qt6ShaderToolsMacros.cmake" \
"${HOST_BUILD}/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsMacros.cmake" 2>/dev/null || true
QST_CFG="${COOKBOOK_STAGE}/usr/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsConfig.cmake"
if [ -f "${QST_CFG}" ] && ! grep -q "ShaderToolsMacros" "${QST_CFG}"; then
sed -i 's/foreach(extra_cmake_include )/foreach(extra_cmake_include Qt6ShaderToolsMacros.cmake)/' "${QST_CFG}"
fi
QST_CFG_HOST="${HOST_BUILD}/lib/cmake/Qt6ShaderTools/Qt6ShaderToolsConfig.cmake"
if [ -f "${QST_CFG_HOST}" ] && ! grep -q "ShaderToolsMacros" "${QST_CFG_HOST}"; then
sed -i 's/foreach(extra_cmake_include )/foreach(extra_cmake_include Qt6ShaderToolsMacros.cmake)/' "${QST_CFG_HOST}"
fi
for lib in lib/libQt6*.so*; do
[ -f "${lib}" ] && cp -an "${lib}" "${COOKBOOK_STAGE}/usr/lib/"
done
+2
View File
@@ -97,6 +97,8 @@ cmake "${COOKBOOK_SOURCE}" \
-DCMAKE_EXE_LINKER_FLAGS="-lc -lffi" \
-DCMAKE_C_STANDARD_LIBRARIES="-lffi" \
-DCMAKE_CXX_STANDARD_LIBRARIES="-lffi" \
-DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON \
-DQT_GENERATE_SBOM=OFF \
-Wno-dev
cmake --build . -j"${COOKBOOK_MAKE_JOBS}"
@@ -107,6 +107,15 @@ pub fn rapl_power_watts(
(watts, curr)
}
/// Read a RAPL energy counter for an arbitrary domain MSR (PP0 core,
/// PP1 uncore, DRAM). Same energy-unit conversion as package domain.
/// Returns `None` when the MSR is unavailable.
pub fn read_rapl_domain_energy(msr: u32) -> Option<(u64, Instant)> {
let unit = crate::msr::RaplUnit::read(0)?;
let raw = crate::msr::read_rapl_energy(0, msr)?;
Some((unit.energy_to_uj(raw), Instant::now()))
}
pub fn detect_cpus() -> Vec<u32> {
// Redox exposes the CPU count via the sys:cpu scheme file
// (kernel/src/scheme/sys/cpu.rs) as "CPUs: N\n...". /dev/cpu/ does
@@ -79,6 +79,7 @@ pub struct CpuRow {
pub prev_load: (u64, u64),
pub load_history: VecDeque<u8>,
pub core_type: CoreType,
pub hwp: Option<crate::msr::HwpInfo>,
}
impl CpuRow {
@@ -138,6 +139,10 @@ pub meminfo: crate::meminfo::MemInfo,
pub prev_refresh_secs: f64,
pub process_sort: crate::process::SortMode,
pub process_filter: String,
/// Scheduler statistics from `/scheme/sys/stat` (Redox) or
/// `/proc/stat` (Linux). Read every tick — it's a single
/// file read. Fields degrade to `None` when unavailable.
pub sched_stats: crate::sched::SchedStats,
/// When true, render the Process tab as a tree (parents above
/// children, prefixed with `├─ ` / `└─ ` connector characters).
/// Toggled by the `T` hotkey. Sort modes are honored within
@@ -155,6 +160,12 @@ pub meminfo: crate::meminfo::MemInfo,
rapl_prev: Option<(u64, std::time::Instant)>,
/// Why RAPL package power is unavailable (empty if working).
pub rapl_status: String,
pub pp0_power_w: Option<f64>,
rapl_pp0_prev: Option<(u64, std::time::Instant)>,
pub pp1_power_w: Option<f64>,
rapl_pp1_prev: Option<(u64, std::time::Instant)>,
pub dram_power_w: Option<f64>,
rapl_dram_prev: Option<(u64, std::time::Instant)>,
/// Per-PID IO rate history (normalized KiB/s samples,
/// 0..=255 against the per-history max). Used by the
/// Process tab to render a small sparkline per process.
@@ -229,6 +240,7 @@ pub meminfo: crate::meminfo::MemInfo,
pub interval_input: Option<String>,
pub current_tab: TabId,
pub bench_start_time: Option<Instant>,
pub collector_stats: crate::collector::CollectorStats,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -325,6 +337,7 @@ impl App {
prev_load: (0, 0),
load_history: VecDeque::with_capacity(LOAD_HISTORY_LEN),
core_type: type_for(id),
hwp: None,
}
})
.collect();
@@ -393,11 +406,18 @@ impl App {
prev_refresh_secs: 0.0,
process_sort: crate::process::SortMode::default(),
process_filter: String::new(),
sched_stats: crate::sched::SchedStats::default(),
process_tree: false,
folded: std::collections::BTreeSet::new(),
pkg_power_w: None,
rapl_prev: None,
rapl_status: "probing...".into(),
pp0_power_w: None,
rapl_pp0_prev: None,
pp1_power_w: None,
rapl_pp1_prev: None,
dram_power_w: None,
rapl_dram_prev: None,
io_history: std::collections::BTreeMap::new(),
last_clicked_cpu: None,
sort_ascending: false,
@@ -416,6 +436,7 @@ impl App {
max_history_pids: 500,
pid_last_seen: std::collections::BTreeMap::new(),
refresh_tick: 0,
collector_stats: crate::collector::CollectorStats::default(),
};
// v1.40: load persisted session state and apply.
// Missing or malformed session falls back to the
@@ -605,7 +626,17 @@ impl App {
self.prev_refresh_secs = now_secs;
}
for row in &mut self.cpus {
// Scheduler stats (context switches, IRQs). One file read
// — cheap enough for every tick.
self.sched_stats = crate::sched::SchedStats::read();
let pkg_temps: Vec<Option<u32>> = self
.cpus
.iter()
.map(|row| self.sensors.pkg_temp_c(row.id))
.collect();
self.collector_stats = crate::collector::collect(&mut self.cpus, |i, row| {
if let Some(status) = read_thermal_status(row.id) {
row.temp_c = if status & THERM_STATUS_READOUT_VALID != 0 {
Some(((status & THERM_STATUS_TEMP_MASK) >> 16) as u32)
@@ -616,11 +647,7 @@ impl App {
row.critical = status & THERM_STATUS_CRITICAL != 0;
row.power_limit = status & THERM_STATUS_POWER_LIMIT != 0;
} else {
// IA32_THERM_STATUS is Intel-only. On AMD, fall back to
// k10temp Tctl (the package control temperature), which
// applies to all CPUs on the same package. This is the
// canonical hwmon-based CPU temperature for Zen and later.
row.temp_c = self.sensors.pkg_temp_c(row.id);
row.temp_c = pkg_temps[i];
row.prochot = false;
row.critical = false;
row.power_limit = false;
@@ -628,16 +655,13 @@ impl App {
if let Some(ctl) = read_current_perf_ctl(row.id) {
let state = ((ctl & PERF_CTL_STATE_MASK) >> 8) as u8;
row.current_idx = row.pstates.iter().position(|p| ((p.ctl & PERF_CTL_STATE_MASK) >> 8) as u8 == state);
let cur = row.current_idx.and_then(|i| row.pstates.get(i));
let cur = row.current_idx.and_then(|idx| row.pstates.get(idx));
row.freq_khz = cur.map(|p| p.freq_khz).unwrap_or(0);
row.current_power_mw = cur.map(|p| p.power_mw);
} else {
// MSR unavailable — try sysfs cpufreq (Linux fallback, like htop).
row.current_idx = None;
row.freq_khz = read_cpu_freq_khz_sysfs(row.id).unwrap_or(0);
row.current_power_mw = None;
// Match current frequency against synthetic P-state table
// to determine which P-state we're in (intel_pstate fallback).
if row.freq_khz > 0 && !row.pstates.is_empty() {
row.current_idx = row
.pstates
@@ -646,7 +670,7 @@ impl App {
.min_by_key(|(_, p)| {
(p.freq_khz as i64 - row.freq_khz as i64).unsigned_abs()
})
.map(|(i, _)| i);
.map(|(idx, _)| idx);
}
}
row.load_pct = read_load(row.id, &mut row.prev_load) * 100.0;
@@ -655,7 +679,8 @@ impl App {
}
row.load_history
.push_back(row.load_pct.clamp(0.0, 100.0) as u8);
}
row.hwp = crate::msr::HwpInfo::read(row.id);
});
// Read package power from RAPL powercap (Intel/AMD). Requires
// kernel CONFIG_POWERCAP and intel_rapl/amd_energy driver.
// Falls back to MSR-based power or sysfs power values.
@@ -679,6 +704,36 @@ impl App {
// No MSR device at all — RAPL unsupported (QEMU, old CPU).
self.rapl_status = "n/a (unsupported)".into();
}
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_PP0_ENERGY_STATUS) {
match self.rapl_pp0_prev {
Some(prev) => {
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
self.rapl_pp0_prev = Some(next);
if w > 0.0 { self.pp0_power_w = Some(w); }
}
None => self.rapl_pp0_prev = Some(curr),
}
}
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_PP1_ENERGY_STATUS) {
match self.rapl_pp1_prev {
Some(prev) => {
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
self.rapl_pp1_prev = Some(next);
if w > 0.0 { self.pp1_power_w = Some(w); }
}
None => self.rapl_pp1_prev = Some(curr),
}
}
if let Some(curr) = crate::acpi::read_rapl_domain_energy(crate::msr::MSR_DRAM_ENERGY_STATUS) {
match self.rapl_dram_prev {
Some(prev) => {
let (w, next) = crate::acpi::rapl_power_watts(curr, prev);
self.rapl_dram_prev = Some(next);
if w > 0.0 { self.dram_power_w = Some(w); }
}
None => self.rapl_dram_prev = Some(curr),
}
}
// Re-read cpufreq state (catches external governor changes).
self.cpufreq.refresh();
if let Some(pkg) = read_package_thermal_status(self.cpus[0].id) {
@@ -0,0 +1,129 @@
//! Multi-threaded per-CPU data collector.
//!
//! Spawns one worker thread per CPU using `std::thread::scope` (which
//! exercises `pthread_create` via relibc). Workers are barrier-synchronised
//! (`std::sync::Barrier` → futex wake/wait) and attempt CPU pinning
//! (`sched_setaffinity` via the kernel proc scheme). Thread names are
//! set via `pthread_setname_np` (through `Builder::name`).
//!
//! This module exists to visibly demonstrate Red Bear OS Phase 0
//! threading infrastructure: per-CPU scheduler placement, futex
//! sharding, and the POSIX scheduling API surface.
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Barrier;
use std::thread;
use std::time::Instant;
#[derive(Clone, Debug, Default)]
pub struct CollectorStats {
pub thread_count: usize,
pub pinned_count: usize,
pub elapsed_us: u64,
pub barrier_size: usize,
}
#[cfg(target_os = "linux")]
unsafe extern "C" {
fn sched_setaffinity(pid: i32, cpusetsize: usize, mask: *const u64) -> i32;
}
fn try_pin_cpu(cpu_id: u32) -> bool {
if cpu_id >= 64 {
return false;
}
let mask: u64 = 1u64 << cpu_id;
let bytes = mask.to_le_bytes();
if std::fs::write("/proc/self/sched-affinity", bytes).is_ok() {
return true;
}
#[cfg(target_os = "linux")]
unsafe {
sched_setaffinity(0, std::mem::size_of::<u64>(), &mask) == 0
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
pub fn collect<T, F>(items: &mut [T], work: F) -> CollectorStats
where
F: Fn(usize, &mut T) + Sync,
T: Send,
{
let n = items.len();
if n <= 1 {
if let Some((i, item)) = items.iter_mut().enumerate().next() {
work(i, item);
}
return CollectorStats {
thread_count: if n == 1 { 1 } else { 0 },
..Default::default()
};
}
let barrier = Barrier::new(n);
let pinned = AtomicUsize::new(0);
let spawned = AtomicUsize::new(0);
let start = Instant::now();
thread::scope(|s| {
for (i, item) in items.iter_mut().enumerate() {
let barrier = &barrier;
let work = &work;
let pinned = &pinned;
let spawned = &spawned;
s.spawn(move || {
spawned.fetch_add(1, Ordering::Relaxed);
if try_pin_cpu(i as u32) {
pinned.fetch_add(1, Ordering::Relaxed);
}
barrier.wait();
work(i, item);
});
}
});
CollectorStats {
thread_count: spawned.load(Ordering::Relaxed),
pinned_count: pinned.load(Ordering::Relaxed),
elapsed_us: start.elapsed().as_micros() as u64,
barrier_size: n,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_item_runs_inline() {
let mut data = [0u64; 1];
let stats = collect(&mut data, |i, v| {
*v = i as u64 + 1;
});
assert_eq!(data[0], 1);
assert_eq!(stats.thread_count, 1);
}
#[test]
fn all_items_updated() {
let mut data = vec![0u64; 8];
let stats = collect(&mut data, |i, v| {
*v = (i * i) as u64;
});
for (i, v) in data.iter().enumerate() {
assert_eq!(*v, (i * i) as u64, "item {i} not updated");
}
assert!(stats.thread_count >= 1);
}
#[test]
fn empty_slice_is_safe() {
let mut data: Vec<u64> = vec![];
let stats = collect(&mut data, |_, _| {});
assert_eq!(stats.thread_count, 0);
}
}
@@ -47,6 +47,7 @@ mod acpi;
mod app;
mod battery;
mod bench;
mod collector;
mod config;
mod cpufreq;
mod cpuid;
@@ -59,6 +60,7 @@ mod pid_detail;
mod platform;
mod process;
mod render;
mod sched;
mod sensor;
mod session;
mod smart;
@@ -344,7 +346,7 @@ fn main() -> io::Result<()> {
match app.current_tab {
TabId::PerCpu => {
f.render_stateful_widget(
render_cpu_table(&app.cpus, app.expanded_cpu, focused_panel == 1, app.pkg_power_w, &app.rapl_status),
render_cpu_table(&app.cpus, app.expanded_cpu, focused_panel == 1, app.pkg_power_w, &app.rapl_status, &app.sched_stats.per_cpu_irqs),
body_area,
&mut app.table_state,
);
@@ -280,4 +280,67 @@ impl PackageThermal {
if self.hfi { parts.push("HFI"); }
if parts.is_empty() { "".to_string() } else { parts.join(" ") }
}
}
// ---------------------------------------------------------------------------
// HWP (Hardware P-states / Intel Speed Shift)
// ---------------------------------------------------------------------------
/// Intel HWP (Hardware P-states) capability and request data read
/// from per-CPU MSRs. `None` from `HwpInfo::read()` means HWP is
/// not supported (AMD, pre-Skylake Intel, or QEMU without MSR
/// passthrough).
#[derive(Clone, Debug)]
pub struct HwpInfo {
/// Whether HWP has been enabled (IA32_PM_ENABLE bit 0).
pub enabled: bool,
/// Highest performance level the hardware can deliver
/// (IA32_HWP_CAPABILITIES bits 31:24). Arbitrary units — not
/// directly MHz, though roughly proportional.
pub max_perf: u8,
/// Guaranteed performance level (bits 23:16).
pub guaranteed_perf: u8,
/// Most efficient performance level (bits 15:8).
pub efficient_perf: u8,
/// Minimum requested performance (IA32_HWP_REQUEST bits 23:16).
pub min_request: u8,
/// Maximum requested performance (bits 31:24).
pub max_request: u8,
/// Desired performance target (bits 15:8).
pub desired_perf: u8,
/// Energy Performance Preference (bits 7:0). Range 0255:
/// 0 = all-out performance, 128 = balanced, 255 = max power
/// savings.
pub epp: u8,
}
impl HwpInfo {
/// Read HWP data for `cpu`. Returns `None` if any of the three
/// required MSRs are unreadable.
pub fn read(cpu: u32) -> Option<Self> {
let enable = read_msr(cpu, IA32_PM_ENABLE)?;
let caps = read_msr(cpu, IA32_HWP_CAPABILITIES)?;
let req = read_msr(cpu, IA32_HWP_REQUEST)?;
Some(Self {
enabled: enable & 1 != 0,
max_perf: ((caps >> 24) & 0xFF) as u8,
guaranteed_perf: ((caps >> 16) & 0xFF) as u8,
efficient_perf: ((caps >> 8) & 0xFF) as u8,
min_request: ((req >> 16) & 0xFF) as u8,
max_request: ((req >> 24) & 0xFF) as u8,
desired_perf: ((req >> 8) & 0xFF) as u8,
epp: (req & 0xFF) as u8,
})
}
/// Human-readable label for the EPP value.
pub fn epp_label(&self) -> &'static str {
match self.epp {
0..=25 => "Performance",
26..=100 => "Bal. Perf",
101..=155 => "Balanced",
156..=220 => "Bal. Power",
221..=255 => "Power",
}
}
}
@@ -461,6 +461,7 @@ pub struct ProcessInfo {
/// Process panel renders a compact range-string form
/// (e.g. "0-3,5,7-11") for at-a-glance scanning.
pub cpu_affinity: Option<Vec<u32>>,
pub sched_policy: String,
}
impl ProcessInfo {
@@ -834,6 +835,7 @@ fn parse_stat_line(line: &str) -> Option<ProcessInfo> {
thread_io_read_rate_kbs: None,
thread_io_write_rate_kbs: None,
cpu_affinity,
sched_policy: String::new(),
})
}
@@ -844,9 +846,31 @@ fn read_process(pid: u32) -> Option<ProcessInfo> {
if info.comm.is_empty() || info.comm == "?" {
info.comm = read_comm(pid);
}
info.sched_policy = derive_sched_policy(pid, info.priority);
Some(info)
}
fn derive_sched_policy(pid: u32, priority: i64) -> String {
if let Ok(data) = fs::read(format!("/proc/{}/sched-policy", pid)) {
if let Some(&policy) = data.first() {
return match policy {
0 => "FIFO",
1 => "RR",
2 => "OTHER",
_ => "?",
}
.into();
}
}
if priority > 0 && priority < 40 {
"OTHER".into()
} else if priority >= 40 && priority < 100 {
"RT".into()
} else {
"OTHER".into()
}
}
impl ProcInfo {
pub fn read() -> Self {
Self::read_sorted(SortMode::default())
@@ -386,6 +386,67 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
if any_pl { "PL ".set_style(theme::POWER_LIMIT_FLAG) } else { "PL ".set_style(theme::VALUE_OFF) },
]));
{
let s = &app.sched_stats;
let mut parts: Vec<ratatui::text::Span> = Vec::new();
parts.push("Sched: ".set_style(theme::LABEL));
let mut wrote = false;
if let Some(v) = s.context_switches {
parts.push(format!("switches={v} ").set_style(theme::VALUE));
wrote = true;
}
if let Some(v) = s.contexts_created {
parts.push(format!("created={v} ").set_style(theme::VALUE));
wrote = true;
}
if let Some(v) = s.contexts_running {
parts.push(format!("running={v} ").set_style(theme::VALUE));
wrote = true;
}
if let Some(v) = s.contexts_blocked {
parts.push(format!("blocked={v} ").set_style(theme::VALUE));
wrote = true;
}
if let Some(v) = s.total_irqs {
parts.push(format!("IRQs={v} ").set_style(theme::VALUE));
wrote = true;
}
if !s.per_cpu_steals.is_empty() {
let total_steals: u64 = s.per_cpu_steals.iter().sum();
parts.push(format!("steals={total_steals} ").set_style(theme::VALUE));
wrote = true;
}
if !s.per_cpu_queue_depth.is_empty() {
let total_qd: u64 = s.per_cpu_queue_depth.iter().sum();
let avg_qd = total_qd / s.per_cpu_queue_depth.len() as u64;
parts.push(format!("avg_qd={avg_qd}").set_style(theme::VALUE));
wrote = true;
}
if wrote {
lines.push(Line::from(parts));
}
}
if app.pkg_power_w.is_some() || app.pp0_power_w.is_some()
|| app.pp1_power_w.is_some() || app.dram_power_w.is_some()
{
let mut parts: Vec<ratatui::text::Span> = Vec::new();
parts.push("Power: ".set_style(theme::LABEL));
if let Some(w) = app.pkg_power_w {
parts.push(format!("Pkg {w:.1}W ").set_style(theme::VALUE));
}
if let Some(w) = app.pp0_power_w {
parts.push(format!("Core {w:.1}W ").set_style(theme::VALUE));
}
if let Some(w) = app.pp1_power_w {
parts.push(format!("Uncore {w:.1}W ").set_style(theme::VALUE));
}
if let Some(w) = app.dram_power_w {
parts.push(format!("DRAM {w:.1}W").set_style(theme::VALUE));
}
lines.push(Line::from(parts));
}
// OS identity (matches cpu-x System tab)
if app.os_info.available {
lines.push(Line::from(vec![
@@ -435,8 +496,26 @@ pub fn render_system_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
}
}
let cs = &app.collector_stats;
if cs.thread_count > 1 {
lines.push(Line::from(vec![
"Collector: ".set_style(theme::LABEL),
format!("{} threads", cs.thread_count).set_style(theme::VALUE),
" pinned: ".set_style(theme::LABEL),
format!("{}/{}", cs.pinned_count, cs.barrier_size).set_style(theme::VALUE),
" barrier: ".set_style(theme::LABEL),
format!("{}", cs.barrier_size).set_style(theme::VALUE),
" last: ".set_style(theme::LABEL),
format!("{:.2} ms", cs.elapsed_us as f64 / 1000.0).set_style(theme::VALUE),
]));
} else {
lines.push(Line::from(vec![
"Collector: ".set_style(theme::LABEL),
"single-threaded".set_style(theme::VALUE_OFF),
]));
}
lines.push(Line::from(vec![
"Benchmark: ".set_style(theme::LABEL),
if app.bench_line.is_empty() { "(idle)".set_style(theme::VALUE_OFF) } else { app.bench_line.as_str().set_style(theme::VALUE) },
]));
Paragraph::new(lines)
@@ -946,7 +1025,7 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
_ => "RSS",
};
let header_str = format!(
" PID STATE PRIO NI THR CPU% IO RATE {:<11} T-IO T-IO/s IO-RATE CPU% RSS AFF COMM",
" PID STATE SCHED PRIO NI THR CPU% IO RATE {:<11} T-IO T-IO/s IO-RATE CPU% RSS AFF COMM",
mem_header
);
lines.push(Line::from(vec![
@@ -1058,10 +1137,11 @@ pub fn render_process_panel<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
theme::VALUE
};
lines.push(Line::from(format!(
" {}{:<7} {} {:<4} {:<3} {:<3} {:<6} {:<11} {:<11} {:<11} {:<11} {:<11} {:<12} {:<6} {:<5} {:<3} {}",
" {}{:<7} {} {:<5} {:<4} {:<3} {:<3} {:<6} {:<11} {:<11} {:<11} {:<11} {:<11} {:<12} {:<6} {:<5} {:<3} {}",
prefix,
p.pid,
p.state,
p.sched_policy,
p.priority,
p.nice,
p.num_threads,
@@ -1405,6 +1485,7 @@ pub fn render_cpu_table<'a>(
focused: bool,
pkg_power_w: Option<f64>,
rapl_status: &'a str,
per_cpu_irqs: &'a [u64],
) -> Table<'a> {
let header = Row::new(vec![
"CPU".set_style(theme::LABEL),
@@ -1415,6 +1496,7 @@ pub fn render_cpu_table<'a>(
"State".set_style(theme::LABEL),
"Flags".set_style(theme::LABEL),
"Load % (30s)".set_style(theme::LABEL),
"IRQs".set_style(theme::LABEL),
])
.height(1);
let rows: Vec<Row> = cpus
@@ -1483,6 +1565,15 @@ pub fn render_cpu_table<'a>(
spark_text.set_style(Style::new().fg(lcolor)),
format!(" {load_label}").set_style(Style::new().fg(lcolor).bold()),
])),
{
let irq_val = per_cpu_irqs.get(cpu.id as usize).copied();
Cell::from(
irq_val
.map(|v| format!("{v}"))
.unwrap_or_else(|| "".into())
.set_style(if irq_val.is_some() { theme::VALUE } else { theme::VALUE_OFF }),
)
},
])
})
.collect();
@@ -1511,6 +1602,32 @@ pub fn render_cpu_table<'a>(
"".set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
]));
}
if let Some(ref hwp) = cpu.hwp {
let s = sub_style;
rows.push(Row::new(vec![
" HWP caps".set_style(s),
format!("eff={} guar={} max={}", hwp.efficient_perf, hwp.guaranteed_perf, hwp.max_perf).set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
format!("{}", if hwp.enabled { "ON" } else { "OFF" }).set_style(if hwp.enabled { Style::new().green() } else { theme::VALUE_OFF }),
"".set_style(s),
"".set_style(s),
"".set_style(s),
]));
rows.push(Row::new(vec![
" HWP req".set_style(s),
format!("min={} max={} des={} EPP={} ({})", hwp.min_request, hwp.max_request, hwp.desired_perf, hwp.epp, hwp.epp_label()).set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
"".set_style(s),
]));
}
}
@@ -1526,6 +1643,7 @@ pub fn render_cpu_table<'a>(
Constraint::Length(8),
Constraint::Length(6),
Constraint::Length(SPARK_WIDTH as u16 + 6),
Constraint::Length(8),
],
)
.header(header)
@@ -1671,7 +1789,7 @@ pub fn snapshot(app: &App, width: u16, height: u16) -> String {
);
f.render_widget(render_header(app, true), header_area);
f.render_stateful_widget(
render_cpu_table(&app.cpus, app.expanded_cpu, true, app.pkg_power_w, &app.rapl_status),
render_cpu_table(&app.cpus, app.expanded_cpu, true, app.pkg_power_w, &app.rapl_status, &app.sched_stats.per_cpu_irqs),
table_area,
&mut state,
);
@@ -0,0 +1,230 @@
//! Scheduler statistics from `/scheme/sys/stat` (Redox) or
//! `/proc/stat` (Linux).
//!
//! Redox's sys scheme exposes a richer set of scheduler counters than
//! Linux: per-CPU IRQ counts, context-switch totals, and scheduler
//! context state (created / running / blocked). On Linux, only the
//! `ctxt` (context switches) and `intr` (total interrupts) lines are
//! available from `/proc/stat`; per-CPU IRQ distribution lives in
//! `/proc/interrupts` and is not read here (kept cheap — one file
//! read).
//!
//! All fields degrade to `None` / empty when the data source is absent
//! or unparseable, so the TUI never panics on a missing scheme.
use std::fs;
/// Scheduler and IRQ statistics. Fields are `Option` because the
/// underlying data source may not provide all of them (Linux vs
/// Redox). `Default` gives all-`None` / empty, suitable as a
/// pre-probe placeholder.
#[derive(Clone, Debug, Default)]
pub struct SchedStats {
pub context_switches: Option<u64>,
pub contexts_created: Option<u64>,
pub contexts_running: Option<u64>,
pub contexts_blocked: Option<u64>,
pub per_cpu_irqs: Vec<u64>,
pub total_irqs: Option<u64>,
pub per_cpu_switches: Vec<u64>,
pub per_cpu_steals: Vec<u64>,
pub per_cpu_queue_depth: Vec<u64>,
}
impl SchedStats {
/// Read scheduler stats from the first available data source.
///
/// Tries `/scheme/sys/stat` (Redox) first, then falls back to
/// `/proc/stat` (Linux). Returns a `SchedStats` with `None` /
/// empty fields for anything the source does not provide.
pub fn read() -> Self {
let mut stats = Self::default();
if let Ok(data) = fs::read_to_string("/scheme/sys/stat") {
stats = Self::parse_redox(&data);
} else if let Ok(data) = fs::read_to_string("/proc/stat") {
stats = Self::parse_linux(&data);
}
if let Ok(data) = fs::read_to_string("/scheme/sys/sched") {
Self::merge_sched_detail(&mut stats, &data);
}
stats
}
/// Parse the Redox `/scheme/sys/stat` format:
/// ```text
/// cpu <u> <n> <k> <i> <irq>
/// cpu0 ...
/// IRQs <total> <cpu0_irqs> <cpu1_irqs> ...
/// boot_time: <secs>
/// context_switches: <num>
/// contexts_created: <num>
/// contexts_running: <num>
/// contexts_blocked: <num>
/// ```
fn parse_redox(data: &str) -> Self {
let mut stats = Self::default();
for line in data.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("IRQs ") {
// "IRQs <total> <cpu0> <cpu1> ..."
let nums: Vec<u64> = rest
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
// First number is the total; remaining are per-CPU.
if let Some(&total) = nums.first() {
stats.total_irqs = Some(total);
stats.per_cpu_irqs = nums[1..].to_vec();
}
} else if let Some(rest) = trimmed.strip_prefix("context_switches:") {
stats.context_switches = rest.trim().parse().ok();
} else if let Some(rest) = trimmed.strip_prefix("contexts_created:") {
stats.contexts_created = rest.trim().parse().ok();
} else if let Some(rest) = trimmed.strip_prefix("contexts_running:") {
stats.contexts_running = rest.trim().parse().ok();
} else if let Some(rest) = trimmed.strip_prefix("contexts_blocked:") {
stats.contexts_blocked = rest.trim().parse().ok();
}
}
stats
}
/// Parse the Linux `/proc/stat` format. Only `ctxt` (context
/// switches) and `intr` (total interrupts) are available.
/// Per-CPU IRQ distribution is in `/proc/interrupts` and is not
/// read here.
///
/// ```text
/// cpu 1000 200 3000 50000 ...
/// cpu0 ...
/// intr 200 50 50 ... (first number = total)
/// ctxt 5000
/// btime 1000
/// ```
fn parse_linux(data: &str) -> Self {
let mut stats = Self::default();
for line in data.lines() {
let trimmed = line.trim();
// "ctxt <number>" — context switches since boot.
if let Some(rest) = trimmed.strip_prefix("ctxt ") {
stats.context_switches = rest.trim().parse().ok();
}
// "intr <total> <irq0> <irq1> ..." — first field is total.
if let Some(rest) = trimmed.strip_prefix("intr ") {
if let Some(total_str) = rest.split_whitespace().next() {
stats.total_irqs = total_str.parse().ok();
}
}
}
stats
}
fn merge_sched_detail(stats: &mut SchedStats, data: &str) {
for line in data.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("cpu") {
let parts: Vec<&str> = rest.split_whitespace().collect();
if parts.len() < 7 {
continue;
}
let switches: u64 = parts[2].parse().unwrap_or(0);
let steals: u64 = parts[4].parse().unwrap_or(0);
let qd: u64 = parts[6].parse().unwrap_or(0);
stats.per_cpu_switches.push(switches);
stats.per_cpu_steals.push(steals);
stats.per_cpu_queue_depth.push(qd);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_redox_all_fields() {
let data = "\
cpu 1000 200 3000 50000 100
cpu0 500 100 1500 25000 50
cpu1 500 100 1500 25000 50
IRQs 200 100 100
boot_time: 1000
context_switches: 5000
contexts_created: 100
contexts_running: 2
contexts_blocked: 0
";
let stats = SchedStats::parse_redox(data);
assert_eq!(stats.context_switches, Some(5000));
assert_eq!(stats.contexts_created, Some(100));
assert_eq!(stats.contexts_running, Some(2));
assert_eq!(stats.contexts_blocked, Some(0));
assert_eq!(stats.total_irqs, Some(200));
assert_eq!(stats.per_cpu_irqs, vec![100, 100]);
}
#[test]
fn parse_linux_context_switches_and_intr() {
let data = "\
cpu 1000 200 3000 50000 100 0 0 0 0 0
cpu0 500 100 1500 25000 50 0 0 0 0 0
intr 200 50 50 100
ctxt 5000
btime 1000
";
let stats = SchedStats::parse_linux(data);
assert_eq!(stats.context_switches, Some(5000));
assert_eq!(stats.total_irqs, Some(200));
// Per-CPU IRQs are NOT available from /proc/stat on Linux.
assert!(stats.per_cpu_irqs.is_empty());
// Redox-specific fields are not available.
assert_eq!(stats.contexts_created, None);
assert_eq!(stats.contexts_running, None);
assert_eq!(stats.contexts_blocked, None);
}
#[test]
fn default_is_all_none() {
let stats = SchedStats::default();
assert_eq!(stats.context_switches, None);
assert_eq!(stats.total_irqs, None);
assert!(stats.per_cpu_irqs.is_empty());
}
#[test]
fn parse_redox_missing_context_fields() {
// Only the IRQs line — no context_* lines.
let data = "IRQs 42 10 20 12\n";
let stats = SchedStats::parse_redox(data);
assert_eq!(stats.total_irqs, Some(42));
assert_eq!(stats.per_cpu_irqs, vec![10, 20, 12]);
assert_eq!(stats.context_switches, None);
}
#[test]
fn parse_redox_empty_irqs_line() {
let data = "cpu 100 0 0 100 0\ncontext_switches: 99\n";
let stats = SchedStats::parse_redox(data);
assert_eq!(stats.context_switches, Some(99));
assert_eq!(stats.total_irqs, None);
assert!(stats.per_cpu_irqs.is_empty());
}
#[test]
fn merge_sched_detail_parses_per_cpu() {
let mut stats = SchedStats::default();
let data = "\
cpu0 switches 1000 steals 5 queue_depth 3
cpu1 switches 2000 steals 2 queue_depth 1
total switches 3000 steals 7
";
SchedStats::merge_sched_detail(&mut stats, data);
assert_eq!(stats.per_cpu_switches, vec![1000, 2000]);
assert_eq!(stats.per_cpu_steals, vec![5, 2]);
assert_eq!(stats.per_cpu_queue_depth, vec![3, 1]);
}
}
+2 -2
View File
@@ -11,9 +11,9 @@ esac
# virtio-gl, native CPU, net boost
qemu-system-x86_64 -m 12G -smp 8 -device qemu-xhci -net nic,model=virtio -net user --enable-kvm -cpu host -display gtk,gl=on -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/RedBear-OS/build/x86_64/redbear-mini.iso,format=raw -device virtio-gpu-pci -enable-kvm -serial mon:stdio
qemu-system-x86_64 -m 2G -smp 8 -device qemu-xhci -net nic,model=virtio -net user --enable-kvm -cpu host -display gtk,gl=on -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/RedBear-OS/build/x86_64/redbear-mini.iso,format=raw -device virtio-gpu-pci -enable-kvm -serial mon:stdio
#qemu-system-x86_64 -m 12G -smp 8 -device qemu-xhci -net nic,model=virtio -net user --enable-kvm -cpu host -display gtk,gl=on -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd -drive file=/home/kellito/Builds/RedBear-OS/build/x86_64/redbear-full.iso,format=raw -device virtio-gpu-pci -enable-kvm -serial mon:stdio
#qemu-system-x86_64 -d guest_errors -name "Red Bear OS x86_64" -device qemu-xhci -smp 4 -m 2048 -bios /usr/share/edk2/x64/OVMF.4m.fd -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device e1000,netdev=net0,id=nic0 -netdev user,id=net0 -nographic -drive file=build/x86_64/redbear-mini/harddrive.img,format=raw,if=none,id=drv0 -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=build/x86_64/redbear-mini/extra.img,format=raw,if=none,id=drv1 -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host 2>&1 | tee /tmp/qemu-boot-full.log | tail -n 100
#qemu-system-x86_64 -d guest_errors -ame "Red Bear OS x86_64" -device qemu-xhci -smp 4 -m 2048 -bios /usr/share/edk2/x64/OVMF.4m.fd -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device e1000,netdev=net0,id=nic0 -netdev user,id=net0 -nographic -drive file=build/x86_64/redbear-mini/harddrive.img,format=raw,if=none,id=drv0 -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=build/x86_64/redbear-mini/extra.img,format=raw,if=none,id=drv1 -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host 2>&1 | tee /tmp/qemu-boot-full.log | tail -n 100