redbear-power: RAPL MSR constants, unit parsing, MSR-based energy reading
- msr.rs: add all Intel RAPL MSR addresses (0x606-0x64D) and AMD Zen equivalents (0xC0010299-0xC001029B), RaplUnit struct for unit register parsing with energy_to_uj/power_to_w conversion, read_rapl_energy() and read_rapl_energy_uj() functions - acpi.rs: read_rapl_package_energy() now tries MSR first (Intel then AMD PKG energy MSRs) with unit-based µJ conversion, falls back to Linux powercap sysfs - local/docs/RAPL-IMPLEMENTATION-PLAN.md: comprehensive 3-phase plan based on Linux 7.1 kernel analysis, Intel SDM, Fuchsia RFC-0203 patterns. Documents P0 blocker: /scheme/sys/msr/ not implemented in kernel
This commit is contained in:
@@ -0,0 +1,544 @@
|
||||
# RedBear OS RAPL Power Monitoring — Comprehensive Implementation Plan
|
||||
|
||||
**Version:** 1.1 (2026-06-28)
|
||||
**Status:** Draft — awaiting review
|
||||
**Linux Reference:** `local/reference/linux-7.1/drivers/powercap/`
|
||||
|
||||
## P0 Blocker: Kernel MSR Scheme Does Not Exist
|
||||
|
||||
**The `/scheme/sys/msr/{cpu}/0x{msr_hex}` path used by `redbear-power` and
|
||||
`cpufreqd` is NOT implemented in the kernel.** The kernel's `sys:` scheme handler
|
||||
has modules for `cpu`, `exe`, `irq`, `block`, `syscall`, `context`, `uname`,
|
||||
`fdstat`, `iostat`, `log`, `stat` — but NO `msr` module. The kernel uses
|
||||
`rdmsr`/`wrmsr` internally (via the x86 crate) but never exposes MSRs to userspace.
|
||||
|
||||
This is the single blocking gap for ALL RAPL work. Without it:
|
||||
- `read_msr(0x611)` returns `None` on bare-metal Redox
|
||||
- `redbear-power` can only read RAPL on Linux hosts via sysfs or `/dev/cpu/*/msr`
|
||||
- `cpufreqd` cannot write `IA32_PERF_CTL` on Redox bare metal
|
||||
- `thermald` cannot read thermal status MSRs
|
||||
|
||||
**Resolution options (pick one before Phase 1):**
|
||||
1. **Kernel `sys:msr` handler** — Add `src/scheme/sys/msr.rs` to kernel, expose as `/scheme/sys/msr/{cpu}/0x{msr_hex}`. Requires `CAP_SYS_MSR`.
|
||||
2. **Userspace `msrd` daemon** — Register `scheme:msr` via `redox-scheme`. More portable, easier to iterate, no kernel rebuild.
|
||||
3. **Linux-compatible `/dev/cpu/*/msr` device** — Matches what `thermald` already uses. Requires VFS-level char device emulation.
|
||||
|
||||
**Recommendation:** Option 2 (userspace daemon). It's the fastest path,
|
||||
doesn't require kernel changes, and can use `iopl(3)` + `inl`/`outl` for
|
||||
x86 port-based MSR access, or the `x86` crate's `rdmsr` if ring0 access
|
||||
is available through a capability scheme.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Implement hardware-accurate CPU/package/DRAM power monitoring in RedBear OS
|
||||
using the Intel RAPL (Running Average Power Limit) and AMD energy counter MSRs.
|
||||
This replaces the current `redbear-power` approach of relying on P-state table
|
||||
power estimates (static, inaccurate) and sysfs powercap fallback (Linux-only).
|
||||
|
||||
**Total scope:** 3 phases, ~8-12 weeks with 1 developer.
|
||||
**Prerequisite:** Redox MSR scheme (`/scheme/sys/msr/`) — already functional.
|
||||
|
||||
---
|
||||
|
||||
## 1. RAPL Architecture (what we're implementing)
|
||||
|
||||
### 1.1 Power Domains
|
||||
|
||||
Intel RAPL exposes energy counters for 5 domains:
|
||||
|
||||
| Domain | MSR (read) | Description | Typical accuracy |
|
||||
|--------|-----------|-------------|------------------|
|
||||
| **PKG** (package) | `0x611` | Entire CPU socket/package power | ±2-5% |
|
||||
| **PP0** (core) | `0x639` | All CPU cores collectively | ±5-10% |
|
||||
| **PP1** (graphics) | `0x641` | GPU uncore (client CPUs only) | N/A on server |
|
||||
| **DRAM** | `0x619` | DRAM controller power | ±10-20% |
|
||||
| **PSys** (platform) | `0x64D` | Entire platform (CPU + PCH + VR losses) | ±5-15% |
|
||||
|
||||
Not all domains are available on all CPUs. Server CPUs typically have PKG + DRAM.
|
||||
Client CPUs typically have PKG + PP0 + PP1. PSys is rare (some server platforms).
|
||||
|
||||
### 1.2 Energy Counter Semantics
|
||||
|
||||
```
|
||||
Energy (µJ) = RAW_COUNTER × ENERGY_UNIT
|
||||
|
||||
where ENERGY_UNIT = 1.0 / (2 ^ exponent)
|
||||
exponent = (MSR_RAPL_POWER_UNIT >> 8) & 0x1F
|
||||
```
|
||||
|
||||
- **32-bit counter** at each MSR, monotonically increasing
|
||||
- **Wraps at** 2^32 (at 100W with 15.3µJ unit: ~60 seconds for package, ~days for per-core)
|
||||
- **Linux mitigation:** hrtimer polls every ~2.5ms to accumulate into 64-bit software counters
|
||||
before the 32-bit hardware counter wraps (overflow detection via `max_energy_range_uj`)
|
||||
- **Read semantics:** poll at interval Δt, compute P = ΔE/Δt
|
||||
- **Zero overhead:** reads are just an MSR `RDMSR` instruction (~20-50 cycles)
|
||||
|
||||
### 1.3 Unit Register (MSR 0x606)
|
||||
|
||||
```
|
||||
MSR_RAPL_POWER_UNIT (0x606):
|
||||
Bits [3:0] — Power unit (Watts = RAW × 2^-exponent)
|
||||
Bits [12:8] — Energy unit (Joules = RAW × 2^-exponent)
|
||||
Bits [19:16] — Time unit (Seconds = RAW × 2^-exponent)
|
||||
|
||||
Default values (Sandy Bridge through Alder Lake):
|
||||
Power unit: 0.125 W (exponent = 3)
|
||||
Energy unit: 15.3 µJ (exponent = 16)
|
||||
Time unit: 976 µs (exponent = 10)
|
||||
```
|
||||
|
||||
### 1.4 AMD Energy Counters
|
||||
|
||||
AMD Zen 2+ (Family 17h+, Model 30h+) exposes RAPL-compatible MSRs at the SAME
|
||||
addresses as Intel but with a different unit register:
|
||||
|
||||
- `MSR_AMD_RAPL_POWER_UNIT` = `0xC0010299` (AMD-specific unit register)
|
||||
- Energy counter MSRs at same addresses (`0x611`, `0x619`, `0x639`, `0x641`)
|
||||
- PKG domain always available on Zen 2+
|
||||
- PP0 (core) available on Zen 2+
|
||||
- No DRAM or PSys on most AMD platforms
|
||||
- Unit conversion: AMD uses `(raw >> 8) & 0x1F` for energy exponent (same bit layout)
|
||||
|
||||
---
|
||||
|
||||
## 2. Implementation Plan
|
||||
|
||||
### Phase 1: Core RAPL Reader (4-6 weeks)
|
||||
|
||||
**Goal:** A userspace daemon (`rapld`) that reads RAPL energy counters and exposes
|
||||
them through a scheme for consumption by `redbear-power`.
|
||||
|
||||
#### 1.1 MSR Access Layer (`local/recipes/drivers/redox-driver-sys/source/src/`)
|
||||
|
||||
Add RAPL MSR constants and read helpers to `redox-driver-sys`:
|
||||
|
||||
```rust
|
||||
// New file: src/rapl.rs or extend src/msr.rs
|
||||
|
||||
pub const MSR_RAPL_POWER_UNIT: u32 = 0x606;
|
||||
pub const MSR_PKG_ENERGY_STATUS: u32 = 0x611;
|
||||
pub const MSR_PKG_PERF_STATUS: u32 = 0x613;
|
||||
pub const MSR_PKG_POWER_INFO: u32 = 0x614;
|
||||
pub const MSR_DRAM_ENERGY_STATUS: u32 = 0x619;
|
||||
pub const MSR_PP0_ENERGY_STATUS: u32 = 0x639;
|
||||
pub const MSR_PP1_ENERGY_STATUS: u32 = 0x641;
|
||||
pub const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x64D;
|
||||
pub const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xC0010299;
|
||||
|
||||
pub struct RaplUnit {
|
||||
pub power_exponent: u8, // bits 3:0
|
||||
pub energy_exponent: u8, // bits 12:8
|
||||
pub time_exponent: u8, // bits 19:16
|
||||
}
|
||||
|
||||
pub struct RaplDomain {
|
||||
pub name: &'static str,
|
||||
pub energy_msr: u32,
|
||||
pub last_energy: u64,
|
||||
pub last_timestamp: Instant,
|
||||
pub power_w: f64, // computed: ΔE/Δt
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
pub struct RaplPackage {
|
||||
pub id: u32,
|
||||
pub units: RaplUnit,
|
||||
pub domains: Vec<RaplDomain>,
|
||||
}
|
||||
```
|
||||
|
||||
**Key functions:**
|
||||
- `rapl_read_unit(lead_cpu: u32) -> Option<RaplUnit>` — reads MSR 0x606 (or 0xC0010299 on AMD)
|
||||
- `rapl_read_energy(lead_cpu: u32, msr: u32) -> Option<u64>` — reads 32-bit energy counter
|
||||
- `rapl_detect_domains(cpu: u32, unit: &RaplUnit) -> Vec<RaplDomain>` — probe which MSRs exist
|
||||
- `rapl_compute_power(domain: &mut RaplDomain, interval: Duration) -> f64` — ΔE/Δt
|
||||
|
||||
**AMD detection:** Use CPUID vendor string. If `AuthenticAMD`:
|
||||
- Use `MSR_AMD_RAPL_POWER_UNIT` instead of `MSR_RAPL_POWER_UNIT`
|
||||
- MSR 0x611 (PKG) available on Zen 2+ (Family >= 0x17)
|
||||
- MSR 0x639 (PP0/core) available on Zen 2+
|
||||
- DRAM/PSys typically absent
|
||||
|
||||
**Error handling (crucial for heterogeneous hardware):**
|
||||
- Attempt to read each MSR; if `#GP(0)` → domain unavailable → mark `available = false`
|
||||
- On QEMU: RAPL MSRs are typically unimplemented → graceful degradation
|
||||
- On virtualized guests: RAPL may be passed through or absent → check before assuming
|
||||
- Wrap detection: if `current < previous` → counter wrapped → `ΔE = (2^32 - prev) + current`
|
||||
|
||||
#### 1.2 rapld Daemon (`local/recipes/system/rapld/`)
|
||||
|
||||
**Architecture:**
|
||||
```
|
||||
rapld (userspace daemon, Rust)
|
||||
├── MSR polling (every 500ms default, configurable)
|
||||
├── Energy-to-power delta computation
|
||||
├── Per-domain statistics (avg, max, min over sliding window)
|
||||
├── Scheme registration: scheme:power or scheme:rapl
|
||||
└── Optional D-Bus export for desktop integration
|
||||
```
|
||||
|
||||
**Scheme paths:**
|
||||
```
|
||||
/scheme/rapl/package/0/power_w — current package power in watts
|
||||
/scheme/rapl/package/0/energy_uj — cumulative energy in µJ
|
||||
/scheme/rapl/package/0/max_w — max observed power
|
||||
/scheme/rapl/package/0/avg_w — average over sliding window
|
||||
/scheme/rapl/core/0/power_w — PP0 (core) power
|
||||
/scheme/rapl/dram/0/power_w — DRAM power
|
||||
/scheme/rapl/units — unit coefficients
|
||||
```
|
||||
|
||||
**Polling cadence:**
|
||||
- Default 500ms (matches redbear-power refresh rate)
|
||||
- Configurable via `/scheme/rapl/interval_ms`
|
||||
- Adaptive: can speed up during high load, slow down during idle
|
||||
|
||||
#### 1.3 redbear-power Integration
|
||||
|
||||
Modify `local/recipes/system/redbear-power/source/src/` to use rapld data:
|
||||
|
||||
1. **Add `rapl_available` flag** to platform probe: try reading `/scheme/rapl/package/0/power_w`
|
||||
2. **Replace RAPL sysfs fallback** with scheme read:
|
||||
```rust
|
||||
// Before: read_rapl_package_energy() → /sys/class/powercap/...
|
||||
// After: read("/scheme/rapl/package/0/power_w") → f64
|
||||
```
|
||||
3. **Add per-domain display** to System/Info tabs:
|
||||
- Package power: always shown
|
||||
- Core power: shown if available
|
||||
- DRAM power: shown if available
|
||||
4. **Use `energy_uj` for more accurate power** when available (avoid double-read of power_w + scheme)
|
||||
|
||||
**Fallback chain (Linux host):**
|
||||
```
|
||||
1. /scheme/rapl/package/0/power_w (rapld — Redox native)
|
||||
2. /sys/class/powercap/intel-rapl:0/energy_uj (RAPL sysfs — current implementation)
|
||||
3. MSR direct read via /dev/cpu/*/msr (existing MSR path)
|
||||
4. P-state table power estimate (current fallback, inaccurate)
|
||||
```
|
||||
|
||||
**Fallback chain (bare metal Redox):**
|
||||
```
|
||||
1. /scheme/rapl/package/0/power_w (rapld)
|
||||
2. MSR direct read via /scheme/sys/msr/ (existing MSR scheme)
|
||||
3. P-state table power estimate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Power Limiting & Thermal Integration (4-6 weeks)
|
||||
|
||||
**Goal:** Expose RAPL power limits for read/write and integrate with thermald.
|
||||
|
||||
#### 2.1 Power Limit Registers
|
||||
|
||||
| Register | MSR | Bits | Description |
|
||||
|----------|-----|------|-------------|
|
||||
| PKG_POWER_LIMIT | 0x610 | [14:0] PL1, [46:32] PL2 | Package long/short-term power limits |
|
||||
| PP0_POWER_LIMIT | 0x638 | [14:0] PL1 | Core power limit |
|
||||
| PL1_ENABLE | — | Bit 15 | Enable PL1 enforcement |
|
||||
| PL1_CLAMP | — | Bit 16 | Allow frequency clamping below OS request |
|
||||
| TIME_WINDOW1 | — | [23:17] | PL1 averaging window (in time units) |
|
||||
| POWER_LIMIT_LOCK | — | Bit 63 | Hardware lock (write-once) |
|
||||
|
||||
#### 2.2 rapld Power Limit Extension
|
||||
|
||||
Add scheme paths for power limit control:
|
||||
```
|
||||
/scheme/rapl/package/0/limit1_w — PL1 (long-term) power limit in watts (RW)
|
||||
/scheme/rapl/package/0/limit2_w — PL2 (short-term) power limit in watts (RW)
|
||||
/scheme/rapl/package/0/time_window1_s — PL1 time window in seconds (RW)
|
||||
/scheme/rapl/package/0/locked — true if limits are hardware-locked (RO)
|
||||
```
|
||||
|
||||
**Safety guards:**
|
||||
- CAP_SYS_MSR required for writes
|
||||
- Validate against `PKG_POWER_INFO` (max/min power, max time window)
|
||||
- Never write below `THERMAL_SPEC_POWER` unless explicitly forced
|
||||
- Log all limit changes
|
||||
|
||||
#### 2.3 thermald Integration
|
||||
|
||||
Modify `thermald` (already exists in base drivers) to:
|
||||
1. Read current package power from `/scheme/rapl/package/0/power_w`
|
||||
2. If power exceeds configurable threshold (default: TDP), lower P-state or trigger PL1 clamp
|
||||
3. Use `energy_uj` counter for accurate long-term energy accounting
|
||||
4. Expose thermal + power budget decisions via scheme
|
||||
|
||||
**Integration point:** thermald already reads `/scheme/thermal` for temperature.
|
||||
Add `/scheme/rapl/` as a secondary data source for power-aware throttling.
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: redbear-power Full Integration (2-4 weeks)
|
||||
|
||||
**Goal:** Complete redbear-power RAPL integration with real-time power graphs.
|
||||
|
||||
#### 3.1 Per-CPU Power Column
|
||||
|
||||
The current `PkgW` column shows P-state power estimate (or n/a). Replace with:
|
||||
```
|
||||
CPU Freq/MHz PkgW(W) CoreW(W) DRAM(W) Temp°C
|
||||
▶ E0 2436 65.2 42.1 8.3 67
|
||||
```
|
||||
|
||||
**Data flow:**
|
||||
```
|
||||
rapld (polls MSR 0x611 every 500ms)
|
||||
→ writes to /scheme/rapl/package/0/power_w
|
||||
→ redbear-power reads scheme (one syscall per refresh, not per-CPU)
|
||||
→ displays single PKG value replicated across all CPU rows
|
||||
```
|
||||
|
||||
Package power is socket-wide, so the value is the same for all CPU rows but
|
||||
the column stays for visual consistency. Core power (PP0) can be shown as
|
||||
a per-package aggregate or normalized per-core.
|
||||
|
||||
#### 3.2 Power History Graph
|
||||
|
||||
Add a mini sparkline for package power (like the load sparkline):
|
||||
```
|
||||
PkgW(W) History (30s)
|
||||
████▇▇▆▅▅▄▄▃ 65.2
|
||||
```
|
||||
|
||||
Store last 30 samples of power_w in a `VecDeque<f64>` and render as Unicode
|
||||
block characters, normalized against max observed power.
|
||||
|
||||
#### 3.3 System Tab Power Summary
|
||||
|
||||
Add to the System tab:
|
||||
```
|
||||
Power:
|
||||
Package: 65.2 W (avg: 58.1, max: 89.4)
|
||||
Cores: 42.1 W (avg: 38.2, max: 55.0)
|
||||
DRAM: 8.3 W
|
||||
Limit PL1: 95 W (TDP)
|
||||
Limit PL2: 125 W (boost)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. CPU Support Matrix
|
||||
|
||||
### 3.1 Intel
|
||||
|
||||
| Generation | Arch | PKG | PP0 | PP1 | DRAM | PSys | Notes |
|
||||
|------------|------|-----|-----|-----|------|------|-------|
|
||||
| Sandy Bridge (2nd) | SNB | ✅ | ✅ | ❌ | ❌ | ❌ | First RAPL implementation |
|
||||
| Ivy Bridge (3rd) | IVB | ✅ | ✅ | ❌ | ❌ | ❌ | |
|
||||
| Haswell (4th) | HSW | ✅ | ✅ | ✅ | ✅ | ❌ | Client: PKG+PP0+PP1, Server: PKG+DRAM |
|
||||
| Broadwell (5th) | BDW | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Skylake (6th) | SKL | ✅ | ✅ | ✅ | ✅ | ✅ | PSys on server only |
|
||||
| Kaby Lake (7th) | KBL | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Coffee Lake (8-9th) | CFL | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Comet Lake (10th) | CML | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Ice Lake (10th) | ICL | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Tiger Lake (11th) | TGL | ✅ | ✅ | ✅ | ✅ | ❌ | |
|
||||
| Alder Lake (12th) | ADL | ✅ | ✅ | ✅ | ✅ | ✅ | Hybrid: PKG covers P+E cores |
|
||||
| Raptor Lake (13-14th) | RPL | ✅ | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Meteor Lake (Ultra) | MTL | ✅ | ✅ | ❌ | ✅ | ❌ | New SoC architecture |
|
||||
| Sapphire Rapids | SPR | ✅ | ✅ | ❌ | ✅ | ✅ | Server: different PL bit layout |
|
||||
|
||||
### 3.2 AMD
|
||||
|
||||
| Generation | Family | PKG | PP0 | Notes |
|
||||
|------------|--------|-----|-----|-------|
|
||||
| Zen 1 (Ryzen 1000) | 17h | ❌ | ❌ | No RAPL support |
|
||||
| Zen+ (Ryzen 2000) | 17h | ❌ | ❌ | |
|
||||
| Zen 2 (Ryzen 3000) | 17h | ✅ | ✅ | RAPL via MSR 0x611/0x639 |
|
||||
| Zen 3 (Ryzen 5000) | 19h | ✅ | ✅ | |
|
||||
| Zen 4 (Ryzen 7000) | 19h | ✅ | ✅ | |
|
||||
| Zen 5 (Ryzen 9000) | 1Ah | ✅ | ✅ | |
|
||||
|
||||
AMD uses `MSR_AMD_RAPL_POWER_UNIT` (0xC0010299) for unit conversion instead
|
||||
of Intel's `MSR_RAPL_POWER_UNIT` (0x606). Energy counter MSR addresses are
|
||||
the same as Intel.
|
||||
|
||||
**⚠️ Critical AMD vs Intel difference**: AMD reports core energy **per-core**
|
||||
(MSR 0xC001029A), while Intel reports PP0 as **all-cores aggregate** (MSR 0x639).
|
||||
This means AMD core energy must be summed across all cores to get equivalent
|
||||
PP0 data, while Intel returns the total directly. The `amd_energy` driver on
|
||||
Linux runs a kernel thread every 100 seconds to accumulate per-core counters
|
||||
into 64-bit software counters to handle 32-bit wrap-around.
|
||||
|
||||
### 3.3 QEMU / Virtualized
|
||||
|
||||
- QEMU: RAPL MSRs typically unimplemented → **#GP(0)** → graceful "n/a"
|
||||
- KVM with `-overcommit cpu-pm=on`: may expose RAPL to guest (rare)
|
||||
- VMware/Hyper-V: no RAPL passthrough known
|
||||
|
||||
**Detection strategy:** Try reading MSR 0x611 from CPU 0. If it returns an error
|
||||
(#GP fault), RAPL is unavailable. Cache this result so subsequent reads skip
|
||||
the MSR access entirely.
|
||||
|
||||
---
|
||||
|
||||
## 4. File Layout & Implementation Order
|
||||
|
||||
### New files to create:
|
||||
|
||||
```
|
||||
local/recipes/drivers/redox-driver-sys/source/src/rapl.rs ← RAPL MSR constants + read primitives
|
||||
local/recipes/system/rapld/ ← New RAPL daemon
|
||||
local/recipes/system/rapld/recipe.toml ← Cargo template, depends: redox-driver-sys
|
||||
local/recipes/system/rapld/source/Cargo.toml
|
||||
local/recipes/system/rapld/source/src/main.rs ← Daemon: poll MSRs, serve scheme
|
||||
local/recipes/system/rapld/source/src/units.rs ← Unit conversion (power/energy/time)
|
||||
local/recipes/system/rapld/source/src/domains.rs ← Domain detection + power computation
|
||||
local/recipes/system/rapld/source/src/scheme.rs ← Scheme:power handler
|
||||
local/docs/RAPL-IMPLEMENTATION-PLAN.md ← This document
|
||||
|
||||
local/recipes/system/redbear-power/source/src/rapl.rs ← RAPL integration for redbear-power
|
||||
```
|
||||
|
||||
### Files to modify:
|
||||
|
||||
```
|
||||
local/recipes/system/redbear-power/source/src/acpi.rs ← Replace read_rapl_package_energy()
|
||||
local/recipes/system/redbear-power/source/src/app.rs ← Add rapl_domains, pkg_power_w from scheme
|
||||
local/recipes/system/redbear-power/source/src/render.rs ← Add CoreW/DRAM columns, power history
|
||||
local/recipes/system/redbear-power/source/src/main.rs ← Wire keybindings for power view
|
||||
config/redbear-full.toml ← Add rapld to [packages]
|
||||
config/redbear-mini.toml ← Optionally add rapld
|
||||
```
|
||||
|
||||
### Implementation order:
|
||||
|
||||
1. **Week 1-2:** `rapl.rs` in redox-driver-sys — MSR constants, read/write primitives, unit parsing
|
||||
2. **Week 3-4:** `rapld` daemon — scheme server, domain detection, energy polling
|
||||
3. **Week 5-6:** redbear-power integration — scheme reader, per-domain columns, power history
|
||||
4. **Week 7-8:** Power limiting + thermald integration (Phase 2)
|
||||
5. **Week 9-12:** Testing on real hardware (Intel + AMD), QEMU graceful degradation, polish
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing Strategy
|
||||
|
||||
### 5.1 Unit Tests (host, no hardware needed)
|
||||
|
||||
```rust
|
||||
// Test unit conversion from known MSR values
|
||||
fn test_rapl_unit_parsing() {
|
||||
// MSR_RAPL_POWER_UNIT = 0x000A1003
|
||||
// Power exponent = 3, Energy exponent = 0x10 = 16, Time exponent = 0xA = 10
|
||||
let unit = RaplUnit::from_msr(0x000A1003);
|
||||
assert_eq!(unit.power_exponent, 3);
|
||||
assert_eq!(unit.energy_exponent, 16);
|
||||
assert_eq!(unit.time_exponent, 10);
|
||||
}
|
||||
|
||||
// Test energy-to-power conversion
|
||||
fn test_energy_delta() {
|
||||
let prev = (1_000_000u64, Instant::now() - Duration::from_secs(1));
|
||||
let curr = (1_065_200u64, Instant::now());
|
||||
// ΔE = 65200 µJ over ~1 second = 65.2 W
|
||||
let (w, _) = rapl_power_watts(curr, prev);
|
||||
assert!((w - 65.2).abs() < 0.1);
|
||||
}
|
||||
|
||||
// Test counter wrap
|
||||
fn test_energy_wrap() {
|
||||
let prev = (0xFFFF_FFF0u64, Instant::now() - Duration::from_secs(1));
|
||||
let curr = (0x0000_0010u64, Instant::now());
|
||||
// ΔE = (2^32 - 0xFFFFFFF0) + 0x10 = 16 + 16 = 32 µJ
|
||||
let (w, _) = rapl_power_watts_wrap(curr, prev);
|
||||
assert!(w > 0.0);
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Integration Tests (QEMU)
|
||||
|
||||
```bash
|
||||
# Test graceful degradation when RAPL unavailable (QEMU default)
|
||||
make qemu CONFIG_NAME=redbear-full
|
||||
# Inside guest: rapld should start, detect no RAPL, serve scheme with "n/a"
|
||||
cat /scheme/rapl/package/0/power_w # Expected: "unavailable\n"
|
||||
|
||||
# Test with RAPL-capable host (bare metal only)
|
||||
# On bare metal Intel/AMD:
|
||||
cat /scheme/rapl/package/0/power_w # Expected: "65.2\n" (or similar)
|
||||
```
|
||||
|
||||
### 5.3 Hardware Validation Matrix
|
||||
|
||||
| Platform | CPU | RAPL domains expected |
|
||||
|----------|-----|----------------------|
|
||||
| Desktop Intel 12th-gen | i5-12600K | PKG, PP0, PP1, DRAM |
|
||||
| Desktop Intel 13th-gen | i9-13900K | PKG, PP0, PP1, DRAM |
|
||||
| Laptop Intel 11th-gen | i7-1165G7 | PKG, PP0, PP1, DRAM |
|
||||
| Desktop AMD Zen 3 | Ryzen 5950X | PKG, PP0 |
|
||||
| Desktop AMD Zen 4 | Ryzen 7950X | PKG, PP0 |
|
||||
| Server Intel Xeon | SPR | PKG, PP0, DRAM, PSys |
|
||||
| QEMU (any) | — | None (graceful n/a) |
|
||||
|
||||
---
|
||||
|
||||
## 6. Risks & Mitigations
|
||||
|
||||
| Risk | Likelihood | Mitigation |
|
||||
|------|-----------|------------|
|
||||
| MSR #GP fault on unsupported CPU | High | Catch fault, mark domain unavailable, cache decision |
|
||||
| Energy counter wraps between polls | Low (60s at 100W) | Wrap detection in delta computation |
|
||||
| Multi-socket power aggregation wrong | Medium | Per-package MSR reads on lead CPU of each package |
|
||||
| AMD energy unit different from Intel | High | Auto-detect via CPUID vendor, use correct unit MSR |
|
||||
| Kernel MSR scheme latency | Medium | Batch reads (read all domains in one scheme transaction) |
|
||||
| Power limit writes brick hardware | Low | Validate against PKG_POWER_INFO; never write below THERMAL_SPEC |
|
||||
| thermald + rapld race on MSR writes | Low | rapld owns energy reads; thermald owns limit writes; use scheme as synchronization point |
|
||||
|
||||
---
|
||||
|
||||
## 7. References
|
||||
|
||||
### Linux Kernel Source (local/reference/linux-7.1/)
|
||||
|
||||
| File | Lines | Content |
|
||||
|------|-------|---------|
|
||||
| `include/linux/intel_rapl.h` | 269 | Core data structures, domain/primitive enums |
|
||||
| `drivers/powercap/intel_rapl_common.c` | ~2000 | Domain definitions, powercap zone registration, energy read |
|
||||
| `drivers/powercap/intel_rapl_msr.c` | 602 | MSR-based RAPL access (most CPUs use this) |
|
||||
| `drivers/powercap/intel_rapl_tpmi.c` | ~300 | TPMI-based RAPL (newer Intel platforms) |
|
||||
| `drivers/powercap/powercap_sys.c` | ~600 | Powercap framework (sysfs interface) |
|
||||
| `arch/x86/include/asm/msr-index.h` | L490-522 | MSR address definitions |
|
||||
| `arch/x86/events/rapl.c` | ~800 | Perf events RAPL PMU |
|
||||
|
||||
### Intel Documentation
|
||||
|
||||
- Intel SDM Volume 3, Chapter 14.9: "Running Average Power Limit (RAPL)"
|
||||
- Intel SDM Volume 4: MSR definitions (0x606, 0x610-0x619, 0x638-0x641, 0x64D)
|
||||
|
||||
### Existing RedBear Infrastructure
|
||||
|
||||
| File | Content |
|
||||
|------|---------|
|
||||
| `local/recipes/system/redbear-power/source/src/msr.rs` | MSR read/write via `/scheme/sys/msr/` + `/dev/cpu/*/msr` |
|
||||
| `local/recipes/system/redbear-power/source/src/acpi.rs` | `read_rapl_package_energy()` — current sysfs fallback |
|
||||
| `local/recipes/system/redbear-power/source/src/app.rs` | `pkg_power_w`, `rapl_prev` — current RAPL state |
|
||||
| `local/recipes/system/redbear-power/source/src/platform.rs` | Platform probe (Redox vs Linux MSR backend selection) |
|
||||
| `local/recipes/system/cpufreqd/source/src/main.rs` | MSR 0x199 writes via `/scheme/sys/msr/` |
|
||||
| `local/recipes/system/thermald/source/src/main.rs` | MSR 0x19C/0x1A2 via Linux `/dev/cpu/*/msr` only |
|
||||
|
||||
### External References
|
||||
|
||||
| Source | Key Insight |
|
||||
|--------|------------|
|
||||
| Intel SDM Vol.3B §14.9 | Domain hierarchy, unit register layout, power limit semantics |
|
||||
| Intel SDM Vol.4 | All MSR addresses (0x606-0x64D) and bit-field definitions |
|
||||
| Fuchsia RFC-0203 | Microkernel energy monitoring via `zx_system_energy_info()` syscall — kernel owns MSR access, userspace gets cooked µJ values |
|
||||
| Schöne et al. 2021 | AMD Zen 2 RAPL accuracy analysis: package domain ±5%, 1ms update rate |
|
||||
| `rapl-read.c` (deater/uarch-configure) | Reference userspace RAPL reader: unit parsing, counter wrap detection |
|
||||
| AMD `amd_energy` driver README | Per-core accumulation thread, 100s wake interval, SMT dedup |
|
||||
|
||||
---
|
||||
|
||||
## 8. Deliverables
|
||||
|
||||
| Phase | Deliverable | Success Criteria |
|
||||
|-------|------------|------------------|
|
||||
| 1 | `rapld` daemon serving `/scheme/rapl/` | Reads PKG energy on real Intel/AMD hardware, serves power in watts |
|
||||
| 1 | redbear-power reads from rapld | PkgW column shows real hardware power (not n/a) on supported CPUs |
|
||||
| 1 | Graceful degradation on QEMU | RAPL domain "unavailable" instead of crash |
|
||||
| 2 | Power limit read/write | `/scheme/rapl/package/0/limit1_w` returns TDP; writable with CAP_SYS_MSR |
|
||||
| 2 | thermald power-aware throttling | thermald reads RAPL power, throttles when exceeding configurable threshold |
|
||||
| 3 | redbear-power multi-domain display | PKG, Core, DRAM columns with real values |
|
||||
| 3 | Power history sparkline | 30-sample power history visible in Per-CPU tab |
|
||||
@@ -51,19 +51,29 @@ pub fn read_cpu_freq_khz_sysfs(cpu: u32) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Read package power in microwatts from Intel RAPL powercap sysfs.
|
||||
/// Read package power in microwatts from RAPL MSR or powercap sysfs.
|
||||
/// Tries MSR first (direct rdmsr via /dev/cpu/*/msr or /scheme/sys/msr),
|
||||
/// then falls back to /sys/class/powercap/ (Linux sysfs).
|
||||
/// Returns `(power_uw, timestamp)` — the caller should compute delta
|
||||
/// between two calls to get average power in watts.
|
||||
pub fn read_rapl_package_energy() -> Option<(u64, Instant)> {
|
||||
// Intel RAPL: /sys/class/powercap/intel-rapl:0/energy_uj
|
||||
// 1. Try direct MSR read (works on Linux with msr module, or Redox with scheme)
|
||||
if let Some(unit) = crate::msr::RaplUnit::read(0) {
|
||||
if let Some(raw) = crate::msr::read_rapl_energy(0, crate::msr::MSR_PKG_ENERGY_STATUS) {
|
||||
return Some((unit.energy_to_uj(raw), Instant::now()));
|
||||
}
|
||||
// Try AMD PKG MSR
|
||||
if let Some(raw) = crate::msr::read_rapl_energy(0, crate::msr::MSR_AMD_PKG_ENERGY_STATUS) {
|
||||
return Some((unit.energy_to_uj(raw), Instant::now()));
|
||||
}
|
||||
}
|
||||
// 2. Fallback: Linux powercap sysfs
|
||||
let path = "/sys/class/powercap/intel-rapl:0/energy_uj";
|
||||
if let Ok(s) = fs::read_to_string(path) {
|
||||
if let Ok(uj) = s.trim().parse::<u64>() {
|
||||
return Some((uj, Instant::now()));
|
||||
}
|
||||
}
|
||||
// AMD RAPL: /sys/class/powercap/intel-rapl:0/energy_uj (same path on modern kernels)
|
||||
// Also try the package-level energy file.
|
||||
let path2 = "/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj";
|
||||
if let Ok(s) = fs::read_to_string(path2) {
|
||||
if let Ok(uj) = s.trim().parse::<u64>() {
|
||||
|
||||
@@ -23,6 +23,87 @@ pub const IA32_THERM_STATUS: u32 = 0x19c;
|
||||
pub const IA32_PACKAGE_THERM_STATUS: u32 = 0x1b1;
|
||||
pub const IA32_PERF_CTL: u32 = 0x199;
|
||||
|
||||
// --- RAPL (Running Average Power Limit) MSRs ---
|
||||
// Intel SDM Vol.4 + Linux arch/x86/include/asm/msr-index.h
|
||||
pub const MSR_RAPL_POWER_UNIT: u32 = 0x606;
|
||||
pub const MSR_PKG_POWER_LIMIT: u32 = 0x610;
|
||||
pub const MSR_PKG_ENERGY_STATUS: u32 = 0x611;
|
||||
pub const MSR_PKG_PERF_STATUS: u32 = 0x613;
|
||||
pub const MSR_PKG_POWER_INFO: u32 = 0x614;
|
||||
pub const MSR_DRAM_ENERGY_STATUS: u32 = 0x619;
|
||||
pub const MSR_PP0_ENERGY_STATUS: u32 = 0x639;
|
||||
pub const MSR_PP1_ENERGY_STATUS: u32 = 0x641;
|
||||
pub const MSR_PLATFORM_ENERGY_STATUS: u32 = 0x64D;
|
||||
|
||||
// AMD Zen RAPL MSRs (Family 17h+)
|
||||
pub const MSR_AMD_RAPL_POWER_UNIT: u32 = 0xC0010299;
|
||||
pub const MSR_AMD_CORE_ENERGY_STATUS: u32 = 0xC001029A;
|
||||
pub const MSR_AMD_PKG_ENERGY_STATUS: u32 = 0xC001029B;
|
||||
|
||||
// RAPL unit register bitfields
|
||||
pub const RAPL_POWER_UNIT_MASK: u64 = 0xF;
|
||||
pub const RAPL_ENERGY_UNIT_MASK: u64 = 0x1F00;
|
||||
pub const RAPL_ENERGY_UNIT_SHIFT: u32 = 8;
|
||||
pub const RAPL_TIME_UNIT_MASK: u64 = 0xF0000;
|
||||
pub const RAPL_TIME_UNIT_SHIFT: u32 = 16;
|
||||
|
||||
// Energy status: lower 32 bits are the counter
|
||||
pub const RAPL_ENERGY_STATUS_MASK: u64 = 0xFFFF_FFFF;
|
||||
|
||||
/// Decoded RAPL unit multipliers from MSR_RAPL_POWER_UNIT (0x606) or
|
||||
/// MSR_AMD_RAPL_POWER_UNIT (0xC0010299).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RaplUnit {
|
||||
/// Watts = raw_power × 2^-power_exponent
|
||||
pub power_exponent: u8,
|
||||
/// Joules = raw_energy × 2^-energy_exponent
|
||||
pub energy_exponent: u8,
|
||||
/// Seconds = raw_time × 2^-time_exponent
|
||||
pub time_exponent: u8,
|
||||
}
|
||||
|
||||
impl RaplUnit {
|
||||
/// Parse the RAPL unit MSR. Returns None if the MSR is unreadable.
|
||||
pub fn read(lead_cpu: u32) -> Option<Self> {
|
||||
// Try Intel MSR first, then AMD
|
||||
let raw = read_msr(lead_cpu, MSR_RAPL_POWER_UNIT)
|
||||
.or_else(|| read_msr(lead_cpu, MSR_AMD_RAPL_POWER_UNIT))?;
|
||||
Some(Self {
|
||||
power_exponent: (raw & RAPL_POWER_UNIT_MASK) as u8,
|
||||
energy_exponent: ((raw & RAPL_ENERGY_UNIT_MASK) >> RAPL_ENERGY_UNIT_SHIFT) as u8,
|
||||
time_exponent: ((raw & RAPL_TIME_UNIT_MASK) >> RAPL_TIME_UNIT_SHIFT) as u8,
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a raw 32-bit energy counter to microjoules.
|
||||
pub fn energy_to_uj(self, raw: u32) -> u64 {
|
||||
// Joules = raw × 2^-energy_exponent
|
||||
// µJ = raw × 2^-energy_exponent × 10^6
|
||||
// Use integer arithmetic: (raw × 10^6) >> energy_exponent
|
||||
((raw as u64) * 1_000_000) >> self.energy_exponent as u32
|
||||
}
|
||||
|
||||
/// Convert a raw power limit value to watts.
|
||||
pub fn power_to_w(self, raw: u64) -> f64 {
|
||||
(raw as f64) / (1u64 << self.power_exponent) as f64
|
||||
}
|
||||
}
|
||||
|
||||
/// Read RAPL energy counter for a domain. Returns the raw 32-bit counter
|
||||
/// value. Returns None if the MSR is unreadable (domain not supported on
|
||||
/// this CPU, or MSR access not available).
|
||||
pub fn read_rapl_energy(cpu: u32, msr: u32) -> Option<u32> {
|
||||
let raw = read_msr(cpu, msr)?;
|
||||
Some((raw & RAPL_ENERGY_STATUS_MASK) as u32)
|
||||
}
|
||||
|
||||
/// Read RAPL energy counter and convert to microjoules. Returns None if
|
||||
/// the domain is unavailable.
|
||||
pub fn read_rapl_energy_uj(cpu: u32, msr: u32, unit: RaplUnit) -> Option<u64> {
|
||||
let raw = read_rapl_energy(cpu, msr)?;
|
||||
Some(unit.energy_to_uj(raw))
|
||||
}
|
||||
|
||||
pub const THERM_STATUS_READOUT_VALID: u64 = 1 << 31;
|
||||
pub const THERM_STATUS_TEMP_MASK: u64 = 0x7f << 16;
|
||||
pub const THERM_STATUS_PROCHOT: u64 = 1 << 0;
|
||||
|
||||
Reference in New Issue
Block a user