From 7b7c2377a78424f7f63fb06b652ab0434f5b662e Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Mon, 9 Nov 2026 18:11:48 +0000 Subject: [PATCH 1/2] drm/asahi: add the T6041 25G83 profile captured on the J616C The 32-core M4 Max in the J616C boots through a macOS 26.6.2 stub (iBoot mBoot-18000.161.10) and loads the T6041 25G83 image that "drm/asahi: run the 25G83 build on the 40-core M4 Max" admits. That build runs the 40-core configuration, whose core check refuses this GPU. Admit the image a second time, with a 32-core configuration (hw/t6041_25g83.rs). When builds share an image, the selector now takes the one whose configuration matches the probed clusters and enabled cores, else the first one, so a 40-core part runs as before and another layout is still refused by the core check. The GPU ID is read before the build is selected for this. T6040 and T8140 are unchanged. The configuration: - 4 clusters with 32 enabled cores. Operand-state banks for all 40 core slots: the enabled slots run to 39 (core mask 0xf6_fdbf6fdb), and with banks for the 32 enabled cores only, a compute load beside rendering hit reason-0 recoveries in 5 of 5 runs, and in 0 of 4 with 40 banks. Four-cluster deflake and TA mode 1, as the 40-core 25G83 build. - A compute pool of 96 blocks, which the J616C has run with since 23 Oct 2026; 21 blocks hang its firmware on grids of more than ten workgroups. - The 40-core 25G83 register map with six memory controllers in slot 3 (ADT active-mcc-bit-vector 0x3f), laid out by the same rule. A compile-time check holds it to that rule and to the 40-core windows. What varies between units comes from the device tree, as on the 40-core part: the boot loader fills the operating points with one voltage per cluster (from the ADT perf-states tables) and the CS and AFR tables (from cs-perf-states and afr-perf-states), and the 25G83 builders turn them into the performance, CS and AFR tables of the hardware data. The previous version of this change applied contents captured on this unit, binned operating points included, because its device tree had no GPU tables at the time. The rest is static, because the driver has no source for it on T6041: - The selector ladders: the highest and lowest of this unit's operating points at each of its 11 core voltages (state 5 of ladder B is OPP 5, where the 40-core profile has OPP 6), and the M4 Pro's relative ladders. - The power limit, 110586 mW (the M4 Pro's 69116 mW for 20 cores, scaled to 32), and the M4 Pro's leakage coefficients for four clusters. Neither is in the ADT, and neither is a measurement of this unit. - The AON rows of the four GPU clusters and the SoC, from the ADT's aon-ptd ptd_data_table, which the loader does not publish. - Five power configuration words: -1000 / power limit at +0x9ec, the second temperature-sensor mask word at +0x3b5c, the temperature sources of clusters 2 and 3 at +0x6328 and +0x632c, and 5 at +0x6360. Built on the host from the J616C's ADT operating points, the hardware data, power configuration and region C hash (FNV-1a 64 without the runtime words) to those of the contents this unit has run with since October 2026, dumped on 25 Oct 2026. Those contents, and so the static values above, were built by the M4 GPU encoders of Gravity Linux (g16_fw.rs, g16_platform.rs, g16_power.rs), with Jonathan Goyvaerts' T6040 values. Based on work by Cody Ho, who wrote the encoders (GravityLinux bd25d194b0a2, cherry-picked from 778088823509, which carries no Signed-off-by), and Niklas Sheth, who added the bootloader power limit (GravityLinux 3a2ad760d731). Not run on hardware, and not built in a kernel tree: the host tests compile the build selector, the configuration's constants with the 25G83 builders, and its compile-time checks. Link: https://github.com/GravityLinux/linux/commit/bd25d194b0a295a75692a0e6caed3793603b9e65 Link: https://github.com/GravityLinux/linux/commit/3a2ad760d7316813f37c0ddc653ef783827f855b Link: https://github.com/aurora-silicon/linux/pull/1050 Co-developed-by: Niklas Sheth Signed-off-by: Niklas Sheth Signed-off-by: AxxzyWasTaken --- drivers/gpu/drm/asahi/fwid.rs | 13 +- drivers/gpu/drm/asahi/g17/fw/render.rs | 11 +- drivers/gpu/drm/asahi/g17/mod.rs | 34 +++- drivers/gpu/drm/asahi/hw/mod.rs | 1 + drivers/gpu/drm/asahi/hw/t6041.rs | 26 ++- drivers/gpu/drm/asahi/hw/t6041_25g83.rs | 210 ++++++++++++++++++++++++ 6 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 drivers/gpu/drm/asahi/hw/t6041_25g83.rs diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index 514f09539dc73..2aeca708359d8 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -254,8 +254,13 @@ mod tests { else { panic!("T6041 must identify its loaded firmware"); }; - assert_eq!(builds.len(), 2); - for (i, build) in builds.iter().enumerate() { + assert_eq!(builds.len(), 3); + // The last two admit the 25G83 images, with the 40- and the 32-core configuration. + assert_eq!(builds[1].images.len(), builds[2].images.len()); + for (a, b) in builds[1].images.iter().zip(builds[2].images) { + assert_eq!((a.entropy, a.text_sha256), (b.entropy, b.text_sha256)); + } + for (i, build) in builds[..2].iter().enumerate() { let other = &builds[1 - i]; for image in build.images { let (entropy, digest) = (image.entropy, image.text_sha256); @@ -277,6 +282,10 @@ mod tests { let cores = crate::hw::t6041::CONFIG.core_layout.cores; assert_eq!(g83.core_layout.cores, cores); assert_eq!(cores, Some((4, 40))); + let g83_32 = builds[2].config.unwrap(); + assert_eq!(g83_32.core_layout.cores, Some((4, 32))); + assert!(g83_32.reserved_slots.is_empty()); + assert_eq!(g83_32.windows.len(), map.len()); } #[cfg(CONFIG_DRM_ASAHI_G16)] diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index f63821c904fd2..88828bfefb25b 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -1629,13 +1629,18 @@ mod ta_mode_tests { } } - /// The variants of the builds T6041 admits: 25F84, then 25G83. + /// The variants of the builds T6041 admits: 25F84, then 25G83, which the 40- and the + /// 32-core configurations run with the same rules. fn t6041_variants() -> [&'static Variant; 2] { let Firmware::Identified(builds) = crate::hw::t6041::CONFIG.firmware else { panic!("T6041 must identify its loaded firmware"); }; - assert_eq!(builds.len(), 2); - [builds[0].variant, builds[1].variant] + assert_eq!(builds.len(), 3); + let (g83, g83_32) = (builds[1].variant, builds[2].variant); + assert_eq!(g83.ta_mode_1, g83_32.ta_mode_1); + assert_eq!(g83.compute, g83_32.compute); + assert_eq!(g83.programs, g83_32.programs); + [builds[0].variant, g83] } /// The TA mode of every admitted T6041 build. diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index df5a31f621a3c..a13ff85529eb2 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1173,10 +1173,12 @@ impl Gpu { } else { None }; - let (variant, build_cfg) = Self::firmware_variant(dev, &cfg.firmware)?; + let id = regs.get_g17_id()?; + let active_cores: u32 = id.core_masks.iter().map(|mask| mask.count_ones()).sum(); + let (variant, build_cfg) = + Self::firmware_variant(dev, &cfg.firmware, (id.num_clusters, active_cores))?; // A build with its own configuration of the SoC runs with that one. let cfg = build_cfg.unwrap_or(cfg); - let id = regs.get_g17_id()?; if let Some(calibration) = calibration { calibration.check_cores(&id.core_masks)?; calibration.check_opps(&initdata::InitData::opps(dev, cfg.opps, id.num_clusters)?)?; @@ -1216,7 +1218,6 @@ impl Gpu { let renders = availability::Availability::new()?; let reclaim = runtime::teardown::ReclaimBatch::new()?; let feed = feed::Feed::new()?; - let active_cores = id.core_masks.iter().map(|mask| mask.count_ones()).sum(); let alternate = cfg.core_layout.cores.is_some_and(|(clusters, cores)| { clusters == id.num_clusters && cores != active_cores @@ -1329,9 +1330,15 @@ impl Gpu { /// Returns the variant of the loaded firmware build and the configuration it runs with, if /// it has its own. An identified SoC refuses a build it does not know, before anything is /// written to the GPU. + /// + /// Builds can admit the same image with configurations measured on different core layouts. + /// The first identified build whose configuration admits `cores` (clusters and enabled + /// cores, as probed) is chosen, else the first identified one, whose core check then + /// refuses the GPU. fn firmware_variant( dev: &AsahiDrmDevice, firmware: &fw::variant::Firmware, + cores: (u32, u32), ) -> Result<(&'static fw::Variant, Option<&'static Config>)> { let builds = match firmware { fw::variant::Firmware::Fixed(variant) => return Ok((variant, None)), @@ -1346,11 +1353,26 @@ impl Gpu { let others = builds .iter() .filter(|build| Some(build.os_version) != version); + let mut identified = None; for build in Iterator::chain(preferred, others) { - if Self::identify_image(dev, build).is_ok() { - dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); - return Ok((build.variant, build.config)); + if Self::identify_image(dev, build).is_err() { + continue; } + let admits = build.config.is_none_or(|config| { + config + .core_layout + .cores + .is_none_or(|measured| measured == cores) + }); + if admits { + identified = Some(build); + break; + } + identified.get_or_insert(build); + } + if let Some(build) = identified { + dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); + return Ok((build.variant, build.config)); } dev_err!( dev.as_ref(), diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs index 438d071de971c..bf1fd92822ced 100644 --- a/drivers/gpu/drm/asahi/hw/mod.rs +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -15,6 +15,7 @@ pub(crate) mod t600x; pub(crate) mod t602x; pub(crate) mod t6040; pub(crate) mod t6041; +mod t6041_25g83; mod t604x; pub(crate) mod t8103; pub(crate) mod t8112; diff --git a/drivers/gpu/drm/asahi/hw/t6041.rs b/drivers/gpu/drm/asahi/hw/t6041.rs index 956aa0cc7728b..04ddfacd53cec 100644 --- a/drivers/gpu/drm/asahi/hw/t6041.rs +++ b/drivers/gpu/drm/asahi/hw/t6041.rs @@ -9,9 +9,9 @@ use crate::hw::{GpuGen, GpuVariant}; use super::t604x::{instances, window}; use crate::mmu::UatQuirks; -/// What the configurations of both firmware builds share: the 40-core M4 Max measured with -/// 25F84. Only the register map is the build's own. -const BASE: Config = Config { +/// What the configurations of the firmware builds share: the 40-core M4 Max measured with +/// 25F84. The 40-core 25G83 configuration only has its own register map. +pub(super) const BASE: Config = Config { chip_id: CHIP_ID, identity: Identity { family: 0xa, @@ -58,18 +58,26 @@ const BASE: Config = Config { }, }; -/// The 40-core M4 Max. 25F84 runs this configuration; 25G83 runs [`CONFIG_25G83`]. +/// The 40-core M4 Max. 25F84 runs this configuration; 25G83 runs [`CONFIG_25G83`], or +/// [`super::t6041_25g83::CONFIG`] on a 32-core part. pub(crate) const CONFIG: Config = Config { firmware: fw::variant::Firmware::Identified(&BUILDS), ..BASE }; -const BUILDS: [fw::variant::Build; 2] = [ +/// The 25G83 image is admitted twice, with the configuration of each measured core layout; +/// the one that matches the probed cores is used. +const BUILDS: [fw::variant::Build; 3] = [ BUILD_25F84, fw::variant::Build { config: Some(&CONFIG_25G83), ..BUILD_25G83 }, + fw::variant::Build { + name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10), 32 cores", + config: Some(&super::t6041_25g83::CONFIG), + ..BUILD_25G83 + }, ]; const BUILD_25F84: fw::variant::Build = fw::variant::Build { @@ -96,9 +104,9 @@ const BUILD_25F84: fw::variant::Build = fw::variant::Build { /// The macOS 26.6.2 boot firmware (iBoot mBoot-18000.161.10) loads the 25G83 build the M4 Pro /// runs. iBoot fills part of the loaded TEXT for each Mac, so each admitted image is a -/// measurement. The one listed is the only T6041 image measured so far, on a 32-core J616C -/// (linux#1050); there the core count check of [`CONFIG_25G83`] refuses the GPU. -const BUILD_25G83: fw::variant::Build = fw::variant::Build { +/// measurement. The one listed is the only T6041 image measured so far, on a 32-core J616C, +/// which runs it with [`super::t6041_25g83::CONFIG`]. +pub(super) const BUILD_25G83: fw::variant::Build = fw::variant::Build { os_version: [26, 6, 2], name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)", images: &[crate::fwid::FirmwareBuild { @@ -252,7 +260,7 @@ const _: () = { const MAX_POWER_MW: u32 = 129_702; -const REGION_C: RegionCConfig = RegionCConfig { +pub(super) const REGION_C: RegionCConfig = RegionCConfig { unk_54: 0x0fa0, unk_78: [1, 0, 0], max_power_mw: MAX_POWER_MW, diff --git a/drivers/gpu/drm/asahi/hw/t6041_25g83.rs b/drivers/gpu/drm/asahi/hw/t6041_25g83.rs new file mode 100644 index 0000000000000..370561d906bf2 --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t6041_25g83.rs @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright The Gravity Linux Contributors +// +// The power configuration words, leakage coefficients, power limit and AON rows of this +// profile are what the M4 GPU encoders of Gravity Linux (g16_fw.rs, g16_platform.rs and +// g16_power.rs, written by Cody Ho, with the bootloader power limit added by Niklas Sheth) +// built on a 32-core J616C: +// https://github.com/GravityLinux/linux/commit/bd25d194b0a295a75692a0e6caed3793603b9e65 +// https://github.com/GravityLinux/linux/commit/3a2ad760d7316813f37c0ddc653ef783827f855b + +//! T6041 (M4 Max) with 32 cores on the 25G83 firmware build, as the J616C runs it. +//! +//! The J616C boots through a macOS 26.6.2 stub (iBoot mBoot-18000.161.10), which loads the +//! 25G83 image [`t6041::BUILD_25G83`] admits. Its GPU has 32 of 40 cores in four clusters, so +//! the 40-core configuration of that build refuses it; this one is selected instead. +//! +//! What differs between units comes from the device tree, which the boot loader fills from the +//! ADT: the operating points with one voltage per cluster (`operating-points-v2`, from the +//! ADT `perf-states` tables) and the CS and AFR tables (`apple,cs-opp`, `apple,afr-opp`, from +//! `cs-perf-states` and `afr-perf-states`). The builders turn them into the performance, CS and +//! AFR tables of the hardware data. The enabled cores are probed from the GPU. +//! +//! Everything else is static, because the driver has no source for it on T6041: +//! - [`PERF`]: the selector ladders, see there. +//! - [`MAX_POWER_MW`] and the leakage coefficients of [`CHIP`]: not in the ADT. They are the +//! M4 Pro's values (its per-core power budget for 32 cores, its per-cluster coefficients for +//! four clusters), not values of this unit. +//! - [`AON_TABLE`]: the GPU cluster and SoC rows of the ADT's `/arm-io/aon-ptd` +//! `ptd_data_table`, which the loader does not publish. +//! - [`REGISTER_WINDOWS`]: slot 3 holds this part's six memory controllers (ADT +//! `/arm-io/sgx` `active-mcc-bit-vector` 0x3f). +//! +//! Built from the device tree of the J616C, the hardware data and region C equal what this +//! unit ran with since October 2026, and the power configuration does with [`CHIP`]'s words. + +use super::t6041; +use super::t604x::{instances, window}; +use crate::g17::fw::{ + self, + initdata::{v25g83, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, +}; +use crate::g17::{Config, CoreLayout}; + +pub(super) const CONFIG: Config = Config { + windows: ®ISTER_WINDOWS, + reserved_slots: &[], + perf: &PERF, + region_c: ®ION_C, + chip: Some(&CHIP), + firmware: fw::variant::Firmware::Identified(&[t6041::BUILD_25G83]), + core_layout: CoreLayout { + cores: Some((4, 32)), + alternate_core_masks: None, + // Banks for all forty core slots: the firmware indexes them by physical slot, and the + // J616C's enabled slots run to 39 (core mask 0xf6_fdbf6fdb). With banks for the 32 + // enabled cores only, a compute load beside rendering hit reason-0 recoveries. + operand_state: fw::compute::OperandState::FOUR_BY_TEN, + deflake: fw::render::Deflake::FOUR_CLUSTERS, + // The compute pool the J616C has run with since 23 Oct 2026; the M4 Pro's 21 blocks + // hang this GPU's firmware on grids of more than ten workgroups. + compute_blocks: 96, + }, + ..t6041::BASE +}; + +/// The register map of [`t6041::REGISTER_WINDOWS_25G83`] with six memory controller +/// instances in slot 3 instead of eight, laid out by the same rule, so every window after it +/// lies 0x30000 lower. +const REGISTER_WINDOWS: [RegisterWindow; 25] = [ + window(0, 0x5_0241_4000, 0x20_0000, 0x4000, 0, 2), + instances( + window(3, 0x2_201c_4000, 0x20_8000, 0x1_8000, 0, 2), + 6, + 0x200_0000, + ), + window(4, 0x5_0244_c000, 0x29_c000, 0x4000, 0, 0), + window(9, 0x5_003d_0000, 0x2a_4000, 0x4000, 0, 2), + window(10, 0x5_003c_0000, 0x2a_c000, 0x4000, 0, 0), + window(11, 0x5_003d_8000, 0x2b_4000, 0x4000, 0, 2), + window(12, 0x4_8565_c000, 0x2b_c000, 0x4000, 0, 2), + window(14, 0x5_0040_0000, 0x2c_4000, 0x1_0000, 0, 0), + window(15, 0x5_0cdc_0000, 0x2d_8000, 0x4000, 0, 0), + window(17, 0x2_8800_0000, 0x2e_0000, 0x2_1400, 0, 2), + instances( + window(20, 0x2_8b00_0000, 0x30_8000, 0x4_0000, 0, 2), + 4, + 0x20_0000, + ), + window(22, 0x2_8900_0000, 0x40_c000, 0x8000, 0, 2), + window(26, 0x2_88d0_4000, 0x41_8000, 0x8000, 0xd0_4000, 2), + window(28, 0x2_88d5_8000, 0x42_4000, 0x8000, 0xd5_8000, 2), + window(29, 0x2_88d1_0000, 0x43_0000, 0x4000, 0xd1_0000, 2), + window(31, 0x2_88d4_0000, 0x43_8000, 0x4000, 0xd4_0000, 2), + window(32, 0x2_88d6_0000, 0x44_0000, 0x2_0000, 0xd6_0000, 2), + window(33, 0x2_88d4_4000, 0x46_4000, 0x4000, 0xd4_4000, 2), + window(34, 0x2_88d4_c000, 0x46_c000, 0x200, 0xd4_c000, 2), + window(35, 0x2_88e0_0000, 0x47_4000, 0x4000, 0xe0_0000, 2), + window(37, 0x2_88e4_0000, 0x47_c000, 0x4000, 0xe4_0000, 2), + window(39, 0x2_88e0_8000, 0x48_4000, 0x8000, 0, 2), + window(40, 0x2_88e1_c000, 0x49_0000, 0x4000, 0xe1_c000, 2), + window(41, 0x2_88e1_f800, 0x49_b800, 0x4000, 0, 2), + window(43, 0x2_88e6_0000, 0x4a_4000, 0x58, 0xe6_0000, 2), +]; + +// The windows of the 40-core 25G83 map, with six controllers, laid out by its rule. +const _: () = { + assert!(super::t604x::laid_out_25g83(®ISTER_WINDOWS)); + let mut index = 0; + while index < REGISTER_WINDOWS.len() { + let (max, ours) = ( + &t6041::REGISTER_WINDOWS_25G83[index], + ®ISTER_WINDOWS[index], + ); + assert!( + max.slot == ours.slot + && max.phys == ours.phys + && max.size == ours.size + && max.unk_18 == ours.unk_18 + && max.unk_20 == ours.unk_20 + && max.phys_stride == ours.phys_stride + && (max.instances == ours.instances || (max.slot == 3 && ours.instances == 6)) + ); + index += 1; + } +}; + +/// The selector ladders. `states_a` and `states_b` are the highest and lowest of the J616C's +/// operating points at each of its 11 core voltages, in ADT order; the 40-core 25F84 profile +/// selects OPP 6 for state 5 of `states_b`, this one OPP 5. The relative ladders are the M4 +/// Pro's. The driver has no source for them on T6041. +const PERF: PerfConfig = PerfConfig { + states_a: [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15], + states_b: [0, 1, 2, 3, 4, 5, 7, 9, 11, 13, 15], + relative_a: [0, 9, 16, 23, 29, 38, 49, 60, 69, 79, 100], + relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + sram_min_mv: 810, +}; + +const _: () = { + let mut index = 0; + while index < PERF_STATES { + assert!((PERF.states_a[index] as usize) < t6041::OPPS); + assert!((PERF.states_b[index] as usize) < t6041::OPPS); + index += 1; + } +}; + +/// The M4 Pro's power budget per core (69116 mW for 20) for 32 cores. +const MAX_POWER_MW: u32 = 110_586; + +const REGION_C: RegionCConfig = RegionCConfig { + max_power_mw: MAX_POWER_MW, + ..t6041::REGION_C +}; + +/// The AON temperature-source rows (12 bytes each) the power configuration names: GPU clusters +/// 0 to 3 and the SoC, as in the ADT's `ptd_data_table`. +const AON_TABLE: [u32; 15] = [ + u32::from_be_bytes(*b"g0TR"), + 0x00c2_0001, + 0x0000_1401, + u32::from_be_bytes(*b"g1TR"), + 0x0097_0001, + 0x0000_1401, + u32::from_be_bytes(*b"g2TR"), + 0x0092_0001, + 0x0000_1401, + u32::from_be_bytes(*b"g3TR"), + 0x00c7_0001, + 0x0000_1401, + u32::from_be_bytes(*b"r0TR"), + 0x012c_0001, + 0x0000_1401, +]; + +const CHIP: v25g83::Chip = v25g83::Chip { + max_power_mw: MAX_POWER_MW, + // The M4 Pro's per-cluster core coefficients for four clusters, its SRAM, CS and AFR ones. + leakage: { + let mut words = [0; 56]; + words[0] = 0x44f0_3000; + words[1] = 0x44f3_0000; + words[2] = 0x44f0_3000; + words[3] = 0x44f3_0000; + words[32] = 0x4373_0000; + words[33] = 0x4373_0000; + words[34] = 0x4373_0000; + words[35] = 0x4373_0000; + words[52] = 0x437b_0000; + words[54] = 0x43bd_c000; + words + }, + aon_table: &AON_TABLE, + hw_data_words: &[], + power_config_words: POWER_CONFIG_WORDS, + perf_a_replicate_lanes: false, + cs_perf_table: true, +}; + +/// What differs from the M4 Pro's power configuration besides the power limit and leakage. +const POWER_CONFIG_WORDS: &[(usize, u32, usize)] = &[ + // -1000 / MAX_POWER_MW, as the M4 Pro's word is for its 69116 mW. + (0x9ec, 0xbc14_27fc, 1), + // The second temperature-sensor mask word. + (0x3b5c, 0x2a22_2aa2, 1), + // The temperature sources of GPU clusters 2 and 3. + (0x6328, u32::from_be_bytes(*b"g2TR"), 1), + (0x632c, u32::from_be_bytes(*b"g3TR"), 1), + (0x6360, 0x5, 1), +]; From 226a65c7dc5249927f03abe65d80382776dc08bb Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Mon, 9 Nov 2026 18:11:48 +0000 Subject: [PATCH 2/2] tools/asahi: test the 32-core T6041 25G83 configuration on the host The build selector now takes the probed core layout. Model it, and check that of two builds admitting one image, the configuration of the probed layout is chosen, that another layout gets the first one, and that the layout never changes which image is admitted. Build the 32-core T6041 25G83 objects from the operating points the loader publishes from the J616C's ADT (perf-states for four clusters, cs-perf-states and afr-perf-states), and check that the hardware data, power configuration and region C hash (FNV-1a 64, without the runtime words) to those of the contents dumped on that unit on 25 Oct 2026, and that the word at +0x9ec is -1000 / the power limit. Signed-off-by: AxxzyWasTaken --- tools/asahi/g17-firmware-selection-tests.py | 49 +++++++++++++++---- tools/asahi/g17-t6041-initdata-tests.py | 53 ++++++++++++++++++++- 2 files changed, 91 insertions(+), 11 deletions(-) diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py index 2a3cae91fdb08..6b9cc52d70ea7 100644 --- a/tools/asahi/g17-firmware-selection-tests.py +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -34,7 +34,13 @@ def method(source, signature): image: u32, checked: std::cell::RefCell>, } -struct Config(u32); +struct CoreLayout { + cores: Option<(u32, u32)>, +} +struct Config { + id: u32, + core_layout: CoreLayout, +} mod fw { pub struct Variant(pub u32); pub mod variant { @@ -62,12 +68,23 @@ def method(source, signature): } static FIRST: fw::Variant = fw::Variant(1); static SECOND: fw::Variant = fw::Variant(2); -static OWN: Config = Config(7); +static OWN: Config = Config { id: 7, core_layout: CoreLayout { cores: None } }; static BUILDS: [fw::variant::Build; 2] = [ fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST, config: None }, fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND, config: Some(&OWN) }, ]; +// One image admitted with the configurations of two core layouts, as T6041 25G83 is. +static FORTY: Config = Config { id: 40, core_layout: CoreLayout { cores: Some((4, 40)) } }; +static THIRTY_TWO: Config = Config { id: 32, core_layout: CoreLayout { cores: Some((4, 32)) } }; +static SHARED: [fw::variant::Build; 3] = [ + fw::variant::Build { os_version: [26,5,2], name: "other", image: 1, variant: &FIRST, config: None }, + fw::variant::Build { os_version: [26,6,2], name: "40", image: 2, variant: &SECOND, + config: Some(&FORTY) }, + fw::variant::Build { os_version: [26,6,2], name: "32", image: 2, variant: &SECOND, + config: Some(&THIRTY_TWO) }, +]; +const CORES: (u32, u32) = (4, 40); fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } } @@ -75,26 +92,40 @@ def method(source, signature): use fw::variant::Firmware; for version in [None, Some([26,6,2]), Some([26,7,0]), Some([99,0,0])] { let dev = device(version, 1); - let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap(); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS), CORES).unwrap(); assert_eq!(variant.0, 1); assert!(config.is_none(), "a build without its own configuration keeps the probed one"); } let dev = device(Some([27,0,0]), 1); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0.0, 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS), CORES).unwrap().0.0, 1); assert_eq!(*dev.checked.borrow(), [2,1], "the OS version only orders the candidates"); let dev = device(None, 2); - let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap(); - assert_eq!((variant.0, config.map(|c| c.0)), (2, Some(7)), "the identified build's configuration"); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS), CORES).unwrap(); + assert_eq!((variant.0, config.map(|c| c.id)), (2, Some(7)), "the identified build's configuration"); let dev = device(Some([26,6,2]), 9); - assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).is_err()); + assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS), CORES).is_err()); assert_eq!(*dev.checked.borrow(), [1,2]); let dev = device(None, 9); - let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap(); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND), CORES).unwrap(); assert_eq!(variant.0, 2); assert!(config.is_none()); assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); + for (cores, expected) in [((4, 40), 40), ((4, 32), 32), ((4, 36), 40)] { + for version in [None, Some([26,6,2])] { + let dev = device(version, 2); + let (variant, config) = + Gpu::firmware_variant(&dev, &Firmware::Identified(&SHARED), cores).unwrap(); + assert_eq!((variant.0, config.map(|c| c.id)), (2, Some(expected)), + "the configuration of the probed core layout, else the first one"); + } + } + let dev = device(None, 1); + let (variant, config) = + Gpu::firmware_variant(&dev, &Firmware::Identified(&SHARED), (4, 32)).unwrap(); + assert_eq!(variant.0, 1); + assert!(config.is_none(), "the core layout does not change which image is admitted"); println!("PASS G17 selection: exact image required, OS hint optional, build configuration \ - follows the image, fixed Neo unchanged"); + follows the image and the probed core layout, fixed Neo unchanged"); } ''' diff --git a/tools/asahi/g17-t6041-initdata-tests.py b/tools/asahi/g17-t6041-initdata-tests.py index a48174df48547..f243c34f4b294 100644 --- a/tools/asahi/g17-t6041-initdata-tests.py +++ b/tools/asahi/g17-t6041-initdata-tests.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""Build T6041 initialization data and compare it with captures when given.""" +"""Build T6041 initialization data and compare it with captures when given. + +The 32-core 25G83 configuration is also built from the operating points the loader publishes +from the J616C's ADT, and must give the contents that unit ran with.""" import argparse import re @@ -110,6 +113,16 @@ def item(source, start): "const POWER_CONFIG_WORDS", ] +J616C_ITEMS = [ + "const REGISTER_WINDOWS: [", + "const PERF: PerfConfig", + "const MAX_POWER_MW", + "const REGION_C", + "const AON_TABLE", + "const CHIP: ", + "const POWER_CONFIG_WORDS", +] + T6041_OPP_HZ = [0, 338, 618, 796, 924, 952, 1056, 1062, 1182, 1182, 1312, 1242, 1380, 1326, 1470, 1578] T6041_OPP_MV = [ @@ -121,6 +134,19 @@ def item(source, start): T6041_AUX = [(24, 585000), (338, 585000), (618, 635000), (796, 685000), (992, 720000), (1188, 770000), (1380, 855000), (1470, 885000)] +# The 32-core J616C's ADT /arm-io/sgx: perf-states (one table per cluster) and cs-perf-states +# (afr-perf-states is the same), as the loader publishes them in the device tree. +J616C_OPP_HZ = [0, 338, 618, 796, 924, 952, 1056, 1062, 1182, 1182, 1312, 1242, 1380, 1326, + 1470, 1578] +J616C_OPP_MV = [ + [125, 600, 650, 695, 720, 770, 770, 815, 815, 870, 870, 910, 910, 950, 950, 1030], + [125, 595, 645, 690, 720, 760, 760, 805, 805, 850, 850, 890, 890, 930, 930, 1025], + [125, 615, 660, 705, 725, 775, 775, 805, 805, 855, 855, 895, 895, 935, 935, 1030], + [125, 600, 660, 700, 715, 770, 770, 805, 805, 860, 860, 900, 900, 940, 940, 1020], +] +J616C_AUX = [(24, 585000), (338, 585000), (618, 630000), (796, 680000), (992, 720000), + (1188, 770000), (1380, 865000), (1470, 900000)] + def opps(name, hz, mv): rows = [] @@ -205,6 +231,26 @@ def aux(name, rows): .filter(|(i, (x, y))| x != y && !map.contains(i)).count(); assert_eq!(outside(&hw_g83, &hw), 0, "the 25G83 hardware data differs only in its register map"); std::fs::write(format!("{}/t6041-25g83-hwdata.bin", out), &hw_g83).unwrap(); + // The 32-core J616C on 25G83, from its operating points. What it ran with since October + // 2026, hashed without the runtime words (FNV-1a 64), from a dump of 25 Oct 2026. + let (hw32, power32, region32) = build(t6041::CHIP_ID, &t6041_25g83::REGISTER_WINDOWS, &[], + &t6041_25g83::PERF, &J616C_OPPS, &J616C_AUX, &t6041_25g83::CHIP, + &t6041_25g83::REGION_C, 32); + println!("T6041 25G83 32-core initialization objects built"); + let fnv = |bytes: &[u8], skip: &[(usize, usize)]| { + bytes.iter().enumerate().filter(|(i, _)| !skip.iter().any(|&(at, n)| (at..at + n).contains(i))) + .fold(0xcbf2_9ce4_8422_2325u64, |h, (_, &b)| (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3)) + }; + assert_eq!(fnv(&hw32, &[(0x2620, 0x80), (0x26b8, 8)]), 0xd781_973f_9983_1e35, + "the J616C hardware data"); + assert_eq!(fnv(&power32, &[(0x3b50, 8), (0x6318, 8), (0x63d4, 4)]), 0xc36d_7dba_a597_aac4, + "the J616C power configuration"); + assert_eq!(fnv(®ion32, &[]), 0x91a4_c670_17e3_0a24, "the J616C region C"); + let word = |at: usize| u32::from_le_bytes(power32[at..at + 4].try_into().unwrap()); + assert_eq!(word(0x9ec), (-1000.0 / t6041_25g83::MAX_POWER_MW as f32).to_bits()); + println!("T6041 25G83 32-core objects match the J616C's"); + std::fs::write(format!("{}/t6041-25g83-32-hwdata.bin", out), &hw32).unwrap(); + std::fs::write(format!("{}/t6041-25g83-32-power-config.bin", out), &power32).unwrap(); } """ @@ -222,12 +268,15 @@ def program(): common = (DRIVER / "hw/t604x.rs").read_text().replace("use crate::g17::fw::initdata::RegisterWindow;", "use super::RegisterWindow;") code.append("mod t604x {\n%s\n}\n" % common) code.append("pub mod v25g83 {\n%s\n}\n" % v25g83) - for name, source, items in (("t6041", t6041, T6041_ITEMS),): + j616c = (DRIVER / "hw/t6041_25g83.rs").read_text() + for name, source, items in (("t6041", t6041, T6041_ITEMS), ("t6041_25g83", j616c, J616C_ITEMS)): body = "\n".join(item(source, i) for i in items) body = re.sub(r"(?m)^const ", "pub(crate) const ", body) code.append("pub mod %s {\nuse super::*;\nuse super::t604x::{instances, window};\n%s\n}\n" % (name, body)) code.append(opps("T6041_OPPS", T6041_OPP_HZ, T6041_OPP_MV)) code.append(aux("T6041_AUX", T6041_AUX)) + code.append(opps("J616C_OPPS", J616C_OPP_HZ, J616C_OPP_MV)) + code.append(aux("J616C_AUX", J616C_AUX)) code.append(MAIN) return "\n".join(code)