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
4 changes: 4 additions & 0 deletions drivers/gpu/drm/asahi/g17/clpc.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active)
{
apple_clpc_gpu_set_active(gpu, active);
}
void asahi_clpc_gpu_set_busy(struct apple_clpc_gpu *gpu, unsigned int permille)
{
apple_clpc_gpu_set_busy(gpu, permille);
}
void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu)
{
apple_clpc_gpu_put(gpu);
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/g17/clpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ struct apple_clpc_gpu;
int asahi_clpc_gpu_get(void *dev, struct apple_clpc_gpu **out);
void asahi_clpc_gpu_set_active(struct apple_clpc_gpu *gpu, bool active);
void asahi_clpc_gpu_put(struct apple_clpc_gpu *gpu);
void asahi_clpc_gpu_set_busy(struct apple_clpc_gpu *gpu, unsigned int permille);
#endif
5 changes: 4 additions & 1 deletion drivers/gpu/drm/asahi/g17/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ pub(crate) struct Config {
pub(crate) gpu_generation: hw::GpuGen,
/// The firmware builds this SoC runs.
pub(crate) firmware: fw::variant::Firmware,
/// Lower firmware effort for quiet GPU work; `None` runs every submission at full effort.
pub(crate) light_effort: Option<power::LightEffort>,
/// Layouts of the command buffers that follow the GPU's core layout.
pub(crate) core_layout: CoreLayout,
}
Expand Down Expand Up @@ -929,7 +931,7 @@ impl Firmware {
self.primary.state.shared.note_gpu_activity();
self.effort.submit();
self.effort.note_waiters(now_ns(), fence::waiters());
self.apply_effort(power::FULL_EFFORT)
self.apply_effort(power::submission_effort())
}

/// One governor step with the hotspot temperature reading; returns whether to run again and
Expand Down Expand Up @@ -1579,6 +1581,7 @@ impl Gpu {
cfg: &'static Config,
regs: regs::G17Resources,
) -> Result<Arc<Gpu>> {
power::configure_light(cfg.light_effort.as_ref());
let calibration = if cfg.chip_id == crate::hw::t6040::CHIP_ID {
Some(calibration::Calibration::read(dev).inspect_err(|_| {
dev_err!(
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/asahi/g17/port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,9 @@ impl Port {
pub(super) fn note_submission(&self) -> bool {
self.submissions.fetch_add(1, Ordering::SeqCst);
self.note_gpu_activity();
// Take the slow path when the governor wants a different effort.
self.effort_full.load(Ordering::SeqCst)
&& super::power::submission_effort() == super::power::FULL_EFFORT
}

pub(super) fn submissions(&self) -> u64 {
Expand Down Expand Up @@ -564,6 +566,7 @@ impl Port {
self.last_submission_ns
.store(super::now_ns(), Ordering::Relaxed);
self.demand.set_active(true);
self.demand.report_busy(super::now_ns());
}

pub(super) fn write_power(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
Expand Down
109 changes: 108 additions & 1 deletion drivers/gpu/drm/asahi/g17/power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! Work-based firmware performance effort. Idle power gating is managed separately by the
//! device-control ring; this input never keeps an otherwise idle GPU powered.

use core::sync::atomic::AtomicU32;
use core::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};

pub(super) const FULL_EFFORT: u32 = 0x10000;
pub(super) const IDLE_HOLD_NS: u64 = 16_000_000;
Expand Down Expand Up @@ -351,6 +351,86 @@ unsafe extern "C" {
fn asahi_clpc_gpu_get(dev: *mut core::ffi::c_void, out: *mut *mut core::ffi::c_void) -> core::ffi::c_int;
fn asahi_clpc_gpu_set_active(gpu: *mut core::ffi::c_void, active: bool);
fn asahi_clpc_gpu_put(gpu: *mut core::ffi::c_void);
fn asahi_clpc_gpu_set_busy(gpu: *mut core::ffi::c_void, permille: core::ffi::c_uint);
}

/// Busy-share window: long enough to span frames, short enough to follow a burst.
const BUSY_WINDOW_NS: u64 = 200_000_000;
/// A longer gap between reports was idle: start over rather than average it in.
const BUSY_WINDOW_MAX_NS: u64 = 2_000_000_000;
static BUSY_WINDOW_START: AtomicU64 = AtomicU64::new(0);
static BUSY_WINDOW_TOTAL: AtomicU64 = AtomicU64::new(0);

/// A window under this share (permille) with no long job is quiet.
/// Consecutive quiet windows needed to go light.
const LIGHT_WINDOWS: u32 = 4;
static LIGHT: AtomicBool = AtomicBool::new(false);
static QUIET_WINDOWS: AtomicU32 = AtomicU32::new(0);

/// A SoC's light effort for quiet GPU work. Light effort runs the same work about twice as
/// long, so the way back to full effort sits at about twice the way in, or work that fits
/// light effort sticks at full. Shares in permille of a busy window.
pub(crate) struct LightEffort {
/// Firmware performance effort, of 65536.
pub(crate) effort: u32,
/// A window below this share with no job longer than `job_enter_us` is quiet.
pub(crate) enter_permille: u32,
/// Back to full effort from this share, or from a job of `job_exit_us`.
pub(crate) exit_permille: u32,
pub(crate) job_enter_us: u32,
pub(crate) job_exit_us: u32,
}

static LIGHT_EFFORT: AtomicU32 = AtomicU32::new(0);
static LIGHT_ENTER: AtomicU32 = AtomicU32::new(0);
static LIGHT_EXIT: AtomicU32 = AtomicU32::new(0);
static LIGHT_JOB_ENTER_US: AtomicU32 = AtomicU32::new(0);
static LIGHT_JOB_EXIT_US: AtomicU32 = AtomicU32::new(0);

/// Takes the SoC's light effort from its configuration; `None` keeps full effort.
pub(super) fn configure_light(light: Option<&LightEffort>) {
let (effort, enter, exit, job_enter, job_exit) = light.map_or((0, 0, 0, 0, 0), |l| {
(l.effort, l.enter_permille, l.exit_permille, l.job_enter_us, l.job_exit_us)
});
LIGHT_ENTER.store(enter, Ordering::Relaxed);
LIGHT_EXIT.store(exit, Ordering::Relaxed);
LIGHT_JOB_ENTER_US.store(job_enter, Ordering::Relaxed);
LIGHT_JOB_EXIT_US.store(job_exit, Ordering::Relaxed);
LIGHT.store(false, Ordering::Relaxed);
QUIET_WINDOWS.store(0, Ordering::Relaxed);
LIGHT_EFFORT.store(effort, Ordering::Relaxed);
}

/// The effort for the next submission: light once the GPU has been quiet for a while.
pub(super) fn submission_effort() -> u32 {
let light = LIGHT_EFFORT.load(Ordering::Relaxed);
if LIGHT.load(Ordering::Relaxed) && light != 0 {
light
} else {
FULL_EFFORT
}
}

fn light_param(param: &AtomicU32) -> u64 {
u64::from(param.load(Ordering::Relaxed))
}

fn light_step(permille: u64, longest_ns: u64) {
if LIGHT_EFFORT.load(Ordering::Relaxed) == 0 {
return;
}
if LIGHT.load(Ordering::Relaxed) {
if permille >= light_param(&LIGHT_EXIT) || longest_ns >= light_param(&LIGHT_JOB_EXIT_US) * 1000 {
LIGHT.store(false, Ordering::Relaxed);
QUIET_WINDOWS.store(0, Ordering::Relaxed);
}
} else if permille < light_param(&LIGHT_ENTER) && longest_ns < light_param(&LIGHT_JOB_ENTER_US) * 1000 {
if QUIET_WINDOWS.fetch_add(1, Ordering::Relaxed) + 1 >= LIGHT_WINDOWS {
LIGHT.store(true, Ordering::Relaxed);
}
} else {
QUIET_WINDOWS.store(0, Ordering::Relaxed);
}
}

/// DT-selected memory-demand provider. Its independent allocation outlives a
Expand Down Expand Up @@ -378,6 +458,33 @@ impl Demand {
// SAFETY: the retained reference is live; NULL denotes no DT provider.
unsafe { asahi_clpc_gpu_set_active(self.0, active) };
}

/// Reports the share of the last window the GPU spent executing, once a window has run.
/// Of submitters racing at the end of a window, only the one that moves its start reports.
pub(super) fn report_busy(&self, now_ns: u64) {
let start = BUSY_WINDOW_START.load(Ordering::Relaxed);
let elapsed = now_ns.saturating_sub(start);
if elapsed < BUSY_WINDOW_NS
|| BUSY_WINDOW_START
.compare_exchange(start, now_ns, Ordering::AcqRel, Ordering::Relaxed)
.is_err()
{
return;
}
let total = crate::usage::TOTAL_NS.load(Ordering::Relaxed);
let busy = total.saturating_sub(BUSY_WINDOW_TOTAL.swap(total, Ordering::Relaxed));
if start == 0 || elapsed > BUSY_WINDOW_MAX_NS {
return;
}
let permille = busy.saturating_mul(1000) / elapsed;
light_step(permille, crate::usage::MAX_NS.swap(0, Ordering::Relaxed));
// Where light effort is configured, full effort does the same work in about half the
// time; report the share in light effort's terms so that one memory ladder fits both.
let light = LIGHT_EFFORT.load(Ordering::Relaxed) != 0;
let scaled = if !light || LIGHT.load(Ordering::Relaxed) { permille } else { permille * 2 };
// SAFETY: the retained reference is live; NULL denotes no DT provider.
unsafe { asahi_clpc_gpu_set_busy(self.0, scaled.min(1000) as core::ffi::c_uint) };
}
}

impl Drop for Demand {
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/hw/t6040.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub(crate) const CONFIG: Config = Config {
config: None,
}]),
// Measured on the 20-core M4 Pro; the alternative below remains experimental.
light_effort: None,
core_layout: CoreLayout {
cores: Some((2, 20)),
// Admit two clusters of ten slots with eight enabled cores in each cluster.
Expand Down
8 changes: 8 additions & 0 deletions drivers/gpu/drm/asahi/hw/t6041.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ pub(super) const BASE: Config = Config {
recovery_slot_keys: None,
gpu_generation: GpuGen::G16,
firmware: fw::variant::Firmware::Identified(&[]),
// Measured on the M4 Max only: scrolling 24.9 -> 22.1 W with the T6041 CLPC table.
light_effort: Some(crate::g17::power::LightEffort {
effort: 0x8000,
enter_permille: 350,
exit_permille: 750,
job_enter_us: 3000,
job_exit_us: 7000,
}),
core_layout: CoreLayout {
cores: Some((4, 40)),
alternate_core_masks: None,
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/asahi/hw/t6050.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ pub(crate) const CONFIG: Config = Config {
}]),
// Two clusters of ten cores. The buffers are sized for every core slot of the board, so the
// board's cluster and slot counts admit the GPU, whichever cores are fused off.
light_effort: None,
core_layout: CoreLayout {
cores: None,
alternate_core_masks: None,
Expand Down Expand Up @@ -143,6 +144,7 @@ pub(crate) const CONFIG_MAX: Config = Config {
}]),
// Four clusters of ten cores. The buffers are sized for every core slot of the board, so the
// board's cluster and slot counts admit the GPU, whichever cores are fused off.
light_effort: None,
core_layout: CoreLayout {
cores: None,
alternate_core_masks: None,
Expand Down Expand Up @@ -229,6 +231,7 @@ pub(crate) const CONFIG_ULTRA: Config = Config {
// (`fw::m5::render_registers`), so the deflake layout is the M5 Pro's. As on the M5 Pro, the
// buffers are sized for all 80 core slots and the board admits the GPU by clusters and slots:
// parts with fused-off cores (64 enabled) take the same layout.
light_effort: None,
core_layout: CoreLayout {
cores: None,
alternate_core_masks: None,
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/hw/t8140.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub(crate) const CONFIG: Config = Config {
sleep_restart: true,
firmware: crate::g17::fw::variant::Firmware::Fixed(&crate::g17::fw::variant::V26A428),
// The layouts the A18 Pro has always run with.
light_effort: None,
core_layout: crate::g17::CoreLayout {
cores: None,
alternate_core_masks: None,
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/hw/t8142.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub(crate) const PROBE: Config = Config {
recovery_slot_keys: None,
gpu_generation: GpuGen::G17,
firmware: crate::g17::fw::variant::Firmware::Fixed(&crate::g17::fw::variant::V26A428_M5),
light_effort: None,
core_layout: CoreLayout {
cores: None,
alternate_core_masks: None,
Expand Down
7 changes: 7 additions & 0 deletions drivers/gpu/drm/asahi/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

use core::sync::atomic::{AtomicU64, Ordering};

/// Engine time charged across all clients, in ns, for the GPU busy share.
pub(crate) static TOTAL_NS: AtomicU64 = AtomicU64::new(0);
/// Longest single stage charged since the governor last read it, in ns.
pub(crate) static MAX_NS: AtomicU64 = AtomicU64::new(0);

#[derive(Clone, Copy)]
pub(crate) enum Engine {
Vertex,
Expand Down Expand Up @@ -35,6 +40,8 @@ impl Client {
return;
}
let ns = ticks_to_ns(end - start, frequency_hz);
TOTAL_NS.fetch_add(ns, Ordering::Relaxed);
MAX_NS.fetch_max(ns, Ordering::Relaxed);
let counter = &self.ns[engine as usize];
let mut old = counter.load(Ordering::Relaxed);
loop {
Expand Down
Loading
Loading