From 44230fbda0585b47bc74dbc0dbd0bcea7585fe0e Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 09:26:22 -0500 Subject: [PATCH 1/3] soc: apple: display-gate: adopt the M4 Max internal display handoff aura hands the M4 Max's internal display over the way it hands over the M4 Pro's: the DCP, the display subsystem and the DCP's piodma carry apple,t6041-handoff, the DCP names the 26.6.2 firmware, and the streams sit on the same DARTs. The M4 Max gate only enabled the external chains, so a handoff there was ignored and the panel stayed on simpledrm. Run the M4 Pro's internal display check on the M4 Max first. With the handoff and a running DCP it enables the DARTs, the mailbox, the display subsystem, the DCP and the panel; without it nothing changes. The external chains still need an enabled display subsystem, now the one this handoff enables, and still only add their DARTs to it. Link: https://github.com/aurora-silicon/linux/issues/1332 Signed-off-by: Diljit Singh --- drivers/soc/apple/display-gate-test.c | 5 ++-- drivers/soc/apple/display-gate.c | 39 +++++++++++++++++--------- drivers/soc/apple/t6041-gate-test.dtso | 5 ++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/drivers/soc/apple/display-gate-test.c b/drivers/soc/apple/display-gate-test.c index c767a064e9280..a732f844683b8 100644 --- a/drivers/soc/apple/display-gate-test.c +++ b/drivers/soc/apple/display-gate-test.c @@ -20,8 +20,9 @@ * leaves the internal panel alone. * - T6041 on the J616c (t6041-gate-test.dtso): no PMP; each external chain * is enabled once its processor has firmware memory, one without it or - * already enabled stays as it is, and the display subsystem is never - * written. + * already enabled stays as it is, and the chains never write the display + * subsystem's status. The tree has no dcp alias, so the internal display + * handoff finds nothing to adopt. * Included at the end of display-gate.c. * * The test trees are applied once and stay: the gate gives some properties diff --git a/drivers/soc/apple/display-gate.c b/drivers/soc/apple/display-gate.c index bdb4b6dac2031..73c56cc29ca21 100644 --- a/drivers/soc/apple/display-gate.c +++ b/drivers/soc/apple/display-gate.c @@ -78,11 +78,12 @@ * driver of its own, t6041-display-gate.c, by Ryan Murray and František * Balázsy. * - * On the M4 Max (J616c, apple,t6041) the PMP never runs: iBoot leaves the - * external chains powered and the boot loader fills each external display - * processor's firmware memory. The gate enables a chain once its processor has - * that memory, before any device exists (see gate_t6041_max_run()), and never - * touches the internal display. + * On the M4 Max (J614c and J616c, apple,t6041) the internal display is + * handed over and enabled as on the M4 Pro. The PMP never runs there: iBoot + * leaves the external chains powered and the boot loader fills each external + * display processor's firmware memory. The gate enables a chain once its + * processor has that memory and the display subsystem is enabled, before any + * device exists (see gate_t6041_max_run()). */ #define pr_fmt(fmt) fmt @@ -336,11 +337,17 @@ static const struct gate_soc gate_t6041 __initconst = { static void __init gate_t6041_max_run(void); -/* T6041 (M4 Max): no PMP; the external chains follow the boot loader's firmware memory. */ +/* + * T6041 (M4 Max): the M4 Pro's internal display handoff; no PMP, so the + * external chains follow the boot loader's firmware memory. + */ static const struct gate_soc gate_t6041_max __initconst = { .machine = "apple,t6041", .prefix = "apple-t6041-display: ", .name = "T6041", + .dcp_compat = "apple,t6041-dcp", + .display_compat = "apple,display-subsystem", + .marker = "apple,t6041-handoff", .run = gate_t6041_max_run, }; @@ -3030,12 +3037,13 @@ static int __init t6041_max_apply(unsigned int i, struct device_node *display) } /* - * Each chain is enabled in one changeset, once its processor has firmware - * memory. A chain that has none, or is not all there and disabled (a boot - * loader that enabled it itself), stays as it is, and the other chain does not - * depend on it. The display subsystem only gains the DART of an enabled chain: - * its status is never written, so the internal display stays as the boot - * loader left it whatever happens here. + * The internal display first, as on the M4 Pro: an accepted handoff enables + * the display subsystem with the DCP. Then each external chain is enabled in + * one changeset, once its processor has firmware memory. A chain that has + * none, or is not all there and disabled (a boot loader that enabled it + * itself), stays as it is, and the other chain does not depend on it. The + * chains need the display subsystem enabled, by the handoff or by the boot + * loader, and only add their DARTs to it: its status is not written here. */ static void __init gate_t6041_max_run(void) { @@ -3046,10 +3054,15 @@ static void __init gate_t6041_max_run(void) if (IS_ENABLED(CONFIG_VIDEO) && video_firmware_drivers_only()) return; + gate_t6041_internal(); staged_prefix = gate_soc->prefix; display = of_find_node_by_path("/display-subsystem"); - if (!display || !of_device_is_available(display)) { + if (!display) { staged_err("no display subsystem, nothing enabled\n"); + return; + } + if (!of_device_is_available(display)) { + staged_info("display subsystem not enabled, external displays left off\n"); of_node_put(display); return; } diff --git a/drivers/soc/apple/t6041-gate-test.dtso b/drivers/soc/apple/t6041-gate-test.dtso index 96c0eaa541ced..d12929a73c0c9 100644 --- a/drivers/soc/apple/t6041-gate-test.dtso +++ b/drivers/soc/apple/t6041-gate-test.dtso @@ -7,8 +7,9 @@ * heap regions of both processors are the boot loader's disabled * placeholders, the text region the one it adds. * - * The display subsystem is the only node that is enabled. It sits at the - * root, as on the J616c, so a tree that has one does not get this applied. + * The display subsystem is the only node that is enabled, as the internal + * display handoff leaves it. It sits at the root, as on the J616c, so a tree + * that has one does not get this applied. */ /dts-v1/; From ffcc24f462200bccd83b0b3ff046ffc6a00c4caa Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 09:26:24 -0500 Subject: [PATCH 2/3] arm64: dts: apple: t6041-j614c-j616c: leave the internal display to the handoff The J614c/J616c tree enabled /display-subsystem itself. aura refuses to hand over a display whose nodes are already enabled ("display is not status "disabled"", linux#1332), so the panel stayed on simpledrm. Keep the display subsystem disabled, and the panel with it, as the J616s tree does. The display gate enables them with the DCP once the boot loader has handed the display over. Without a handoff the display subsystem now stays disabled, and the external chains, which need it, stay off too. aura publishes the external processors' firmware memory only together with the panel handoff, so nothing that worked with aura before is lost. A loader that fills that memory without handing the panel over loses the external displays until it hands the panel over as well. Link: https://github.com/aurora-silicon/linux/issues/1332 Signed-off-by: Diljit Singh --- arch/arm64/boot/dts/apple/t6041-j614c-j616c.dtsi | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t6041-j614c-j616c.dtsi b/arch/arm64/boot/dts/apple/t6041-j614c-j616c.dtsi index de5eed04aed63..98337a09a596f 100644 --- a/arch/arm64/boot/dts/apple/t6041-j614c-j616c.dtsi +++ b/arch/arm64/boot/dts/apple/t6041-j614c-j616c.dtsi @@ -448,9 +448,13 @@ status = "okay"; }; +/* + * As on the J616s, the display gate enables the display subsystem, the DCP, + * its DARTs, mailbox and panel once the boot loader has handed the internal + * display over (apple,t6041-handoff); otherwise the boot framebuffer stays. + */ &display { phandle = <&display>; - status = "okay"; }; &dcp { @@ -459,6 +463,7 @@ panel: panel { apple,max-brightness = <500>; + status = "disabled"; }; }; From 579bcc38d686758d9f18470bd404a27f82786df1 Mon Sep 17 00:00:00 2001 From: Diljit Singh Date: Mon, 9 Nov 2026 09:45:40 -0500 Subject: [PATCH 3/3] drm/asahi: run the 25G83 build on the 40-core M4 Max The macOS 26.6.2 boot firmware (iBoot mBoot-18000.161.10) loads the 25G83 GPU firmware on the M4 Max as well. integration/aurora admits only 25F84 there, so a 40-core J614c booting through a 26.6.2 stub has no GPU (linux#1332). A firmware build can now carry the SoC configuration it runs with. Once the build is identified, that configuration replaces the probed one. This is the mechanism linux#1050 proposes, so its 32-core profile can build on it. T6041 gains a 25G83 build. Its configuration is the 40-core 25F84 profile with two changes: - the 25G83 variant, with the four-cluster tiler values (TA mode 1) that 25F84 uses on this machine and #1050 uses for 25G83; - the 25G83 register map. It has the same 25 windows (slot 3 holds the 40-core part's eight memory controllers), laid out the way 25G83 lays them out on the M4 Pro: from +0x200000, each window followed by a 16K guard page, with no reserved slots. This rule reproduces both known 25G83 maps, the M4 Pro's (four controllers) and #1050's 32-core M4 Max map (six). A compile-time check holds the M4 Pro's map and the new one to the rule, and the new one to 25F84's windows. The rest comes from the 25F84 profile: 40 cores in four clusters, the operand-state and deflake layouts, the 160-block compute pool, the hardware data and power words, and region C. The core count check stays, so a 32-core part is refused with this configuration. The build admits one loaded image: the only T6041 25G83 image measured so far (sha256 aa113ed6...effc5, measured on #1050's 32-core J616C). iBoot fills part of the loaded TEXT for each Mac (three J616s loaded three different T6040 images), so a 40-core Mac most likely loads a different one. If so, its "unsupported GPU firmware build" line gives the sha256 to add. KUnit tests cover the identities of both T6041 builds, the 25G83 configuration, and the render policy of both builds. The host model of the build selector now returns the build's configuration, and the T6041 initdata host test also builds the 25G83 objects. Not run on hardware. Link: https://github.com/aurora-silicon/linux/issues/1332 Link: https://github.com/aurora-silicon/linux/pull/1050 Signed-off-by: Diljit Singh --- drivers/gpu/drm/asahi/fwid.rs | 32 ++++ drivers/gpu/drm/asahi/g17/fw/render.rs | 38 ++++- drivers/gpu/drm/asahi/g17/fw/variant.rs | 3 + drivers/gpu/drm/asahi/g17/mod.rs | 15 +- drivers/gpu/drm/asahi/hw/t6040.rs | 1 + drivers/gpu/drm/asahi/hw/t6041.rs | 158 +++++++++++++++++--- drivers/gpu/drm/asahi/hw/t604x.rs | 18 +++ tools/asahi/g17-firmware-selection-tests.py | 24 ++- tools/asahi/g17-t6041-initdata-tests.py | 18 ++- 9 files changed, 264 insertions(+), 43 deletions(-) diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index 34c65a0d8a097..514f09539dc73 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -247,6 +247,38 @@ mod tests { assert!(measured(build.images, 0x138000, [0x59839, 0x5991c], [0; 32]).is_none()); } + #[test] + fn t6041_builds_admit_only_their_own_images() { + let crate::g17::fw::variant::Firmware::Identified(builds) = + crate::hw::t6041::CONFIG.firmware + else { + panic!("T6041 must identify its loaded firmware"); + }; + assert_eq!(builds.len(), 2); + for (i, build) in builds.iter().enumerate() { + let other = &builds[1 - i]; + for image in build.images { + let (entropy, digest) = (image.entropy, image.text_sha256); + let found = measured(build.images, image.data_size, entropy, digest); + assert!(core::ptr::eq(found.unwrap(), image)); + assert!(measured(other.images, image.data_size, entropy, digest).is_none()); + assert!(measured(build.images, 0x13c000, entropy, digest).is_none()); + } + } + // Only the 25G83 build runs its own configuration: the 25G83 register map, 40 cores. + assert!(builds[0].config.is_none()); + let g83 = builds[1].config.unwrap(); + let map = &crate::hw::t6041::REGISTER_WINDOWS_25G83; + assert_eq!(g83.windows.len(), map.len()); + for (window, expected) in g83.windows.iter().zip(map.iter()) { + assert_eq!((window.slot, window.va), (expected.slot, expected.va)); + } + assert!(g83.reserved_slots.is_empty()); + let cores = crate::hw::t6041::CONFIG.core_layout.cores; + assert_eq!(g83.core_layout.cores, cores); + assert_eq!(cores, Some((4, 40))); + } + #[cfg(CONFIG_DRM_ASAHI_G16)] #[test] fn j713_accepts_both_loaded_images() { diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index 8fcc25500ab2d..f63821c904fd2 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -1629,14 +1629,38 @@ mod ta_mode_tests { } } + /// The variants of the builds T6041 admits: 25F84, then 25G83. + 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] + } + + /// The TA mode of every admitted T6041 build. + fn t6041_ta_mode_1() -> bool { + let [f84, g83] = t6041_variants(); + assert_eq!(f84.ta_mode_1, g83.ta_mode_1); + f84.ta_mode_1 + } + #[test] - fn policy_is_local_to_the_admitted_t6041_build() { - let max = selected(&crate::hw::t6041::CONFIG.firmware); + fn policy_is_local_to_the_admitted_t6041_builds() { + let [f84, g83] = t6041_variants(); let pro = selected(&crate::hw::t6040::CONFIG.firmware); - assert_eq!(max.compute, crate::g17::fw::compute::ComputeProgram::V25F84); - assert!(max.split_firmware_area); - assert_eq!(max.programs, RegisterPrograms::V25G83); - assert!(max.ta_mode_1); + assert_eq!(f84.compute, crate::g17::fw::compute::ComputeProgram::V25F84); + assert!(f84.split_firmware_area); + // 25G83 keeps the M4 Pro's queue and compute rules on the M4 Max. + assert_eq!(g83.compute, pro.compute); + assert_eq!(g83.split_firmware_area, pro.split_firmware_area); + assert_eq!(g83.opening_record, pro.opening_record); + assert_eq!(g83.qid_floor, pro.qid_floor); + assert_eq!(g83.registration, pro.registration); + for max in [f84, g83] { + assert_eq!(max.programs, RegisterPrograms::V25G83); + assert!(max.ta_mode_1); + } assert!(!pro.ta_mode_1); assert!(!V25F84.ta_mode_1); assert!(!V25G83.ta_mode_1); @@ -1704,7 +1728,7 @@ mod ta_mode_tests { for scs in [false, true] { let mut args = args(&pass, &resources, scs); let (before, count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); - args.ta_mode_1 = selected(&crate::hw::t6041::CONFIG.firmware).ta_mode_1; + args.ta_mode_1 = t6041_ta_mode_1(); let (after, new_count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); assert_eq!(count, 83 + usize::from(scs)); assert_eq!(new_count, count); diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index c5287e66a641d..222473a8b8c36 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -89,6 +89,9 @@ pub(crate) struct Build { pub(crate) images: &'static [crate::fwid::FirmwareBuild], /// Layouts and rules of the build. pub(crate) variant: &'static Variant, + /// The SoC configuration this build runs with, which replaces the probed one once the + /// build is identified; `None` keeps the probed configuration. + pub(crate) config: Option<&'static crate::g17::Config>, } /// How a SoC's firmware build is determined. diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index ffb0c20d0267f..df5a31f621a3c 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1173,7 +1173,9 @@ impl Gpu { } else { None }; - let variant = Self::firmware_variant(dev, &cfg.firmware)?; + let (variant, build_cfg) = Self::firmware_variant(dev, &cfg.firmware)?; + // 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)?; @@ -1324,14 +1326,15 @@ impl Gpu { Ok(gpu) } - /// Returns the variant of the loaded firmware build. An identified SoC refuses a build it - /// does not know, before anything is written to the 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. fn firmware_variant( dev: &AsahiDrmDevice, firmware: &fw::variant::Firmware, - ) -> Result<&'static fw::Variant> { + ) -> Result<(&'static fw::Variant, Option<&'static Config>)> { let builds = match firmware { - fw::variant::Firmware::Fixed(variant) => return Ok(variant), + fw::variant::Firmware::Fixed(variant) => return Ok((variant, None)), fw::variant::Firmware::Identified(builds) => builds, }; let version = Self::os_firmware_version(dev); @@ -1346,7 +1349,7 @@ impl Gpu { 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); + return Ok((build.variant, build.config)); } } dev_err!( diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index d531e20c7e9f9..5f6b9944a811e 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -100,6 +100,7 @@ pub(crate) const CONFIG: Config = Config { }, ], variant: &fw::variant::V25G83, + config: None, }]), // Measured on the 20-core M4 Pro; the alternative below remains experimental. core_layout: CoreLayout { diff --git a/drivers/gpu/drm/asahi/hw/t6041.rs b/drivers/gpu/drm/asahi/hw/t6041.rs index 8be466f7842ab..956aa0cc7728b 100644 --- a/drivers/gpu/drm/asahi/hw/t6041.rs +++ b/drivers/gpu/drm/asahi/hw/t6041.rs @@ -9,7 +9,9 @@ use crate::hw::{GpuGen, GpuVariant}; use super::t604x::{instances, window}; use crate::mmu::UatQuirks; -pub(crate) const CONFIG: Config = Config { +/// 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 { chip_id: CHIP_ID, identity: Identity { family: 0xa, @@ -46,26 +48,7 @@ pub(crate) const CONFIG: Config = Config { fabric_bridge: false, recovery_slot_keys: None, gpu_generation: GpuGen::G16, - firmware: fw::variant::Firmware::Identified(&[fw::variant::Build { - os_version: [26, 5, 2], - name: "macOS 26.5.2 (25F84, iBoot mBoot-18000.121.3)", - images: &[crate::fwid::FirmwareBuild { - name: "Apple GFX 25F84 for T6041 (normalized loaded image)", - text_size: 0x60000, - data_size: 0x138000, - entropy: [0x5983a, 0x5991d], - text_sha256: [ - 0x6d, 0xa4, 0xd4, 0x42, 0x35, 0x35, 0x14, 0x44, 0xf6, 0xec, 0x00, 0x33, 0xf1, 0x9c, - 0xbf, 0x98, 0xd3, 0x43, 0x91, 0x28, 0x83, 0xe4, 0xe2, 0x94, 0xc1, 0x8b, 0xb8, 0xb8, - 0xa3, 0xc1, 0x83, 0xe8, - ], - }], - // macOS writes these tiler segment values on this machine, not the two-cluster ones. - variant: &fw::variant::Variant { - ta_mode_1: true, - ..fw::variant::V25F84 - }, - }]), + firmware: fw::variant::Firmware::Identified(&[]), core_layout: CoreLayout { cores: Some((4, 40)), alternate_core_masks: None, @@ -75,6 +58,76 @@ pub(crate) const CONFIG: Config = Config { }, }; +/// The 40-core M4 Max. 25F84 runs this configuration; 25G83 runs [`CONFIG_25G83`]. +pub(crate) const CONFIG: Config = Config { + firmware: fw::variant::Firmware::Identified(&BUILDS), + ..BASE +}; + +const BUILDS: [fw::variant::Build; 2] = [ + BUILD_25F84, + fw::variant::Build { + config: Some(&CONFIG_25G83), + ..BUILD_25G83 + }, +]; + +const BUILD_25F84: fw::variant::Build = fw::variant::Build { + os_version: [26, 5, 2], + name: "macOS 26.5.2 (25F84, iBoot mBoot-18000.121.3)", + images: &[crate::fwid::FirmwareBuild { + name: "Apple GFX 25F84 for T6041 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x5983a, 0x5991d], + text_sha256: [ + 0x6d, 0xa4, 0xd4, 0x42, 0x35, 0x35, 0x14, 0x44, 0xf6, 0xec, 0x00, 0x33, 0xf1, 0x9c, + 0xbf, 0x98, 0xd3, 0x43, 0x91, 0x28, 0x83, 0xe4, 0xe2, 0x94, 0xc1, 0x8b, 0xb8, 0xb8, + 0xa3, 0xc1, 0x83, 0xe8, + ], + }], + // macOS writes these tiler segment values on this machine, not the two-cluster ones. + variant: &fw::variant::Variant { + ta_mode_1: true, + ..fw::variant::V25F84 + }, + config: None, +}; + +/// 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 { + os_version: [26, 6, 2], + name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)", + images: &[crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6041 (normalized loaded image, 32-core J616C)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0xaa, 0x11, 0x3e, 0xd6, 0x20, 0x79, 0xbc, 0xfd, 0xee, 0xf0, 0xf8, 0x6c, 0x2a, 0x8d, + 0x7e, 0xae, 0x9f, 0x90, 0x7b, 0x03, 0xbe, 0x28, 0xcc, 0xf2, 0x98, 0xc9, 0x43, 0x95, + 0x1d, 0x0e, 0xff, 0xc5, + ], + }], + // Four clusters: the tiler segment values of 25F84 on this machine. + variant: &fw::variant::Variant { + ta_mode_1: true, + ..fw::variant::V25G83 + }, + config: None, +}; + +/// The 40-core M4 Max on 25G83: the 25F84 configuration with the 25G83 register map. +const CONFIG_25G83: Config = Config { + windows: ®ISTER_WINDOWS_25G83, + reserved_slots: &[], + firmware: fw::variant::Firmware::Identified(&[BUILD_25G83]), + ..BASE +}; + pub(crate) const CHIP_ID: u32 = 0x6041; pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ @@ -113,6 +166,69 @@ pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(43, 0x2_88e6_0000, 0x2f_8000, 0x58, 0xe6_0000, 2), ]; +/// The register map of the 25G83 build: the windows of [`REGISTER_WINDOWS`] (slot 3 holds the +/// eight memory controllers) laid out as 25G83 lays them out on the M4 Pro +/// ([`super::t6040::REGISTER_WINDOWS`]), from `+0x20_0000`, each window followed by a 16K +/// guard page, with no reserved slots. The windows after slot 3 lie 0x60000 further than the M4 +/// Pro's, for its four more controllers. +pub(crate) const REGISTER_WINDOWS_25G83: [RegisterWindow; 25] = [ + window(0, 0x5_0241_4000, 0x20_0000, 0x4000, 0, 2), + instances( + window(3, 0x2_201c_4000, 0x20_8000, 0x1_8000, 0, 2), + 8, + 0x200_0000, + ), + window(4, 0x5_0244_c000, 0x2c_c000, 0x4000, 0, 0), + window(9, 0x5_003d_0000, 0x2d_4000, 0x4000, 0, 2), + window(10, 0x5_003c_0000, 0x2d_c000, 0x4000, 0, 0), + window(11, 0x5_003d_8000, 0x2e_4000, 0x4000, 0, 2), + window(12, 0x4_8565_c000, 0x2e_c000, 0x4000, 0, 2), + window(14, 0x5_0040_0000, 0x2f_4000, 0x1_0000, 0, 0), + window(15, 0x5_0cdc_0000, 0x30_8000, 0x4000, 0, 0), + window(17, 0x2_8800_0000, 0x31_0000, 0x2_1400, 0, 2), + instances( + window(20, 0x2_8b00_0000, 0x33_8000, 0x4_0000, 0, 2), + 4, + 0x20_0000, + ), + window(22, 0x2_8900_0000, 0x43_c000, 0x8000, 0, 2), + window(26, 0x2_88d0_4000, 0x44_8000, 0x8000, 0xd0_4000, 2), + window(28, 0x2_88d5_8000, 0x45_4000, 0x8000, 0xd5_8000, 2), + window(29, 0x2_88d1_0000, 0x46_0000, 0x4000, 0xd1_0000, 2), + window(31, 0x2_88d4_0000, 0x46_8000, 0x4000, 0xd4_0000, 2), + window(32, 0x2_88d6_0000, 0x47_0000, 0x2_0000, 0xd6_0000, 2), + window(33, 0x2_88d4_4000, 0x49_4000, 0x4000, 0xd4_4000, 2), + window(34, 0x2_88d4_c000, 0x49_c000, 0x200, 0xd4_c000, 2), + window(35, 0x2_88e0_0000, 0x4a_4000, 0x4000, 0xe0_0000, 2), + window(37, 0x2_88e4_0000, 0x4a_c000, 0x4000, 0xe4_0000, 2), + window(39, 0x2_88e0_8000, 0x4b_4000, 0x8000, 0, 2), + window(40, 0x2_88e1_c000, 0x4c_0000, 0x4000, 0xe1_c000, 2), + window(41, 0x2_88e1_f800, 0x4c_b800, 0x4000, 0, 2), + window(43, 0x2_88e6_0000, 0x4d_4000, 0x58, 0xe6_0000, 2), +]; + +// The 25G83 map holds the 25F84 windows, laid out as 25G83 lays out the M4 Pro's. +const _: () = { + assert!(super::t604x::laid_out_25g83( + &super::t6040::REGISTER_WINDOWS + )); + assert!(super::t604x::laid_out_25g83(®ISTER_WINDOWS_25G83)); + let mut index = 0; + while index < REGISTER_WINDOWS.len() { + let (f84, g83) = (®ISTER_WINDOWS[index], ®ISTER_WINDOWS_25G83[index]); + assert!( + f84.slot == g83.slot + && f84.phys == g83.phys + && f84.size == g83.size + && f84.unk_18 == g83.unk_18 + && f84.unk_20 == g83.unk_20 + && f84.instances == g83.instances + && f84.phys_stride == g83.phys_stride + ); + index += 1; + } +}; + const RESERVED_SLOTS: [usize; 14] = [2, 5, 6, 7, 8, 27, 30, 38, 42, 46, 47, 48, 49, 52]; pub(crate) const PERF: PerfConfig = PerfConfig { diff --git a/drivers/gpu/drm/asahi/hw/t604x.rs b/drivers/gpu/drm/asahi/hw/t604x.rs index 961cef76cf4aa..6021419eeb1dd 100644 --- a/drivers/gpu/drm/asahi/hw/t604x.rs +++ b/drivers/gpu/drm/asahi/hw/t604x.rs @@ -31,3 +31,21 @@ pub(super) const fn instances(window: RegisterWindow, instances: u32, phys_strid ..window } } + +/// Whether `windows` are laid out as the 25G83 build lays out its register map: in order from +/// `+0x20_0000`, the pages of each window's instances followed by a 16K guard page. +pub(super) const fn laid_out_25g83(windows: &[RegisterWindow]) -> bool { + let mut next = MMIO_APERTURE + 0x20_0000; + let mut index = 0; + while index < windows.len() { + let window = &windows[index]; + let page_offset = window.phys & 0x3fff; + if window.va != next + page_offset { + return false; + } + let pages = (page_offset + window.size as u64 + 0x3fff) & !0x3fff; + next += pages * window.instances as u64 + 0x4000; + index += 1; + } + true +} diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py index 771c9c19c6e1e..2a3cae91fdb08 100644 --- a/tools/asahi/g17-firmware-selection-tests.py +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -34,6 +34,7 @@ def method(source, signature): image: u32, checked: std::cell::RefCell>, } +struct Config(u32); mod fw { pub struct Variant(pub u32); pub mod variant { @@ -43,6 +44,7 @@ def method(source, signature): pub name: &'static str, pub image: u32, pub variant: &'static Variant, + pub config: Option<&'static crate::Config>, } pub enum Firmware { Fixed(&'static Variant), Identified(&'static [Build]) } } @@ -60,9 +62,11 @@ def method(source, signature): } static FIRST: fw::Variant = fw::Variant(1); static SECOND: fw::Variant = fw::Variant(2); +static OWN: Config = Config(7); static BUILDS: [fw::variant::Build; 2] = [ - fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST }, - fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND }, + 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) }, ]; fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } @@ -71,18 +75,26 @@ 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); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).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, 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).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 dev = device(Some([26,6,2]), 9); assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).is_err()); assert_eq!(*dev.checked.borrow(), [1,2]); let dev = device(None, 9); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap().0, 2); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap(); + assert_eq!(variant.0, 2); + assert!(config.is_none()); assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); - println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged"); + println!("PASS G17 selection: exact image required, OS hint optional, build configuration \ + follows the image, fixed Neo unchanged"); } ''' diff --git a/tools/asahi/g17-t6041-initdata-tests.py b/tools/asahi/g17-t6041-initdata-tests.py index 05ddc6b145d9d..a48174df48547 100644 --- a/tools/asahi/g17-t6041-initdata-tests.py +++ b/tools/asahi/g17-t6041-initdata-tests.py @@ -102,6 +102,7 @@ def item(source, start): ] T6041_ITEMS = SOC_ITEMS + [ + "pub(crate) const REGISTER_WINDOWS_25G83", "const RESERVED_SLOTS", "const MAX_POWER_MW", "const CHIP: ", @@ -190,9 +191,20 @@ def aux(name, rows): &t6041::RESERVED_SLOTS, &t6041::PERF, &T6041_OPPS, &T6041_AUX, &t6041::CHIP, &t6041::REGION_C, 40); println!("T6041 initialization objects built"); - std::fs::write(format!("{}/t6041-hwdata.bin", out), hw).unwrap(); - std::fs::write(format!("{}/t6041-power-config.bin", out), power).unwrap(); - std::fs::write(format!("{}/t6041-region-c.bin", out), region).unwrap(); + std::fs::write(format!("{}/t6041-hwdata.bin", out), &hw).unwrap(); + std::fs::write(format!("{}/t6041-power-config.bin", out), &power).unwrap(); + std::fs::write(format!("{}/t6041-region-c.bin", out), ®ion).unwrap(); + // 25G83: the same configuration with its own register map. + let (hw_g83, power_g83, region_g83) = build(t6041::CHIP_ID, &t6041::REGISTER_WINDOWS_25G83, + &[], &t6041::PERF, &T6041_OPPS, &T6041_AUX, &t6041::CHIP, &t6041::REGION_C, 40); + println!("T6041 25G83 initialization objects built"); + assert_eq!(power_g83, power, "the register map changes only the hardware data"); + assert_eq!(region_g83, region, "the register map changes only the hardware data"); + let map = 0x640..0x640 + REGISTER_MAP_SLOTS * core::mem::size_of::(); + let outside = |a: &[u8], b: &[u8]| a.iter().zip(b).enumerate() + .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(); } """