redbear-power: wire package thermal status into header

This commit is contained in:
2026-07-07 17:29:53 +03:00
parent 5c92aeadd5
commit 72fc7c6f87
3 changed files with 54 additions and 17 deletions
@@ -121,7 +121,7 @@ pub struct App {
pub load_available: bool,
pub governor_available: bool,
pub hwmon_available: bool,
pub pkg_thermal: PackageThermal,
pub package_thermal: Option<PackageThermal>,
pub dbus_disconnected: bool,
pub cpuid_info: CpuId,
pub simd: String,
@@ -423,7 +423,7 @@ impl App {
load_available,
governor_available,
hwmon_available,
pkg_thermal: PackageThermal::default(),
package_thermal: None,
dbus_disconnected: false,
cpuid_info,
simd,
@@ -882,11 +882,15 @@ impl App {
}
// Re-read cpufreq state (catches external governor changes).
self.cpufreq.refresh();
if let Some(pkg) = read_package_thermal_status(self.cpus[0].id) {
self.pkg_thermal = PackageThermal::from_msr(pkg);
self.package_thermal = self
.cpus
.first()
.and_then(|cpu| read_package_thermal_status(cpu.id))
.map(PackageThermal::from_msr);
if let Some(pkg) = self.package_thermal.as_ref() {
// PROCHOT at the package level means the entire chip is
// throttling. Surface that in the global header.
let prochot_now = pkg & THERM_STATUS_PROCHOT != 0;
let prochot_now = pkg.prochot;
let prochot_was = matches!(self.throttle, ThrottleMode::ForcedMin);
if prochot_now && !prochot_was {
self.flash_toast("PROCHOT triggered \u{2014} throttling active");
@@ -23,17 +23,21 @@ use std::sync::Mutex;
static MSR_FAILED: Mutex<Vec<bool>> = Mutex::new(Vec::new());
fn msr_failed_for(cpu: u32) -> bool {
let cache = MSR_FAILED.lock().unwrap();
cpu as usize >= cache.len() || cache[cpu as usize]
if let Ok(cache) = MSR_FAILED.lock() {
cpu as usize >= cache.len() || cache[cpu as usize]
} else {
false
}
}
fn mark_msr_failed(cpu: u32) {
let mut cache = MSR_FAILED.lock().unwrap();
let idx = cpu as usize;
if idx >= cache.len() {
cache.resize(idx + 1, false);
if let Ok(mut cache) = MSR_FAILED.lock() {
let idx = cpu as usize;
if idx >= cache.len() {
cache.resize(idx + 1, false);
}
cache[idx] = true;
}
cache[idx] = true;
}
/// Clear the MSR failure cache. Call periodically (every ~30s) so that
@@ -261,7 +265,6 @@ pub fn read_hwp_status(cpu: u32) -> Option<u64> {
/// Decoded view of `IA32_PACKAGE_THERM_STATUS`. Built from the raw MSR
/// value in `app.rs:refresh()`. Empty/default when the MSR is unreadable.
#[derive(Clone, Copy, Debug, Default)]
#[allow(dead_code)]
pub struct PackageThermal {
pub temp_c: Option<u32>,
pub valid: bool,
@@ -166,12 +166,28 @@ pub fn render_toast<'a>(app: &'a App) -> Option<Paragraph<'a>> {
pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
let theme = &app.theme;
let pkg_temp = app
.cpus
.iter()
.filter_map(|c| c.temp_c)
.max()
.package_thermal
.and_then(|pkg| pkg.temp_c)
.or_else(|| app.cpus.iter().filter_map(|c| c.temp_c).max())
.map(|t| format!("{t}°C"))
.unwrap_or_else(|| "n/a".into());
let pkg_thermal = app.package_thermal.map(|pkg| {
let label = pkg.short_label();
let style = if pkg.valid
&& (pkg.prochot
|| pkg.power_limit_1
|| pkg.power_limit_2
|| pkg.critical
|| pkg.hfi
|| pkg.thermal_throttle_1
|| pkg.thermal_throttle_2)
{
theme.value_hot
} else {
theme.value_off
};
(label, style)
});
let lines = vec![
Line::from(vec![
"Vendor: ".set_style(theme.label),
@@ -214,6 +230,20 @@ pub fn render_header<'a>(app: &'a App, focused: bool) -> Paragraph<'a> {
"P-state source: ".set_style(theme.label),
app.pss_source.as_str().into(),
]),
Line::from(vec![
"PkgTherm: ".set_style(theme.label),
match pkg_thermal {
Some((label, style)) => label.set_style(style),
None => "n/a".set_style(theme.value_off),
},
" ".into(),
"D-Bus: ".set_style(theme.label),
if app.dbus_disconnected {
"DISCONNECTED".set_style(theme.status_warn)
} else {
"ok".set_style(theme.value_ok)
},
]),
Line::from(vec![
"SIMD: ".set_style(theme.label),
app.simd.as_str().set_style(theme.value),