Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions drivers/gpu/drm/asahi/m3/m3_adt_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ use crate::{
m3::m3_init_layout as init,
m3::m3_init_storage as storage,
m3::m3_memory::Buffer,
m3::m3_params::{self, G15Debug, InitDataParam, ThermalMode},
m3::m3_params::{self, G15Debug, InitDataParam},
m3::m3_thermal_policy::ThermalMode,
m3::m3_soc::{IoMapping, PstateTable, Soc},
m3::t8122_hwdata,
m3::t8122_start::Experiment,
Expand Down Expand Up @@ -137,12 +138,6 @@ const _: () = assert!(storage::same_iomaps(
&storage::T6030_IOMAPS
));

/// Highest performance state device-tree InitData may use without the driver's thermal limit
/// (`asahi.m3_thermal=off`), and the runtime cap the thermal limit starts at and falls back to
/// (the "safe cap"). The InitData's fast-die temperature controller words stay 0, so the
/// firmware does not limit the die temperature by itself.
pub(crate) const ADT_MAX_PSTATE_LIMIT: u32 = 5;

/// The performance states the runtime backend lets the firmware use.
#[derive(Copy, Clone, Debug)]
pub(crate) struct PstatePolicy {
Expand Down Expand Up @@ -200,42 +195,34 @@ impl PstatePolicy {
ThermalMode::Hold | ThermalMode::On => u64::from(table_max),
ThermalMode::Off => m3_params::MAX_PSTATE_DEFAULT,
});
let mut max = requested.clamp(1, u64::from(table_max)) as u32;
if u64::from(max) != requested {
let table_cap = requested.clamp(1, u64::from(table_max)) as u32;
let (max, safe) = thermal.pstate_caps(table_cap, ceiling);
if u64::from(table_cap) != requested {
dev_info!(
dev,
"M3: asahi.m3_max_pstate={} clamped to {} (the table has states 1..={})\n",
requested,
max,
table_cap,
table_max
);
}
if thermal == ThermalMode::Off && max > ADT_MAX_PSTATE_LIMIT {
dev_info!(
dev,
"M3: performance cap {} lowered to {}: device-tree InitData leaves the fast-die temperature controller off and the thermal limit is off (asahi.m3_thermal=off)\n",
max,
ADT_MAX_PSTATE_LIMIT
);
max = ADT_MAX_PSTATE_LIMIT;
}
if let Some(ceiling) = ceiling {
if max > ceiling {
if max < table_cap {
if thermal == ThermalMode::Off {
dev_info!(
dev,
"M3: performance cap {} lowered to {}: device-tree InitData leaves the fast-die temperature controller off and the thermal limit is off (asahi.m3_thermal=off)\n",
table_cap,
max
);
} else {
dev_info!(
dev,
"M3 G15G start: performance cap {} lowered to {} (asahi.t8122_pstate_cap)\n",
max,
ceiling
table_cap,
max
);
max = ceiling;
}
}
// Without the thermal limit the runtime cap is the ceiling; with it, the runtime cap
// starts at (and never goes below) the cap used without it.
let safe = match thermal {
ThermalMode::Off => max,
ThermalMode::Hold | ThermalMode::On => max.min(ADT_MAX_PSTATE_LIMIT),
};
let boot = m3_params::boot_pstate_param()
.unwrap_or(u64::from(safe))
.clamp(1, u64::from(safe)) as u32;
Expand Down
17 changes: 2 additions & 15 deletions drivers/gpu/drm/asahi/m3/m3_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use kernel::{
prelude::*, //
};

use crate::m3::m3_thermal_policy::ThermalMode;

/// Parse a decimal or `0x`-prefixed hexadecimal `u64`.
fn parse_u64(text: &str) -> Option<u64> {
let text = text.trim();
Expand Down Expand Up @@ -545,21 +547,6 @@ fn parse_thermal(text: &str) -> Option<u64> {
static M3_THERMAL: AtomicU64 = AtomicU64::new(THERMAL_UNSET);
m3_param!("m3_thermal", M3_THERMAL, parse_thermal);

/// How the runtime backend limits the GPU performance state by temperature.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ThermalMode {
/// Today's fixed cap: the firmware is given the cap as its highest state, and nothing
/// changes it at runtime.
Off,
/// The firmware is given the whole table as its highest state, and the runtime cap words
/// hold the fixed cap for the whole boot. The same performance as `Off`, if the firmware
/// follows the runtime cap: this checks that it does.
Hold,
/// As `Hold`, and the runtime cap follows the SoC die temperature, up to the whole table
/// while the die is cool.
On,
}

/// The `asahi.m3_thermal` setting, if given.
pub(crate) fn thermal_param() -> Option<ThermalMode> {
match M3_THERMAL.load(Ordering::Relaxed) {
Expand Down
11 changes: 7 additions & 4 deletions drivers/gpu/drm/asahi/m3/m3_thermal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@
//! J516S) and nothing watches temperatures. The firmware's own die temperature controller is
//! off in the InitData this driver builds. With device-tree InitData the default is `on`;
//! `asahi.m3_thermal=off` restores the fixed cap exactly.
//! On T8122 the fixed cap is state 2 (or a lower requested ceiling). A higher
//! `asahi.t8122_pstate_cap` changes only the monitored ceiling, never this fallback.
//!
//! With it, the InitData gives the firmware the whole table as its highest state (the
//! "ceiling"), and what bounds the firmware is the runtime cap: the Globals performance-state
//! With it, the InitData gives the firmware the requested, table-clamped highest state (the
//! "ceiling", also bounded by the T8122 experiment), and what bounds the firmware is the
//! runtime cap: the Globals performance-state
//! cap and power-interface state targets, which the firmware keeps reading while it runs (a
//! host moves the cap with a single store, with no message). The runtime cap starts at the
//! fixed cap of `off` (the "safe cap") and never goes below it, so at worst the GPU runs as it
Expand Down Expand Up @@ -59,8 +62,8 @@ use kernel::{

use crate::{
m3::m3_adt_config::PstatePolicy,
m3::m3_params::{self, ThermalMode},
m3::m3_thermal_policy::{Change, Policy, Reading, Source, Why, S},
m3::m3_params,
m3::m3_thermal_policy::{Change, Policy, Reading, Source, ThermalMode, Why, S},
};

/// The thermal zones read, each the hottest of a set of SoC die sensors: the SMC's die sensors
Expand Down
144 changes: 143 additions & 1 deletion drivers/gpu/drm/asahi/m3/m3_thermal_policy.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

//! Decisions of the M3 thermal limit (see `m3_thermal`), kept free of kernel dependencies so
//! that host tests drive exactly this code (`tools/asahi/m3-thermal-policy-tests.rs`).
//! that host tests drive exactly this code (`tools/asahi/m3-runtime-tests.rs`).
//!
//! Times are monotonic nanoseconds from any fixed origin, temperatures millidegrees Celsius, and
//! states indices of the published performance-state table.

/// Highest fixed cap for non-T8122 device-tree InitData, whose firmware die-temperature
/// controller is disabled. T8122 retains its lower default cap without temperature feedback.
const ADT_MAX_PSTATE_LIMIT: u32 = 5;
/// T8122's unmonitored cap, independent of any experiment ceiling or future default change.
const T8122_SAFE_PSTATE: u32 = 2;

/// How the runtime backend limits the GPU performance state by temperature.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum ThermalMode {
/// A fixed ceiling no higher than the safe cap, without temperature feedback.
Off,
/// Publish the requested ceiling, but hold the runtime cap at the safe cap.
Hold,
/// Allow the runtime cap to follow the die temperature up to the requested ceiling.
On,
}

impl ThermalMode {
/// Return (firmware ceiling, boot/failure cap) for a table-clamped request. A T8122
/// experiment ceiling permits higher states only with working temperature feedback;
/// it must not also raise the cap used before a reading or after a sensor failure.
pub(crate) fn pstate_caps(self, requested: u32, t8122_ceiling: Option<u32>) -> (u32, u32) {
let (max, limit) = match t8122_ceiling {
Some(ceiling) => (requested.min(ceiling), T8122_SAFE_PSTATE),
None => (requested, ADT_MAX_PSTATE_LIMIT),
};
let safe = max.min(limit);
(if self == Self::Off { safe } else { max }, safe)
}
}

/// Required thermal-zone bits: SMC die (bit 0), PMP hotspot (bit 1).
/// A missing or malformed compatible list requires both zones.
pub(crate) fn required_zones(compatible: &[u8]) -> u32 {
Expand Down Expand Up @@ -257,6 +288,117 @@ impl Policy {
}
}

#[cfg(test)]
mod cap_tests {
use super::*;

fn temp(mc: i32, at: i64) -> Reading {
Reading::Temp { mc, at, zones: 1 }
}

fn boosted_air() -> Policy {
let (ceiling, safe) = ThermalMode::On.pstate_caps(8, Some(5));
let mut policy = Policy::new(true, safe, ceiling, 80_000);
assert_eq!(policy.cap(), 2);
for (at, mc, cap) in [(0, 60_000, 3), (2 * S, 60_001, 4), (4 * S, 60_002, 5)] {
policy.update(at, temp(mc, at)).0.unwrap();
assert_eq!(policy.cap(), cap);
}
policy
}

#[test]
fn air_ceiling_never_raises_the_unmonitored_cap() {
assert_eq!(ThermalMode::On.pstate_caps(8, Some(5)), (5, 2));
assert_eq!(ThermalMode::Hold.pstate_caps(8, Some(5)), (5, 2));
assert_eq!(ThermalMode::Off.pstate_caps(8, Some(5)), (2, 2));
for mode in [ThermalMode::On, ThermalMode::Hold, ThermalMode::Off] {
assert_eq!(mode.pstate_caps(1, Some(5)), (1, 1));
assert_eq!(mode.pstate_caps(8, Some(1)), (1, 1));
}
}

#[test]
fn non_air_caps_keep_the_existing_safe_limit() {
assert_eq!(ThermalMode::On.pstate_caps(8, None), (8, 5));
assert_eq!(ThermalMode::Hold.pstate_caps(8, None), (8, 5));
assert_eq!(ThermalMode::Off.pstate_caps(8, None), (5, 5));
}

#[test]
fn failed_missing_stale_and_implausible_readings_drop_air_to_two() {
for (reading, source) in [
(Reading::None, Source::NoReading),
(Reading::Failed { errno: -5, at: 5 * S }, Source::Failed(-5)),
(temp(60_000, S), Source::Stale),
(temp(9_999, 5 * S), Source::Implausible),
(temp(125_001, 5 * S), Source::Implausible),
] {
let mut policy = boosted_air();
let (usable, change) = policy.update(5 * S, reading);
assert_eq!(usable, Err(source));
assert_eq!(change, Some(Change { from: 5, to: 2, why: Why::NoTemperature(source) }));
policy.update(7 * S, temp(60_003, 7 * S)).0.unwrap();
assert_eq!(policy.cap(), 3);
}
}

#[test]
fn air_boost_requires_fresh_paced_readings_and_respects_the_ceiling() {
let (ceiling, safe) = ThermalMode::On.pstate_caps(8, Some(5));
let mut policy = Policy::new(true, safe, ceiling, 80_000);
policy.update(0, temp(60_000, 0)).0.unwrap();
assert_eq!(policy.cap(), 3);
policy.update(S, temp(60_001, S)).0.unwrap();
assert_eq!(policy.cap(), 3);
policy.update(2 * S, temp(60_000, 0)).0.unwrap();
assert_eq!(policy.cap(), 3);
policy.update(2 * S, temp(60_002, 2 * S)).0.unwrap();
assert_eq!(policy.cap(), 4);
policy.update(4 * S, temp(60_003, 4 * S)).0.unwrap();
policy.update(6 * S, temp(60_004, 6 * S)).0.unwrap();
assert_eq!(policy.cap(), 5);
}

#[test]
fn air_hot_and_critical_readings_reduce_the_cap() {
let mut policy = boosted_air();
policy.update(4 * S + 500 * MS, temp(80_000, 4 * S + 500 * MS)).0.unwrap();
assert_eq!(policy.cap(), 4);
policy.update(5 * S, temp(80_000, 4 * S + 500 * MS)).0.unwrap();
assert_eq!(policy.cap(), 4);
policy.update(5 * S, temp(80_001, 5 * S)).0.unwrap();
assert_eq!(policy.cap(), 3);
policy.update(5 * S + MS, temp(88_000, 5 * S + MS)).0.unwrap();
assert_eq!(policy.cap(), 2);
assert_eq!(policy.allowed(5 * S + 2 * MS), 5);
assert_eq!(policy.allowed(6 * S + MS), 2);
}

#[test]
fn unchanged_air_temperature_cannot_sustain_boost_forever() {
let (ceiling, safe) = ThermalMode::On.pstate_caps(8, Some(5));
let mut policy = Policy::new(true, safe, ceiling, 80_000);
for at in [0, 2 * S, 4 * S] {
policy.update(at, temp(60_000, at)).0.unwrap();
}
assert_eq!(policy.cap(), 5);
let (usable, _) = policy.update(STUCK_NS, temp(60_000, STUCK_NS));
assert_eq!(usable, Err(Source::Stuck));
assert_eq!(policy.cap(), 2);
}

#[test]
fn hold_mode_never_boosts_the_air() {
let (ceiling, safe) = ThermalMode::Hold.pstate_caps(8, Some(5));
let mut policy = Policy::new(false, safe, ceiling, 80_000);
for at in [0, 2 * S, 4 * S] {
policy.update(at, temp(60_000, at)).0.unwrap();
}
assert_eq!(policy.cap(), 2);
}
}

#[cfg(test)]
mod zone_tests {
use super::required_zones;
Expand Down
5 changes: 2 additions & 3 deletions drivers/gpu/drm/asahi/m3/t8122_knobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ pub(crate) const UNIT_MASK_A_LIMIT: u64 = 0x7_0000_0003;
pub(crate) const UNIT_MASK_B_LIMIT: u64 = 0x7;
/// The default performance-state ceiling: the lowest two states.
pub(crate) const PSTATE_CAP: u32 = 2;
/// The highest performance-state ceiling the experiment accepts. The same as the driver's own
/// limit when the fast-die temperature controller is off (`m3_adt_config::ADT_MAX_PSTATE_LIMIT`):
/// J613/J615 have no SoC die temperature zone yet, so there is no thermal feedback above it.
/// The highest performance-state ceiling the experiment accepts. States above the default
/// require the thermal governor and usable die-temperature readings, not just a higher ceiling.
pub(crate) const PSTATE_CAP_MAX: u32 = 5;

/// Parse a decimal or `0x`-prefixed hexadecimal `u64`. A leading sign is not accepted.
Expand Down
14 changes: 1 addition & 13 deletions drivers/gpu/drm/asahi/m3/t8122_start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,19 +301,7 @@ pub(crate) fn arm(dev: &device::Device, soc: &Soc) -> Result<Option<Experiment>>
e.mapping_count(),
storage::IOMAP_BASE
);
// J615 and J504 have no SoC die temperature zone yet, so a cap above the no-feedback limit
// runs the GPU without any temperature feedback (`m3_thermal` holds the cap but gets no
// reading). The runtime cap and the firmware ceiling stay equal, because the cap is at most
// the no-feedback limit.
if v.pstate_cap > knobs::PSTATE_CAP {
dev_warn!(
dev,
"M3 G15G start: pstate_cap={} is above {}: no SoC die temperature zone on this board, so states up to {} run with no temperature feedback\n",
v.pstate_cap,
knobs::PSTATE_CAP,
v.pstate_cap
);
}

Ok(Some(e))
}

Expand Down
20 changes: 20 additions & 0 deletions drivers/hwmon/macsmc-hwmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ static const struct macsmc_hwmon_board {
const char *compatible;
const char *die_keys;
const char *pressure_key;
bool require_all_die_keys;
} macsmc_hwmon_boards[] = {
/*
* MacBook Pro (16-inch, M3 Pro): key names used for the CPU
Expand Down Expand Up @@ -119,6 +120,18 @@ static const struct macsmc_hwmon_board {
.die_keys = "Tg0D,Tg0P,Tg0X,Tg0b,Tg0j,Tg0v,Te05,Te0P,Tp05,Tp0L,Tp0b,Tp0r",
.pressure_key = "mTPL",
},
/*
* MacBook Air (15-inch, M3): all twelve keys are readable floats on
* J615. The Tg readings rose during a GPU workload and fell after
* it stopped. Require the complete CPU/GPU set, not a CPU-only zone
* if discovery of a GPU key fails.
*/
{
.compatible = "apple,j615",
.die_keys = "Tg0D,Tg0P,Tg0X,Tg0b,Tg0j,Tg0v,Te05,Te0P,Tp05,Tp0L,Tp0b,Tp0r",
.pressure_key = "mTPL",
.require_all_die_keys = true,
},
};

static bool macsmc_hwmon_is_m3(void)
Expand Down Expand Up @@ -1139,6 +1152,7 @@ static void macsmc_hwmon_die_unregister(void *data)

static int macsmc_hwmon_die_register(struct macsmc_hwmon *hwmon)
{
const struct macsmc_hwmon_board *board = macsmc_hwmon_board();
struct thermal_zone_device *tz;
char keys[16 * 5] = "";
size_t len = 0;
Expand All @@ -1151,6 +1165,12 @@ static int macsmc_hwmon_die_register(struct macsmc_hwmon *hwmon)
hwmon->die_listed);
return 0;
}
if (board && board->require_all_die_keys &&
hwmon->die_count != hwmon->die_listed) {
dev_warn(hwmon->dev, "no SoC die temperature zone: only %u of %u required keys is present\n",
hwmon->die_count, hwmon->die_listed);
return 0;
}

for (i = 0; i < hwmon->temp.count && len + 6 <= sizeof(keys); i++) {
smc_key key = hwmon->temp.sensors[i].macsmc_key;
Expand Down
Loading