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), +]; 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)