From 2a7983c3462d8b2205c54c11a986c3b1af0499e8 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:19:29 -0500 Subject: [PATCH 01/11] drm/asahi: g17: m5: refuse firmware whose InitData layouts the graph cannot build From macOS 27.2 on, the M5 GPU firmware expects a different InitData graph than the 26A428 (27.0) one the M5 path builds: - the root version word gains bit 46 (0x0c8e_0339_8728_04c0 -> 0x0c8e_4339_8728_04c0), in 27.2 and 27.3 alike; - the power policy grows from 0x126b0 to 0x12728 bytes and the status config from 0x780 to 0x790, in 27.2 and 27.3 alike; - 27.3 beta 1 alone inserts a HW data word at 0x2720. These layouts are not yet confirmed from captured InitData of a running macOS; the later commits of this series say which parts a running firmware has accepted. Name the layouts a build needs (Variant::m5_layouts) and give the 27.2 firmware its own variant, V26B123_M5: the 26A428 M5 protocol with the 26B123 layouts. Until the M5 graph builds them, Gpu::new() refuses such a build right after identifying it, before anything is written to the GPU, instead of handing it a graph it would reject ("Host and FW Interface is mismatched") or misread. Every M5 build in the tree today is 26A428 and names M5Layouts::V26A428, so nothing changes for the M5 Max and M5 Ultra. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/variant.rs | 22 ++++++++++++++++++++++ drivers/gpu/drm/asahi/g17/mod.rs | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 8712322e976e0..6bbdbea41907e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -36,6 +36,17 @@ pub(crate) enum InitGraph { V25G83, } +/// The InitData layouts of an M5 firmware build ([`super::m5::initdata`]). The 27.2 and later +/// layouts are not yet confirmed from captured InitData. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum M5Layouts { + /// The 26A428 graph: version word 0x0c8e_0339_8728_04c0 and its allocation sizes. + V26A428, + /// The 27.2 (26B123) graph: version word 0x0c8e_4339_8728_04c0, a larger power policy + /// (0x12728) and status config (0x790), the 26A428 HW data. + V26B123, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum QueueRegistration { Firmware, @@ -87,6 +98,8 @@ pub(crate) struct Variant { /// work. The bare-metal M5 firmware powers the cores for work by its own policy: under a /// hold, the M5 Ultra's core-power witness reads unpowered until the hold is released. pub(crate) registration_power_hold: bool, + /// The InitData layouts the M5 graph must build for this firmware; `None` off M5. + pub(crate) m5_layouts: Option, } /// A firmware build a SoC supports, identified by its loaded TEXT. @@ -137,6 +150,7 @@ pub(crate) const V26A428: Variant = Variant { split_firmware_area: false, m5_commands: false, registration_power_hold: true, + m5_layouts: None, }; /// The M4 Pro firmware (25G83, `mBoot-18000.161.10`). @@ -171,6 +185,7 @@ pub(crate) const V25G83: Variant = Variant { split_firmware_area: false, m5_commands: false, registration_power_hold: true, + m5_layouts: None, }; /// The 26A428 firmware of the M5 GPUs, which runs the M5 protocol (the M5 graph, queues and @@ -179,9 +194,16 @@ pub(crate) const V26A428_M5: Variant = Variant { m5_commands: true, render_ray_query: false, registration_power_hold: false, + m5_layouts: Some(M5Layouts::V26A428), ..V26A428 }; +/// The 27.2 M5 firmware (26B123): the 26A428 M5 protocol with the 26B123 InitData layouts. +pub(crate) const V26B123_M5: Variant = Variant { + m5_layouts: Some(M5Layouts::V26B123), + ..V26A428_M5 +}; + pub(crate) const V25F84: Variant = Variant { qid_floor: 0, registration: QueueRegistration::Mailbox, diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 4e68d0bd74a47..61a2cb248aa8b 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1488,6 +1488,16 @@ impl Gpu { } let m5 = cfg.profile.is_m5(); if m5 { + // The M5 graph builds only the 26A428 InitData layouts. A firmware that checks + // another layout is refused here, before anything is written to the GPU. + if variant.m5_layouts != Some(fw::variant::M5Layouts::V26A428) { + dev_err!( + dev.as_ref(), + "No M5 InitData layouts for this GPU firmware build ({:?})\n", + variant.m5_layouts + ); + return Err(ENODEV); + } let board = cfg.m5.ok_or(EINVAL)?; // The M5 layouts are sized for every core slot of the board, fused-off cores // included, so the GPU is matched on clusters and slots, not on enabled cores. From 2e0cccd3cd83924d7838e0bfeaaf0f217c5706aa Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:21:21 -0500 Subject: [PATCH 02/11] drm/asahi: t6050: identify the M5 Pro's 27.2 and 27.3 G17S firmware builds The M5 Pro admits only the 27.0 (26A428) G17S build. Pin the 27.2 (26B123) and 27.3 beta 1 (26C5035r) G17S builds of both roles as well, measured the way fwid defines a build (the SHA-256 of the primary's then the secondary's TEXT with the entropy records, the two header load fields and the two boot-argument sections of each role zeroed): 27.3 beta 1 (26C5035r) b5537c8f06bd...66763600 27.2 (26B123) 2b4613c62a8a...913f0fd9 The images are the G17S (FW Build variant a010) primary and secondary GFX firmware of the public Mac17,8 IPSW for 27.2 and of the 27.3 beta 1 restore bundle (im4p SHA-256 ae7f35b1... and da8ff3fb...). The offsets come from each image's Mach-O load commands: text at 0x50000 + 0x54000, DATA 0x184000 as on the M5 Max, the _rtk_tunables and _rtk_patchbay sections per role. 27.2 needs an entry of its own although its interface matches 27.3's: its primary entropy record sits at 0x49ea4, 27.3's at 0x49eac. Each image header still carries its load fields at 0x224/0x23c (zero in the file, the patchbay's offset and size at 0x22c/0x230, as in 26A428). The 27.0 entry is the one the base already pins: this digest procedure gives the same digest for it, and reproduces the two pinned 26A428 digests of the M5 Max (G17C) and M5 Ultra (G17D). 27.0 runs the 26A428 graph. 27.2 and, for now, 27.3 name V26B123_M5, whose layouts the M5 graph does not build yet, so they are identified and then refused before any GPU write; 27.3 gets its own variant once its HW data is built. The fwid KUnit tests cover the new builds. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/fwid.rs | 20 +++++++ drivers/gpu/drm/asahi/hw/t6050.rs | 99 ++++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 21 deletions(-) diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index d646bff94cf1f..a19ac8be41142 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -554,6 +554,26 @@ mod tests { } } + #[test] + fn m5_pro_builds_name_their_layouts() { + use crate::g17::fw::variant::{Firmware, M5Layouts}; + let Firmware::Identified(builds) = crate::hw::t6050::CONFIG.firmware else { + panic!("the M5 Pro must identify its loaded firmware"); + }; + for (i, build) in builds.iter().enumerate() { + // 27.2 (26B123) moved the InitData layouts; 27.3 kept them. + let layouts = if build.os_version >= [27, 2, 0] { + M5Layouts::V26B123 + } else { + M5Layouts::V26A428 + }; + assert_eq!(build.variant.m5_layouts, Some(layouts)); + for other in &builds[i + 1..] { + assert_ne!(build.images[0].text_sha256, other.images[0].text_sha256); + } + } + } + fn t6050_admits_only(builds: &[crate::g17::fw::variant::Build]) { for image in builds.iter().flat_map(|build| build.images) { // Both roles' records, inside TEXT. diff --git a/drivers/gpu/drm/asahi/hw/t6050.rs b/drivers/gpu/drm/asahi/hw/t6050.rs index ae2b48d9184d1..b074dd6f239b9 100644 --- a/drivers/gpu/drm/asahi/hw/t6050.rs +++ b/drivers/gpu/drm/asahi/hw/t6050.rs @@ -42,33 +42,90 @@ pub(crate) const CONFIG: Config = Config { fabric_bridge: false, recovery_slot_keys: None, gpu_generation: GpuGen::G17, - // The firmware in `fw-text` must be this build. iBoot loads the G17S build of both roles - // back to back: the primary's TEXT 0x50000, then the secondary's 0x54000, with the same - // entropy records and boot-filled ranges as the M5 Max. The digest is that of the - // firmware's TEXT with those ranges zeroed, measured on two J714s boots. - firmware: crate::g17::fw::variant::Firmware::Identified(&[crate::g17::fw::variant::Build { - os_version: [27, 0, 0], - name: "27.0 (26A428), G17S", - images: &[ - crate::fwid::FirmwareBuild { - name: "26A428 for T6050 G17S, primary then secondary", + // The firmware in `fw-text` must be one of these builds. iBoot loads the G17S build of both + // roles back to back: the primary's text at 0x50000 of `fw-text`, then the secondary's at + // 0x54000, with the same entropy records and boot-filled ranges as the M5 Max (27.2 and 27.3 + // move the primary's records by a few bytes). Each digest is that of the firmware's text with + // those ranges zeroed, a loaded copy normalizing to it on every boot. Newest first. + firmware: crate::g17::fw::variant::Firmware::Identified(&[ + crate::g17::fw::variant::Build { + os_version: [27, 3, 0], + name: "27.3 (26C5035r), G17S", + images: &[crate::fwid::FirmwareBuild { + name: "26C5035r for T6050 G17S, primary then secondary", text_size: 0xa4000, data_size: 0x184000, - entropy: &[[0x49e60, 0x49f43], [0x9d918, 0x9d9fb]], + entropy: &[[0x49eac, 0x49f8f], [0x9d92c, 0x9da0f]], boot_filled: &[ - (0x224, 4), (0x23c, 8), (0x49720, 0x740), (0x49e60, 0x231), - (0x50224, 4), (0x5023c, 8), (0x9d1d8, 0x740), (0x9d918, 0x231), + (0x224, 4), + (0x23c, 8), + (0x4976c, 0x740), + (0x49eac, 0x231), + (0x50224, 4), + (0x5023c, 8), + (0x9d1ec, 0x740), + (0x9d92c, 0x231), ], text_sha256: [ - 0x85, 0xd0, 0xfd, 0x82, 0x80, 0xf7, 0x24, 0x65, 0x5c, 0xa9, 0xa9, 0x71, 0x7b, - 0xc9, 0x1a, 0x8a, 0x5e, 0xf6, 0x09, 0x28, 0xd7, 0x72, 0x00, 0xf6, 0xd5, 0x3b, - 0xd0, 0x99, 0xb9, 0x47, 0x39, 0xc6, + 0xb5, 0x53, 0x7c, 0x8f, 0x06, 0xbd, 0x19, 0xeb, 0xf0, 0xef, 0xd0, 0xcb, 0xfb, + 0x65, 0x52, 0x33, 0x9a, 0x10, 0xd4, 0xaf, 0xc4, 0xed, 0xa9, 0xd2, 0x1c, 0xbe, + 0xdc, 0x36, 0x66, 0x76, 0x36, 0x00, ], - }, - ], - variant: &crate::g17::fw::variant::V26A428_M5, - config: None, - }]), + }], + variant: &crate::g17::fw::variant::V26B123_M5, + config: None, + }, + crate::g17::fw::variant::Build { + os_version: [27, 2, 0], + name: "27.2 (26B123), G17S", + images: &[crate::fwid::FirmwareBuild { + name: "26B123 for T6050 G17S, primary then secondary", + text_size: 0xa4000, + data_size: 0x184000, + entropy: &[[0x49ea4, 0x49f87], [0x9d92c, 0x9da0f]], + boot_filled: &[ + (0x224, 4), + (0x23c, 8), + (0x49764, 0x740), + (0x49ea4, 0x231), + (0x50224, 4), + (0x5023c, 8), + (0x9d1ec, 0x740), + (0x9d92c, 0x231), + ], + text_sha256: [ + 0x2b, 0x46, 0x13, 0xc6, 0x2a, 0x8a, 0x00, 0x7f, 0x22, 0xa9, 0x41, 0x01, 0x70, + 0x2c, 0x9a, 0xc6, 0xc4, 0xe9, 0x0f, 0x2e, 0x12, 0x3f, 0x3b, 0x36, 0xfb, 0xe6, + 0xff, 0xea, 0x91, 0x3f, 0x0f, 0xd9, + ], + }], + variant: &crate::g17::fw::variant::V26B123_M5, + config: None, + }, + crate::g17::fw::variant::Build { + os_version: [27, 0, 0], + name: "27.0 (26A428), G17S", + images: &[ + crate::fwid::FirmwareBuild { + name: "26A428 for T6050 G17S, primary then secondary", + text_size: 0xa4000, + data_size: 0x184000, + entropy: &[[0x49e60, 0x49f43], [0x9d918, 0x9d9fb]], + boot_filled: &[ + (0x224, 4), (0x23c, 8), (0x49720, 0x740), (0x49e60, 0x231), + (0x50224, 4), (0x5023c, 8), (0x9d1d8, 0x740), (0x9d918, 0x231), + ], + text_sha256: [ + 0x85, 0xd0, 0xfd, 0x82, 0x80, 0xf7, 0x24, 0x65, 0x5c, 0xa9, 0xa9, 0x71, + 0x7b, 0xc9, 0x1a, 0x8a, 0x5e, 0xf6, 0x09, 0x28, 0xd7, 0x72, 0x00, 0xf6, + 0xd5, 0x3b, 0xd0, 0x99, 0xb9, 0x47, 0x39, 0xc6, + ], + }, + ], + variant: &crate::g17::fw::variant::V26A428_M5, + config: None, + }, + ]), // 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. core_layout: CoreLayout { From 415c1fba3e10b7af01d54720260affe61f68dc04 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:36:32 -0500 Subject: [PATCH 03/11] drm/asahi: g17: m5: build the 27.2 (26B123) InitData graph The M5 GPU firmware of macOS 27.2 (26B123) is expected to find a different root version word than 27.0 (26A428): 0x0c8e_4339_8728_04c0 where 27.0 has 0x0c8e_0339_8728_04c0, only bit 46 differs. Its allocation size table differs from 26A428's (ABI_ALLOCATION_SIZES_26A428_M5) in two entries only: index 4, power policy: 0x126b0 -> 0x12728 index 13, status config: 0x780 -> 0x790 The packed layouts derived from it move accordingly: in the shared cluster everything after the policy moves by 0x80 (views 3 and 4, the BRN table and repeated region, the twelve DM rings, both main configs and control rings); in the private cluster everything after the status config moves by 0x40 (both role sets with status A, the secondary control state, secondary extra 1 and the policy state). Both clusters keep their page-rounded sizes (0x34000 and 0x178000), so the fixed windows of the M5 graph still hold them. The HW data of 26B123 is 26A428's. The version word, the sizes and the HW data layout of 26B123 are not yet confirmed from captured InitData. Describe one firmware build's InitData layout as an M5Layout (its size table, the packed clusters derived from it, and how its HW data moves relative to the 26A428 encoding), add M5_LAYOUT_26A428 and M5_LAYOUT_26B123, and a RootProfile J714G17S26B123 that writes the new version word and takes its offsets from the 26B123 layout. build_m5() now takes the firmware build's Variant and builds the profile its M5Layouts names. Gpu::new() keeps refusing every build but 26A428 until the 26B123 power policy and status config are laid out as well (later in this series), so no firmware is handed a partly converted graph. The 26A428 encodings are unchanged: the HW data of every board, the private cluster, the bundle policy, both roots and both main configs encode to the same bytes as before. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 314 +++++++++++++++++--- drivers/gpu/drm/asahi/g17/initdata.rs | 37 ++- drivers/gpu/drm/asahi/g17/initdata/m5.rs | 46 +-- drivers/gpu/drm/asahi/g17/mod.rs | 2 +- 4 files changed, 322 insertions(+), 77 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index ed397b27917cb..a3472923ac959 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -34,22 +34,45 @@ pub(crate) enum RootProfile { J873G18G26A428, J714G17S26A428, + + /// The M5 graph of the macOS 27.2 firmware (26B123): the 26A428 graph with a larger power + /// policy and status config ([`ABI_ALLOCATION_SIZES_26B123_M5`]). + J714G17S26B123, } impl RootProfile { + /// The M5 graph profile of a firmware build's InitData layouts. + pub(crate) const fn for_m5_layouts(layouts: super::super::variant::M5Layouts) -> Self { + use super::super::variant::M5Layouts; + match layouts { + M5Layouts::V26A428 => Self::J714G17S26A428, + M5Layouts::V26B123 => Self::J714G17S26B123, + } + } + + /// The layout of an M5 graph profile, `None` for the others. + pub(crate) const fn m5_layout(self) -> Option<&'static M5Layout> { + match self { + Self::J714G17S26A428 => Some(&M5_LAYOUT_26A428), + Self::J714G17S26B123 => Some(&M5_LAYOUT_26B123), + Self::J700G17P | Self::J813G17G25G76 | Self::J873G18G26A428 => None, + } + } + pub(crate) const fn version_word(self) -> u64 { match self { Self::J700G17P => INITDATA_VERSION_WORD, Self::J813G17G25G76 => 0x0c8a_a322_039e_04c0, Self::J873G18G26A428 => COMMON26_INITDATA_VERSION_WORD, Self::J714G17S26A428 => M5_INITDATA_VERSION_WORD, + Self::J714G17S26B123 => M5_26B123_INITDATA_VERSION_WORD, } } pub(crate) const fn root_size(self, role: InstanceRole) -> usize { match self { Self::J873G18G26A428 => COMMON26_ROOT_SIZE, - Self::J714G17S26A428 => M5_ROOT_SIZE, + Self::J714G17S26A428 | Self::J714G17S26B123 => M5_ROOT_SIZE, Self::J700G17P | Self::J813G17G25G76 => root_size(role), } } @@ -62,8 +85,10 @@ impl RootProfile { (Self::J873G18G26A428, InstanceRole::Secondary) => { COMMON26_CONTROL_OPENING_OPCODE_SECONDARY } - (Self::J714G17S26A428, InstanceRole::Primary) => M5_CONTROL_OPENING_OPCODE_PRIMARY, - (Self::J714G17S26A428, InstanceRole::Secondary) => { + (Self::J714G17S26A428 | Self::J714G17S26B123, InstanceRole::Primary) => { + M5_CONTROL_OPENING_OPCODE_PRIMARY + } + (Self::J714G17S26A428 | Self::J714G17S26B123, InstanceRole::Secondary) => { M5_CONTROL_OPENING_OPCODE_SECONDARY } (_, InstanceRole::Primary) => CONTROL_OPENING_OPCODE_PRIMARY, @@ -73,40 +98,48 @@ impl RootProfile { pub(crate) const fn control_register_opcode(self) -> Option { match self { - Self::J873G18G26A428 | Self::J714G17S26A428 => None, + Self::J873G18G26A428 | Self::J714G17S26A428 | Self::J714G17S26B123 => None, Self::J700G17P | Self::J813G17G25G76 => Some(CONTROL_OPENING_PRIMARY_REGISTER_OPCODE), } } pub(crate) const fn bundle_view_offsets(self) -> [usize; 5] { + if let Some(layout) = self.m5_layout() { + return layout.bundle_view_offsets(); + } match self { Self::J873G18G26A428 => COMMON26_BUNDLE_VIEW_OFFSETS, - Self::J714G17S26A428 => M5_BUNDLE_VIEW_OFFSETS, - Self::J700G17P | Self::J813G17G25G76 => BUNDLE_VIEW_OFFSETS, + _ => BUNDLE_VIEW_OFFSETS, } } pub(crate) const fn bundle_view_extents(self) -> [usize; 5] { + if let Some(layout) = self.m5_layout() { + return layout.bundle_view_extents(); + } match self { Self::J873G18G26A428 => COMMON26_BUNDLE_VIEW_EXTENTS, - Self::J714G17S26A428 => M5_BUNDLE_VIEW_EXTENTS, - Self::J700G17P | Self::J813G17G25G76 => BUNDLE_VIEW_EXTENTS, + _ => BUNDLE_VIEW_EXTENTS, } } pub(crate) const fn repeated_region_bundle_offset(self) -> usize { + if let Some(layout) = self.m5_layout() { + return layout.shared.block_88; + } match self { Self::J873G18G26A428 => COMMON26_SHARED_LAYOUT.block_88, - Self::J714G17S26A428 => M5_SHARED_LAYOUT.block_88, - Self::J700G17P | Self::J813G17G25G76 => MAIN_REPEATED_REGION_BUNDLE_OFFSET, + _ => MAIN_REPEATED_REGION_BUNDLE_OFFSET, } } pub(crate) const fn work_ring_bundle_offsets(self) -> [usize; WORK_CHANNEL_COUNT] { + if let Some(layout) = self.m5_layout() { + return work_ring_offsets(&layout.shared); + } match self { Self::J873G18G26A428 => COMMON26_WORK_RING_BUNDLE_OFFSETS, - Self::J714G17S26A428 => M5_WORK_RING_BUNDLE_OFFSETS, - Self::J700G17P | Self::J813G17G25G76 => WORK_RING_BUNDLE_OFFSETS, + _ => WORK_RING_BUNDLE_OFFSETS, } } } @@ -847,7 +880,7 @@ pub(crate) fn encode_main_config_for_profile( put_u64(out, MAIN_REGION_VIEWS + 0x20, views.alias_high[1]); // J714's firmware takes both primary sentinels too; they are not // exclusive to the older J700 ABI. - if matches!(profile, RootProfile::J700G17P | RootProfile::J714G17S26A428) { + if profile == RootProfile::J700G17P || profile.m5_layout().is_some() { put_u32(out, MAIN_PRIMARY_SENTINEL_A, 1); put_u32(out, MAIN_PRIMARY_SENTINEL_B, 0xabcd_abcd); } @@ -863,14 +896,14 @@ pub(crate) fn encode_main_config_for_profile( RootProfile::J700G17P => MAIN_SECONDARY_KIND_VALUE, RootProfile::J813G17G25G76 => 0xc, - RootProfile::J873G18G26A428 | RootProfile::J714G17S26A428 => { - COMMON26_MAIN_SECONDARY_KIND - } + RootProfile::J873G18G26A428 + | RootProfile::J714G17S26A428 + | RootProfile::J714G17S26B123 => COMMON26_MAIN_SECONDARY_KIND, }); - if profile == RootProfile::J714G17S26A428 { + if let Some(layout) = profile.m5_layout() { put_u64(out, COMMON26_MAIN_SECONDARY_POLICY_PTR, - bundle + M5_SHARED_LAYOUT.policy as u64); + bundle + layout.shared.policy as u64); } else if profile == RootProfile::J873G18G26A428 { put_u64(out, COMMON26_MAIN_SECONDARY_POLICY_PTR, @@ -900,7 +933,7 @@ pub(crate) fn encode_region_c_for_profile( return encode_common26_region_c(out); } - if profile == RootProfile::J714G17S26A428 { + if profile.m5_layout().is_some() { return Err(InitdataError::BufferSize); } @@ -1653,6 +1686,10 @@ pub(crate) const M5_CONTROL_OPENING_OPCODE_SECONDARY: u32 = pub(crate) const M5_INITDATA_VERSION_WORD: u64 = 0x0c8e_0339_8728_04c0; +/// The interface version word, at root+0, of the 27.2 (26B123) and 27.3 beta 1 (26C5035r) M5 +/// firmware: the 26A428 word with bit 46 set. It is not yet confirmed from captured InitData. +pub(crate) const M5_26B123_INITDATA_VERSION_WORD: u64 = 0x0c8e_4339_8728_04c0; + pub(crate) const M5_ROOT_SIZE: usize = 0xc8; pub(crate) const ABI_ALLOCATION_SIZES_26A428_M5: [usize; 24] = [ @@ -1660,11 +1697,144 @@ pub(crate) const ABI_ALLOCATION_SIZES_26A428_M5: [usize; 24] = [ 0xe730, 0x0780, 0x0020, 0x01b0, 0x4800, 0x28800, 0x79800, 0x9000, 0x00a8, 0x0008, 0x1ca0, 0x00e4, ]; -pub(crate) const M5_SHARED_LAYOUT: SharedClusterLayout = - shared_cluster_layout(&ABI_ALLOCATION_SIZES_26A428_M5); +/// The allocation size table of the 27.2 (26B123) and 27.3 beta 1 (26C5035r) M5 firmware, indexed +/// like [`ABI_ALLOCATION_SIZES_26A428_M5`]. Only two entries differ from it: the power policy +/// (index 4, 0x126b0 -> 0x12728) and the status config (index 13, 0x780 -> 0x790). The sizes are +/// not yet confirmed from captured InitData. +pub(crate) const ABI_ALLOCATION_SIZES_26B123_M5: [usize; 24] = [ + 0x2850, 0x0c18, 0x1048, 0x0e10, 0x12728, 0x0068, 0x0800, 0x0088, 0x1800, 0x04c0, 0x4000, + 0x0030, 0xe730, 0x0790, 0x0020, 0x01b0, 0x4800, 0x28800, 0x79800, 0x9000, 0x00a8, 0x0008, + 0x1ca0, 0x00e4, +]; + +const _: () = { + let mut index = 0; + while index < ABI_ALLOCATION_SIZES_26B123_M5.len() { + let same = ABI_ALLOCATION_SIZES_26B123_M5[index] == ABI_ALLOCATION_SIZES_26A428_M5[index]; + assert!(same == (index != ALLOC_POLICY && index != ALLOC_STATUS_CONFIG)); + index += 1; + } +}; + +/// The InitData layout of one M5 firmware build: its allocation size table, the packed clusters +/// derived from it, and how its HW data differs from the 26A428 encoding. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct M5Layout { + /// The allocation size table. + pub(crate) sizes: [usize; 24], + /// The shared cluster (the HW data bundle). + pub(crate) shared: SharedClusterLayout, + /// The private cluster. + pub(crate) private: PrivateClusterLayout, + /// Moves the 26A428 HW data encoding to this build's offsets; empty where they are the same. + pub(crate) hw_data_bands: &'static [LayoutBand], + /// Words of this build's HW data that have no place in the 26A428 encoding: offset, value. + pub(crate) hw_data_words: &'static [(usize, u32)], +} + +impl M5Layout { + const fn new( + sizes: [usize; 24], + hw_data_bands: &'static [LayoutBand], + hw_data_words: &'static [(usize, u32)], + ) -> Self { + Self { + sizes, + shared: shared_cluster_layout(&sizes), + private: private_cluster_layout(&sizes), + hw_data_bands, + hw_data_words, + } + } + + pub(crate) const fn hw_data_size(&self) -> usize { + self.sizes[ALLOC_HW_DATA] + } + + pub(crate) const fn policy_size(&self) -> usize { + self.sizes[ALLOC_POLICY] + } + + pub(crate) const fn status_config_size(&self) -> usize { + self.sizes[ALLOC_STATUS_CONFIG] + } + + /// The bundle up to its BRN table: HW data, the four views and the power policy. + pub(crate) const fn shared_prefix_size(&self) -> usize { + self.shared.brn_table + } + + pub(crate) const fn bundle_view_offsets(&self) -> [usize; 5] { + [ + self.shared.block_c18, + self.shared.block_1048, + self.shared.block_e10, + self.shared.block_68, + self.shared.block_800, + ] + } + + pub(crate) const fn bundle_view_extents(&self) -> [usize; 5] { + [ + self.sizes[ALLOC_BLOCK_C18], + self.sizes[ALLOC_BLOCK_1048], + self.sizes[ALLOC_BLOCK_E10], + self.sizes[ALLOC_BLOCK_68], + self.sizes[ALLOC_BLOCK_800], + ] + } + + pub(crate) const fn private_status_a(&self) -> [usize; 2] { + [ + self.private.roles[0].status_a, + self.private.roles[1].status_a, + ] + } + + pub(crate) const fn private_state_grids(&self) -> [usize; 2] { + [ + self.private.dm_ring_states[0], + self.private.secondary_control_state - WORK_STATE_GRID_OFFSETS[CONTROL_CHANNEL_INDEX], + ] + } + + pub(crate) const fn status_config_in_status_b(&self) -> usize { + self.private.status_config - self.private.status_b + } + + pub(crate) const fn status_b_span(&self) -> usize { + self.private.roles[0].status_a - self.private.status_b + } + + /// Where this build's HW data holds the field the 26A428 encoding holds at `offset`. + pub(crate) const fn hw_data_offset(&self, offset: usize) -> Option { + if self.hw_data_bands.is_empty() { + return Some(offset); + } + relocate_by_bands(self.hw_data_bands, offset) + } +} + +/// The 27.0 (26A428) M5 layout. +pub(crate) const M5_LAYOUT_26A428: M5Layout = + M5Layout::new(ABI_ALLOCATION_SIZES_26A428_M5, &[], &[]); + +/// The 27.2 (26B123) M5 layout. +pub(crate) const M5_LAYOUT_26B123: M5Layout = + M5Layout::new(ABI_ALLOCATION_SIZES_26B123_M5, &[], &[]); + +const _: () = { + // The bundle and the private cluster keep the sizes of their fixed windows. + assert!(M5_LAYOUT_26B123.shared.total == M5_LAYOUT_26A428.shared.total); + assert!(M5_LAYOUT_26B123.private.total == M5_LAYOUT_26A428.private.total); + // The 26B123 HW data is 26A428's. + let kick = M5_KICK_SHARED_POINTER; + assert!(matches!(M5_LAYOUT_26B123.hw_data_offset(kick), Some(0x27dc))); +}; + +pub(crate) const M5_SHARED_LAYOUT: SharedClusterLayout = M5_LAYOUT_26A428.shared; -pub(crate) const M5_PRIVATE_LAYOUT: PrivateClusterLayout = - private_cluster_layout(&ABI_ALLOCATION_SIZES_26A428_M5); +pub(crate) const M5_PRIVATE_LAYOUT: PrivateClusterLayout = M5_LAYOUT_26A428.private; pub(crate) const M5_SHARED_CLUSTER_SIZE: usize = M5_SHARED_LAYOUT.total; @@ -1675,10 +1845,17 @@ pub(crate) const M5_HW_DATA_SIZE: usize = ABI_ALLOCATION_SIZES_26A428_M5[ALLOC_H pub(crate) const M5_KICK_SHARED_SIZE: usize = 0x2000; pub(crate) const M5_KICK_SHARED_POINTER: usize = 0x27dc; -pub(crate) fn set_m5_kick_shared(out: &mut [u8], address: u64) -> Result<(), InitdataError> { - if out.len() != M5_HW_DATA_SIZE { +pub(crate) fn set_m5_kick_shared( + layout: &M5Layout, + out: &mut [u8], + address: u64, +) -> Result<(), InitdataError> { + if out.len() != layout.hw_data_size() { return Err(InitdataError::BufferSize); } + let pointer = layout + .hw_data_offset(M5_KICK_SHARED_POINTER) + .ok_or(InitdataError::BufferSize)?; required_address(address)?; let end = address.checked_add(M5_KICK_SHARED_SIZE as u64 - 1) .ok_or(InitdataError::AddressOutOfRange)?; @@ -1686,7 +1863,7 @@ pub(crate) fn set_m5_kick_shared(out: &mut [u8], address: u64) -> Result<(), Ini if address & (UAT_PAGE_SIZE as u64 - 1) != 0 { return Err(InitdataError::AddressOutOfRange); } - put_u64(out, M5_KICK_SHARED_POINTER, address); + put_u64(out, pointer, address); Ok(()) } @@ -1893,15 +2070,23 @@ const _: () = { /// Populate the enabled-USC count from the device's sampled core masks. /// This count covers the whole device, not one cluster. -pub(crate) fn set_m5_enabled_cores(out: &mut [u8], enabled: u32) -> Result<(), InitdataError> { - if out.len() != M5_HW_DATA_SIZE { +pub(crate) fn set_m5_enabled_cores( + layout: &M5Layout, + out: &mut [u8], + enabled: u32, +) -> Result<(), InitdataError> { + if out.len() != layout.hw_data_size() { return Err(InitdataError::BufferSize); } if enabled == 0 || enabled > 128 { return Err(InitdataError::InterfaceMismatch); } - put_u32(out, 0x10a4, enabled); - put_u32(out, 0x268c, enabled); + for offset in [0x10a4, 0x268c] { + let offset = layout + .hw_data_offset(offset) + .ok_or(InitdataError::BufferSize)?; + put_u32(out, offset, enabled); + } Ok(()) } @@ -2244,13 +2429,14 @@ pub(crate) fn encode_m5_register_map( } pub(crate) fn encode_m5_hw_data( + layout: &M5Layout, board: &Board, aliases: [G17PKsmCompletionAliases; M5_25G76_COMPLETION_QUEUE_COUNT], qos_high: u64, windows: &[M5RegisterWindow], out: &mut [u8], ) -> Result<(), InitdataError> { - if out.len() != M5_HW_DATA_SIZE { + if out.len() != M5_HW_DATA_SIZE || out.len() != layout.hw_data_size() { return Err(InitdataError::BufferSize); } required_address(qos_high)?; @@ -2325,6 +2511,49 @@ pub(crate) fn encode_m5_hw_data( } // The inherited J813 minimum parameter-buffer size is replaced per board. put_u32(out, 0x26b0, board.min_parameter_buffer); + // Then the build's own HW data, which only adds to the 26A428 one. + relocate_hw_data(layout.hw_data_bands, out)?; + for &(offset, value) in layout.hw_data_words { + if offset + 4 > out.len() { + return Err(InitdataError::BufferSize); + } + put_u32(out, offset, value); + } + Ok(()) +} + +/// Moves an HW data encoding by `bands`, which are in ascending order and move their bytes up by +/// non-negative deltas, none smaller than the band before it. The bytes no band moves anything to +/// are cleared; the bytes past the last band have to be zero, as the move drops them. +fn relocate_hw_data(bands: &[LayoutBand], out: &mut [u8]) -> Result<(), InitdataError> { + let Some(last) = bands.last() else { + return Ok(()); + }; + let dropped = out.get(last.end..).ok_or(InitdataError::BufferSize)?; + if dropped.iter().any(|byte| *byte != 0) { + return Err(InitdataError::BufferSize); + } + let mut floor = (0, 0); + for band in bands { + let delta = usize::try_from(band.delta).map_err(|_| InitdataError::BufferSize)?; + if band.start < floor.0 || band.end < band.start || delta < floor.1 { + return Err(InitdataError::BufferSize); + } + if band.end + delta > out.len() { + return Err(InitdataError::BufferSize); + } + floor = (band.end, delta); + } + for band in bands.iter().rev() { + out.copy_within(band.start..band.end, band.start + band.delta as usize); + } + let mut moved = 0; + for band in bands { + let start = band.start + band.delta as usize; + out[moved..start].fill(0); + moved = band.end + band.delta as usize; + } + out[moved..].fill(0); Ok(()) } @@ -2378,18 +2607,19 @@ pub(crate) fn encode_m5_region_c(out: &mut [u8]) -> Result<(), InitdataError> { } pub(crate) fn encode_m5_primary_status_b( + layout: &M5Layout, fwctl_state: u64, fwctl_ring: u64, status_b: u64, out: &mut [u8], ) -> Result<(), InitdataError> { - if out.len() != M5_STATUS_B_SPAN { + if out.len() != layout.status_b_span() { return Err(InitdataError::BufferSize); } required_address(fwctl_state)?; required_address(fwctl_ring)?; required_address(status_b)?; - let config = M5_STATUS_CONFIG_IN_STATUS_B; + let config = layout.status_config_in_status_b(); out.fill(0); encode_status_block(&mut out[..STATUS_BLOCK_SIZE])?; put_u64(out, COMMON26_STATUS_B_FWCTL_STATE, fwctl_state); @@ -2399,7 +2629,7 @@ pub(crate) fn encode_m5_primary_status_b( put_u64(out, COMMON26_STATUS_B_LINK_PTR, status_b + (config + M5_STATUS_CONFIG_LINK_WORD) as u64); write_relocated_words(M5_25G76_STATUS_CONFIG_WORDS, 0, COMMON26_STATUS_CONFIG_BANDS, - &mut out[config..config + M5_STATUS_CONFIG_SIZE])?; + &mut out[config..config + layout.status_config_size()])?; put_u32(out, config + M5_STATUS_CONFIG_LINK_WORD, 1); put_u64(out, config + M5_STATUS_CONFIG_LINK_PTR, status_b + COMMON26_STATUS_B_LINK_WORD as u64); @@ -2407,11 +2637,12 @@ pub(crate) fn encode_m5_primary_status_b( } pub(crate) fn encode_m5_private_cluster( + m5_layout: &M5Layout, private_base: u64, fwctl_state: u64, out: &mut [u8], ) -> Result<(), InitdataError> { - let layout = M5_PRIVATE_LAYOUT; + let layout = m5_layout.private; if out.len() != layout.total { return Err(InitdataError::BufferSize); } @@ -2431,7 +2662,8 @@ pub(crate) fn encode_m5_private_cluster( out.fill(0); let status_b = layout.status_b; let status_a = layout.roles[0].status_a; - encode_m5_primary_status_b(fwctl_state, fwctl_ring, private_base + status_b as u64, + encode_m5_primary_status_b(m5_layout, fwctl_state, fwctl_ring, + private_base + status_b as u64, &mut out[status_b..status_a])?; for role in layout.roles { encode_status_block(&mut out[role.status_a..role.status_a + STATUS_BLOCK_SIZE])?; @@ -2448,23 +2680,25 @@ pub(crate) fn encode_m5_private_cluster( } pub(crate) fn encode_m5_bundle_policy( + m5_layout: &M5Layout, bindings: M5PolicyBindings, out: &mut [u8], ) -> Result<(), InitdataError> { - let layout = M5_SHARED_LAYOUT; - if out.len() < M5_SHARED_PREFIX_SIZE { + let layout = m5_layout.shared; + let prefix = m5_layout.shared_prefix_size(); + if out.len() < prefix { return Err(InitdataError::BufferSize); } required_address(bindings.state)?; required_address(bindings.aon_ptd)?; - out[layout.block_c18..M5_SHARED_PREFIX_SIZE].fill(0); + out[layout.block_c18..prefix].fill(0); let m5 = M5_25G76_SHARED_LAYOUT; for &(offset, value) in M5_25G76_POLICY_WORDS { if (m5.block_1048..m5.block_e10).contains(&offset) { put_u32(out, layout.block_1048 + (offset - m5.block_1048), value); } } - let policy = &mut out[layout.policy..layout.policy + M5_POLICY_SIZE]; + let policy = &mut out[layout.policy..layout.policy + m5_layout.policy_size()]; put_u32(policy, 0x04, J714_GPU_POWER_SAMPLE_PERIOD_AIC_CLOCKS); put_u32(policy, 0x08, J714_GPU_POWER_SAMPLE_PERIOD_AIC_CLOCKS); diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 5c333e05f8788..2dd5c515b1539 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -172,6 +172,8 @@ impl WorkPublication<'_> { /// Session-long storage and mappings. Aliases are released before their backing objects. pub(super) struct InitData { m5: bool, + /// The layout of the M5 graph, once [`InitData::build_m5`] has chosen it. + m5_layout: &'static super::fw::m5::initdata::M5Layout, outer_credits: Option>, // Live publication uses interior writes under the device mutex. This owner is Send but not // Sync, so safe host code cannot issue those writes concurrently through shared references. @@ -284,6 +286,7 @@ impl InitData { pub(super) fn new() -> Self { Self { m5: false, + m5_layout: &super::fw::m5::initdata::M5_LAYOUT_26A428, outer_credits: None, _single_publisher: core::marker::PhantomData, aliases: KVec::new(), @@ -1655,17 +1658,17 @@ impl InitData { } fn main_config_offset(&self, role: Role) -> usize { - if self.m5 { super::fw::m5::initdata::M5_MAIN_BUNDLE_OFFSETS[role as usize] } + if self.m5 { self.m5_layout.shared.main_config[role as usize] } else { abi::bundle::MAIN_CONFIG[role as usize] } } fn work_ring_offset(&self, channel: usize) -> usize { - if self.m5 { super::fw::m5::initdata::M5_WORK_RING_BUNDLE_OFFSETS[channel] } + if self.m5 { super::fw::m5::initdata::work_ring_offsets(&self.m5_layout.shared)[channel] } else { abi::work_ring_offset(channel) } } fn state_offset(&self, role: Role) -> usize { - if self.m5 { return super::fw::m5::initdata::M5_PRIVATE_STATE_GRIDS[role as usize]; } + if self.m5 { return self.m5_layout.private_state_grids()[role as usize]; } match role { Role::Primary => abi::private::PRIMARY_STATE, Role::Secondary => abi::private::SECONDARY_STATE, @@ -1673,7 +1676,7 @@ impl InitData { } fn status_offset(&self, role: Role) -> usize { - if self.m5 { return super::fw::m5::initdata::M5_PRIVATE_STATUS_A[role as usize]; } + if self.m5 { return self.m5_layout.private_status_a()[role as usize]; } match role { Role::Primary => self.status_a, Role::Secondary => abi::private::SECONDARY_STATUS_A, @@ -1770,7 +1773,7 @@ impl InitData { next: u32, ) -> Result { let ring_base = if self.m5 { - super::fw::m5::initdata::M5_CONTROL_RING_BUNDLE_OFFSETS[0] + self.m5_layout.shared.control_ring[0] } else { self.main_config_offset(Role::Primary) + abi::MainConfig::CONTROL_RING }; let offset = ring_base + (slot as usize).checked_mul(record.len()).ok_or(EINVAL)?; let ring = self.object(self.bundle)?.pointer(offset, record.len())?; @@ -1827,14 +1830,13 @@ impl InitData { fence(Ordering::Acquire); Ok(self .object(self.cluster)? - .word(m::M5_PRIVATE_LAYOUT.status_b + m::M5_PROGRESS)? + .word(self.m5_layout.private.status_b + m::M5_PROGRESS)? .load(Ordering::Relaxed)) } fn recovery_state_offset(&self) -> usize { if self.m5 { - super::fw::m5::initdata::M5_PRIVATE_LAYOUT.status_b - + super::fw::m5::initdata::M5_RECOVERY_STATE + self.m5_layout.private.status_b + super::fw::m5::initdata::M5_RECOVERY_STATE } else { self.status_b + abi::PrimaryStatusB::RECOVERY_STATE } } @@ -1842,7 +1844,7 @@ impl InitData { fn recovery_view_words(&self, view: usize, offset: usize) -> Result<[u32; N]> { let bundle = self.object(self.bundle)?; let extent = if self.m5 { - *super::fw::m5::initdata::M5_BUNDLE_VIEW_EXTENTS.get(view).ok_or(EINVAL)? + *self.m5_layout.bundle_view_extents().get(view).ok_or(EINVAL)? } else { *abi::recovery::VIEW_EXTENTS.get(view).ok_or(EINVAL)? }; @@ -1851,7 +1853,7 @@ impl InitData { Some(views) => (self.object(*views.get(view).ok_or(EINVAL)?)?, 0), None if self.m5 => ( bundle, - *super::fw::m5::initdata::M5_BUNDLE_VIEW_OFFSETS.get(view).ok_or(EINVAL)?, + *self.m5_layout.bundle_view_offsets().get(view).ok_or(EINVAL)?, ), None => (bundle, *abi::bundle::VIEWS.get(view).ok_or(EINVAL)?), }; @@ -1879,8 +1881,7 @@ impl super::recovery::Memory for InitData { } fn fault_sources(&self) -> Result { let mask_offset = if self.m5 { - super::fw::m5::initdata::M5_PRIVATE_LAYOUT.status_b - + super::fw::m5::initdata::M5_RECOVERY_SLOT_MASK + self.m5_layout.private.status_b + super::fw::m5::initdata::M5_RECOVERY_SLOT_MASK } else { self.status_b + abi::PrimaryStatusB::RECOVERY_SLOT_MASK }; @@ -1927,8 +1928,7 @@ impl super::recovery::Memory for InitData { Ok(self .object(self.cluster)? .word(if self.m5 { - super::fw::m5::initdata::M5_PRIVATE_LAYOUT.status_b - + super::fw::m5::initdata::M5_HOST_RECOVERY + self.m5_layout.private.status_b + super::fw::m5::initdata::M5_HOST_RECOVERY } else { self.status_b + abi::PrimaryStatusB::HOST_RECOVERY })? @@ -1956,7 +1956,7 @@ impl super::recovery::Memory for InitData { Some(views) => (self.object(*views.get(4).ok_or(EINVAL)?)?, 0), None if self.m5 => ( self.object(self.bundle)?, - super::fw::m5::initdata::M5_BUNDLE_VIEW_OFFSETS[4], + self.m5_layout.bundle_view_offsets()[4], ), None => (self.object(self.bundle)?, abi::bundle::VIEWS[4]), }; @@ -1982,8 +1982,7 @@ impl super::recovery::Memory for InitData { } let cluster = self.object(self.cluster)?; let base = if self.m5 { - super::fw::m5::initdata::M5_PRIVATE_LAYOUT.status_b - + super::fw::m5::initdata::M5_RECOVERY_INFO + self.m5_layout.private.status_b + super::fw::m5::initdata::M5_RECOVERY_INFO } else { self.status_b + abi::PrimaryStatusB::RECOVERY_INFO }; @@ -2016,7 +2015,7 @@ impl InitData { pub(super) fn m5_gpu_power_mw(&self) -> Result { if !self.m5 { return Err(EINVAL); } Ok(self.object(self.bundle)? - .word(super::fw::m5::initdata::M5_SHARED_LAYOUT.policy + super::thermal::POLICY_GPU_POWER)? + .word(self.m5_layout.shared.policy + super::thermal::POLICY_GPU_POWER)? .load(Ordering::Relaxed)) } @@ -2025,7 +2024,7 @@ impl InitData { pub(super) fn m5_firmware_clamped(&self) -> Result { if !self.m5 { return Err(EINVAL); } let block = self.object(self.bundle)?; - let base = super::fw::m5::initdata::M5_SHARED_LAYOUT.policy; + let base = self.m5_layout.shared.policy; let mask = block.word(base + super::thermal::POLICY_LIMITER_MASK)?.load(Ordering::Relaxed); let limit = block.word(base + super::thermal::POLICY_LIMITER_LIMIT)?.load(Ordering::Relaxed); Ok(super::thermal::is_emergency_clamp(mask, limit)) diff --git a/drivers/gpu/drm/asahi/g17/initdata/m5.rs b/drivers/gpu/drm/asahi/g17/initdata/m5.rs index ec150b54f1bee..19b3725827357 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/m5.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/m5.rs @@ -41,17 +41,19 @@ impl InitData { /// Diagnostic-only read of an aligned word within the existing policy allocation. /// Callers hold the device state mutex, retaining this mapping for the entire sample. pub(crate) fn m5_policy_telemetry_word(&self, offset: usize) -> Result { - if !self.m5 || offset & 3 != 0 || offset.checked_add(4).is_none_or(|end| end > m::M5_POLICY_SIZE) { + let layout = self.m5_layout; + let end = offset.checked_add(4); + if !self.m5 || offset & 3 != 0 || end.is_none_or(|end| end > layout.policy_size()) { return Err(EINVAL); } - let at = m::M5_SHARED_LAYOUT.policy.checked_add(offset).ok_or(EINVAL)?; + let at = layout.shared.policy.checked_add(offset).ok_or(EINVAL)?; Ok(self.object(self.bundle)?.word(at)?.load(Ordering::Relaxed)) } pub(crate) fn note_m5_submission_history(&self) -> Result { if !self.m5 { return Err(EINVAL); } let private = self.object(self.cluster)?; - let base = m::M5_PRIVATE_LAYOUT.status_config; + let base = self.m5_layout.private.status_config; for _ in 0..64 { let generation = private.word(base + 0x74c)?.load(Ordering::Acquire); if generation & 1 != 0 { core::hint::spin_loop(); continue; } @@ -81,11 +83,21 @@ impl InitData { Err(EBUSY) } - pub(crate) fn build_m5(&mut self, dev: &AsahiDrmDevice, uat: &mmu::Uat, - id: &crate::regs::G17Id, board: &m::Board) -> Result { + /// Builds the M5 graph in the InitData layouts the loaded firmware build (`variant`) names. + pub(crate) fn build_m5( + &mut self, + dev: &AsahiDrmDevice, + uat: &mmu::Uat, + id: &crate::regs::G17Id, + board: &m::Board, + variant: &crate::g17::fw::Variant, + ) -> Result { use mmu::{PROT_FW_SHARED_RW as RW, PROT_FW_SHARED_RO as RO, PROT_GPU_SHARED_RW as GPU}; + let profile = m::RootProfile::for_m5_layouts(variant.m5_layouts.ok_or(EINVAL)?); + let layout = profile.m5_layout().ok_or(EINVAL)?; self.m5 = true; + self.m5_layout = layout; // TA, fragment and compute share the compute work ring per priority; every // preparation's claim on it is counted from the first publication on. self.outer_credits = Some(super::outer_credit::Credits::new()?); @@ -95,8 +107,8 @@ impl InitData { KernelObject::new(dev, uat.kernel_vm(), Placement::At(address), size, mmu::PROT_FW_PRIV_RW, CpuMap::WriteBack) }; - let private = m::M5_PRIVATE_LAYOUT; - let shared = m::M5_SHARED_LAYOUT; + let private = layout.private; + let shared = layout.shared; self.cluster = self.add(fixed(PRIVATE, private.total, RW)?)?; self.region_c = self.add(fixed(REGION_C, mmu::UAT_PGSZ, RO)?)?; self.roots = self.add(fixed(ROOTS, 0xc000, RO)?)?; @@ -136,7 +148,7 @@ impl InitData { m::encode_common26_aon_ptd(board, &mut self.object_mut(aon)?.bytes_mut()[..m::M5_AON_PTD_SIZE]) .map_err(|_| EINVAL)?; m::encode_m5_region_c(self.object_mut(self.region_c)?.bytes_mut()).map_err(|_| EINVAL)?; - m::encode_m5_private_cluster(PRIVATE, FWCTL, + m::encode_m5_private_cluster(layout, PRIVATE, FWCTL, &mut self.object_mut(self.cluster)?.bytes_mut()[..private.total]).map_err(|_| EINVAL)?; let windows = board.register_windows(); let windows = &windows[..board.register_window_count()]; @@ -161,18 +173,19 @@ impl InitData { let views = m::PrimaryRegionViews { sentinel_high: BASE + 0xc07e_0000, alias_high: [PB, UMA], }; - m::encode_m5_hw_data(board, completions, QOS, windows, - &mut self.object_mut(self.bundle)?.bytes_mut()[..m::M5_HW_DATA_SIZE]).map_err(|_| EINVAL)?; - m::set_m5_kick_shared( - &mut self.object_mut(self.bundle)?.bytes_mut()[..m::M5_HW_DATA_SIZE], KICK_SHARED) + let hw_data = layout.hw_data_size(); + m::encode_m5_hw_data(layout, board, completions, QOS, windows, + &mut self.object_mut(self.bundle)?.bytes_mut()[..hw_data]).map_err(|_| EINVAL)?; + m::set_m5_kick_shared(layout, + &mut self.object_mut(self.bundle)?.bytes_mut()[..hw_data], KICK_SHARED) .map_err(|_| EINVAL)?; let masked_cores: u32 = id.core_masks.iter().map(|mask| mask.count_ones()).sum(); let enabled_cores = if masked_cores != 0 { masked_cores } else { id.num_clusters.checked_mul(id.num_cores).ok_or(EOVERFLOW)? }; - m::set_m5_enabled_cores( - &mut self.object_mut(self.bundle)?.bytes_mut()[..m::M5_HW_DATA_SIZE], enabled_cores) + m::set_m5_enabled_cores(layout, + &mut self.object_mut(self.bundle)?.bytes_mut()[..hw_data], enabled_cores) .map_err(|_| EINVAL)?; - m::encode_m5_bundle_policy(m::M5PolicyBindings { + m::encode_m5_bundle_policy(layout, m::M5PolicyBindings { state: PRIVATE + private.policy_state as u64, aon_ptd: AON, boot_timestamp: 0, }, self.object_mut(self.bundle)?.bytes_mut()).map_err(|_| EINVAL)?; // The controller configuration applies where the part's leakage calibration matches the @@ -207,9 +220,8 @@ impl InitData { [PRIVATE + private.status_config as u64, PRIVATE + private.secondary_extra_1 as u64] }, }; - let profile = m::RootProfile::J714G17S26A428; let channels = m::derive_channel_table_for_profile(profile, role, - PRIVATE + m::M5_PRIVATE_STATE_GRIDS[index] as u64, + PRIVATE + layout.private_state_grids()[index] as u64, instance.status_a, instance.main_config, BUNDLE); let offset = shared.main_config[index]; m::encode_main_config_for_profile(profile, role, &addresses, &instance, diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 61a2cb248aa8b..2ed19e4fc64d9 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1892,7 +1892,7 @@ impl Gpu { .inspect_err(at("root adoption"))?; session .init - .build_m5(dev, &session.uat, id, board) + .build_m5(dev, &session.uat, id, board, shared.variant) .inspect_err(at("M5 graph"))?; session .init From ce2dce2ec7fa47133e6a034a092da8c9ca2c5c7a Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:37:14 -0500 Subject: [PATCH 04/11] drm/asahi: g17: m5: build the 27.3 beta 1 (26C5035r) HW data The M5 firmware of macOS 27.3 beta 1 (26C5035r) takes the 26B123 version word and allocation sizes, but its HW data is not 26B123's: one u32 is inserted at 0x2720 and every later field moves by 4 (the kick-shared pointer from 0x27dc to 0x27e0, the last moved word from 0x2846 to 0x284a; everything below 0x2720 is as before). The version word does not tell the two builds apart; only the firmware identity does. The new word is the time the GPU may take to reach a forced idle after a sleep request, in ms; 0 disables the wait. The value written, 5000, is 5 s times 1000. The layout, the meaning of the word and the value are not yet confirmed from captured InitData. Describe the move with the layout bands the HW data already uses (M5_26C5035_HW_DATA_BANDS: 0..0x2720 stays, 0x2720..0x284c moves by 4; the four bytes it drops past 0x284c are zero in the 26A428 encoding), apply them after the 26A428 encoding and write the word, and find the kick-shared pointer and the enabled-core words through them. Add RootProfile J714G17S26C5035, M5Layouts::V26C5035 and its Variant V26C5035_M5, and have the 27.3 (26C5035r) G17S build of the M5 Pro name it instead of V26B123_M5. The fwid KUnit test that checks each M5 Pro build's layouts follows. The encoded 26C5035r HW data of every board equals the 26A428 encoding below 0x2720, has 5000 at 0x2720 and carries the 26A428 bytes 0x2720..0x284c at 0x2724. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/fwid.rs | 7 +- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 73 +++++++++++++++++---- drivers/gpu/drm/asahi/g17/fw/variant.rs | 10 +++ drivers/gpu/drm/asahi/hw/t6050.rs | 2 +- 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs index a19ac8be41142..f64e11f440685 100644 --- a/drivers/gpu/drm/asahi/fwid.rs +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -561,8 +561,11 @@ mod tests { panic!("the M5 Pro must identify its loaded firmware"); }; for (i, build) in builds.iter().enumerate() { - // 27.2 (26B123) moved the InitData layouts; 27.3 kept them. - let layouts = if build.os_version >= [27, 2, 0] { + // 27.2 (26B123) moved the InitData layouts; 27.3 beta 1 (26C5035r) also moved + // the HW data. + let layouts = if build.os_version >= [27, 3, 0] { + M5Layouts::V26C5035 + } else if build.os_version >= [27, 2, 0] { M5Layouts::V26B123 } else { M5Layouts::V26A428 diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index a3472923ac959..21523cb50ddef 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -38,6 +38,11 @@ pub(crate) enum RootProfile { /// The M5 graph of the macOS 27.2 firmware (26B123): the 26A428 graph with a larger power /// policy and status config ([`ABI_ALLOCATION_SIZES_26B123_M5`]). J714G17S26B123, + + /// The M5 graph of the macOS 27.3 beta 1 firmware (26C5035r): the 26B123 graph with one more + /// HW data word ([`M5_26C5035_HW_DATA_BANDS`]). It takes the same interface version word as + /// 26B123's, so only the firmware's identity tells them apart. + J714G17S26C5035, } impl RootProfile { @@ -47,6 +52,7 @@ impl RootProfile { match layouts { M5Layouts::V26A428 => Self::J714G17S26A428, M5Layouts::V26B123 => Self::J714G17S26B123, + M5Layouts::V26C5035 => Self::J714G17S26C5035, } } @@ -55,6 +61,7 @@ impl RootProfile { match self { Self::J714G17S26A428 => Some(&M5_LAYOUT_26A428), Self::J714G17S26B123 => Some(&M5_LAYOUT_26B123), + Self::J714G17S26C5035 => Some(&M5_LAYOUT_26C5035), Self::J700G17P | Self::J813G17G25G76 | Self::J873G18G26A428 => None, } } @@ -65,14 +72,14 @@ impl RootProfile { Self::J813G17G25G76 => 0x0c8a_a322_039e_04c0, Self::J873G18G26A428 => COMMON26_INITDATA_VERSION_WORD, Self::J714G17S26A428 => M5_INITDATA_VERSION_WORD, - Self::J714G17S26B123 => M5_26B123_INITDATA_VERSION_WORD, + Self::J714G17S26B123 | Self::J714G17S26C5035 => M5_26B123_INITDATA_VERSION_WORD, } } pub(crate) const fn root_size(self, role: InstanceRole) -> usize { match self { Self::J873G18G26A428 => COMMON26_ROOT_SIZE, - Self::J714G17S26A428 | Self::J714G17S26B123 => M5_ROOT_SIZE, + Self::J714G17S26A428 | Self::J714G17S26B123 | Self::J714G17S26C5035 => M5_ROOT_SIZE, Self::J700G17P | Self::J813G17G25G76 => root_size(role), } } @@ -85,12 +92,14 @@ impl RootProfile { (Self::J873G18G26A428, InstanceRole::Secondary) => { COMMON26_CONTROL_OPENING_OPCODE_SECONDARY } - (Self::J714G17S26A428 | Self::J714G17S26B123, InstanceRole::Primary) => { - M5_CONTROL_OPENING_OPCODE_PRIMARY - } - (Self::J714G17S26A428 | Self::J714G17S26B123, InstanceRole::Secondary) => { - M5_CONTROL_OPENING_OPCODE_SECONDARY - } + ( + Self::J714G17S26A428 | Self::J714G17S26B123 | Self::J714G17S26C5035, + InstanceRole::Primary, + ) => M5_CONTROL_OPENING_OPCODE_PRIMARY, + ( + Self::J714G17S26A428 | Self::J714G17S26B123 | Self::J714G17S26C5035, + InstanceRole::Secondary, + ) => M5_CONTROL_OPENING_OPCODE_SECONDARY, (_, InstanceRole::Primary) => CONTROL_OPENING_OPCODE_PRIMARY, (_, InstanceRole::Secondary) => CONTROL_OPENING_OPCODE_SECONDARY, } @@ -98,7 +107,10 @@ impl RootProfile { pub(crate) const fn control_register_opcode(self) -> Option { match self { - Self::J873G18G26A428 | Self::J714G17S26A428 | Self::J714G17S26B123 => None, + Self::J873G18G26A428 + | Self::J714G17S26A428 + | Self::J714G17S26B123 + | Self::J714G17S26C5035 => None, Self::J700G17P | Self::J813G17G25G76 => Some(CONTROL_OPENING_PRIMARY_REGISTER_OPCODE), } } @@ -898,7 +910,8 @@ pub(crate) fn encode_main_config_for_profile( RootProfile::J873G18G26A428 | RootProfile::J714G17S26A428 - | RootProfile::J714G17S26B123 => COMMON26_MAIN_SECONDARY_KIND, + | RootProfile::J714G17S26B123 + | RootProfile::J714G17S26C5035 => COMMON26_MAIN_SECONDARY_KIND, }); if let Some(layout) = profile.m5_layout() { @@ -1716,6 +1729,29 @@ const _: () = { } }; +/// The HW data of the 27.3 beta 1 firmware (26C5035r) is 26A428's with one u32 inserted at 0x2720 +/// ([`M5_26C5035_HW_FORCED_IDLE_TIMEOUT`]): every later field moves by 4 (the kick-shared pointer +/// from 0x27dc to 0x27e0, the last moved word from 0x2846 to 0x284a), and the four zero bytes past +/// 0x284c fall off the end. The layout is not yet confirmed from captured InitData. +pub(crate) const M5_26C5035_HW_DATA_BANDS: &[LayoutBand] = &[ + band(0x0000, 0x2720, 0, Evidence::Inferred), + band(0x2720, 0x284c, 4, Evidence::Inferred), +]; + +/// The 26C5035r HW data word for the time the GPU may take to reach a forced idle after a sleep +/// request, ms; 0 disables the wait. 26A428 and 26B123 HW data have no such word. Its meaning and +/// the value written to it are not yet confirmed from captured InitData. +pub(crate) const M5_26C5035_HW_FORCED_IDLE_TIMEOUT: usize = 0x2720; + +/// The forced-idle wait written to the HW data, in seconds (5000 ms). The value is not yet +/// confirmed from captured InitData. +pub(crate) const M5_GPU_TIMEOUT_SECONDS: u32 = 5; + +/// The forced-idle word for a wait of `seconds`: seconds x 1000 ms, saturating to u32::MAX. +pub(crate) const fn m5_forced_idle_timeout_ms(seconds: u32) -> u32 { + seconds.saturating_mul(1000) +} + /// The InitData layout of one M5 firmware build: its allocation size table, the packed clusters /// derived from it, and how its HW data differs from the 26A428 encoding. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -1823,13 +1859,26 @@ pub(crate) const M5_LAYOUT_26A428: M5Layout = pub(crate) const M5_LAYOUT_26B123: M5Layout = M5Layout::new(ABI_ALLOCATION_SIZES_26B123_M5, &[], &[]); +/// The 27.3 beta 1 (26C5035r) M5 layout. +pub(crate) const M5_LAYOUT_26C5035: M5Layout = M5Layout::new( + ABI_ALLOCATION_SIZES_26B123_M5, + M5_26C5035_HW_DATA_BANDS, + &[( + M5_26C5035_HW_FORCED_IDLE_TIMEOUT, + m5_forced_idle_timeout_ms(M5_GPU_TIMEOUT_SECONDS), + )], +); + const _: () = { // The bundle and the private cluster keep the sizes of their fixed windows. assert!(M5_LAYOUT_26B123.shared.total == M5_LAYOUT_26A428.shared.total); assert!(M5_LAYOUT_26B123.private.total == M5_LAYOUT_26A428.private.total); - // The 26B123 HW data is 26A428's. + // The kick-shared pointer and the last word the 26C5035r layout moves. let kick = M5_KICK_SHARED_POINTER; - assert!(matches!(M5_LAYOUT_26B123.hw_data_offset(kick), Some(0x27dc))); + assert!(matches!(M5_LAYOUT_26A428.hw_data_offset(kick), Some(0x27dc))); + assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(kick), Some(0x27e0))); + assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(0x2846), Some(0x284a))); + assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(0x271c), Some(0x271c))); }; pub(crate) const M5_SHARED_LAYOUT: SharedClusterLayout = M5_LAYOUT_26A428.shared; diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 6bbdbea41907e..837ef562f7e3d 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -45,6 +45,9 @@ pub(crate) enum M5Layouts { /// The 27.2 (26B123) graph: version word 0x0c8e_4339_8728_04c0, a larger power policy /// (0x12728) and status config (0x790), the 26A428 HW data. V26B123, + /// The 27.3 beta 1 (26C5035r) graph: 26B123's, the same version word included, with a HW + /// data word inserted at 0x2720. Only the firmware's identity tells it from 26B123. + V26C5035, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -204,6 +207,13 @@ pub(crate) const V26B123_M5: Variant = Variant { ..V26A428_M5 }; +/// The 27.3 beta 1 M5 firmware (26C5035r): the 26A428 M5 protocol with the 26C5035r InitData +/// layouts. +pub(crate) const V26C5035_M5: Variant = Variant { + m5_layouts: Some(M5Layouts::V26C5035), + ..V26A428_M5 +}; + pub(crate) const V25F84: Variant = Variant { qid_floor: 0, registration: QueueRegistration::Mailbox, diff --git a/drivers/gpu/drm/asahi/hw/t6050.rs b/drivers/gpu/drm/asahi/hw/t6050.rs index b074dd6f239b9..686e68f3c080b 100644 --- a/drivers/gpu/drm/asahi/hw/t6050.rs +++ b/drivers/gpu/drm/asahi/hw/t6050.rs @@ -72,7 +72,7 @@ pub(crate) const CONFIG: Config = Config { 0xdc, 0x36, 0x66, 0x76, 0x36, 0x00, ], }], - variant: &crate::g17::fw::variant::V26B123_M5, + variant: &crate::g17::fw::variant::V26C5035_M5, config: None, }, crate::g17::fw::variant::Build { From 630b155f9a707a04839a0235035a24c8f117404a Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:48:23 -0500 Subject: [PATCH 05/11] drm/asahi: g17: m5: map the 26B123 (macOS 27.2) power policy The 26B123 M5 firmware takes a 0x12728-byte power policy instead of 0x126b0 (allocation size table entry 4); 27.3 beta 1 (26C5035r) has the same layout. Five inserts, as bands in 26A428 offsets, move the words above them: [0x0000, 0x4910) +0 [0x4910, 0x491c) +0x10 two u64 words; the three controller input words at 0x4910 (3500/1000/3000 in the J714 defaults) move to 0x4920 [0x491c, 0x49b8) +0x14 a controller input word at 0x492c [0x49b8, 0x4a98) +0x18 4 bytes between 0x494c and 0x49b8 that the M5 encoders do not write [0x4a98, 0x12668) +0x30 0x18 bytes between 0x4a00 and 0x4a98 that they do not write either [0x12668, 0x126b0) +0x78 the table at +0x122f0 grows from 37 to 40 24-byte entries The state and AON PTD pointers move to 0x8284 and 0x11a4c. The 26B123 policy may use words of its own at 0x94e4..0x954c, in space the M5 encoders do not write in 26A428; they stay zero here. The policy controller input words the 26B123 policy carries beyond the 26A428 ones are 0x492c = 5000 and 0x4a14 = 1600 (26A428 0x49fc, 500 in the J714 defaults) for the core types 0x18/0x19, and for the G17D core type 0x1a 0x4920 = 2500, 0x4928 = 800, 0x492c = 1100, 0x49e4 = 0 and 0x4a14 = 1600. These values and the bands above are not yet confirmed from captured InitData; they are what a capture session has to check. Add M5PolicyMap with the 26A428 identity map and the 26B123 map, chosen from the build's M5Layouts (every layout but 26A428 takes the 26B123 map), and relayout_m5_policy(), which moves the bands of the policy the 26A428 encoders wrote in place, highest first, zeroes the inserts and, where the board's controller defaults were applied, writes the 26B123 controller words for its core type. For 26A428 it changes nothing. Not run on hardware: the controller words (they are written only where the board's controller defaults apply, and no run of this layout has had them) and the G17D (J775D) words on 26B123; no 27.2+ M5 Max or M5 Ultra firmware is identified yet. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 214 ++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index 21523cb50ddef..da543f71fb317 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -9,6 +9,8 @@ use core::ops::Range; use core::result::Result; +use crate::g17::fw::variant::M5Layouts; + pub(crate) const ROOT_SIZE_PRIMARY: usize = 0x0b8; pub(crate) const ROOT_SIZE_SECONDARY: usize = 0x0c8; @@ -2143,6 +2145,218 @@ pub(crate) const M5_POLICY_STATE_PTR: usize = 0x8254; pub(crate) const M5_POLICY_AON_PTD_PTR: usize = 0x11a1c; +/// Power policy 26A428 -> 26B123 (macOS 27.2; 27.3 beta 1 has the same layout), in 26A428 +/// offsets. The policy grows from 0x126b0 to 0x12728 bytes (allocation size table entry 4); five +/// inserts move the words above them: +/// +/// - +0x4910, 0x10 bytes (two u64 words). The three controller input words at 0x4910..0x4918 +/// (3500, 1000 and 3000 in the J714 defaults) move to 0x4920. +/// - +0x491c, 4 bytes: a controller input word, at 0x492c in 26B123 +/// ([`M5_26B123_POLICY_CONTROLLER_WORDS`]). +/// - 4 bytes between 0x494c and 0x49b8, and 0x18 bytes between 0x4a00 and 0x4a98, in space the M5 +/// encoders do not write: the bands start at the next written offset (0x4948 -> 0x495c, +/// 0x49b8 -> 0x49d0, 0x49fc -> 0x4a14, 0x4a98 -> 0x4ac8), and no word the M5 encoders write +/// falls in either range. +/// - +0x12668, 0x48 bytes: the table at +0x122f0 (24-byte entries) grows from 37 to 40 entries, +/// its trailing fields moving from +0x378/+0x37c/+0x37d/+0x380 to +0x3c0/+0x3c4/+0x3c5/+0x3c8. +/// The new entries are assumed to sit at the end. +/// +/// The state and AON PTD pointers move with their band, 0x8254 -> 0x8284 and 0x11a1c -> 0x11a4c. +/// The 26B123 policy may use words of its own at 0x94e4..0x954c, in space the M5 encoders do not +/// write in 26A428 (0x94b4..0x951c); they stay zero here. The bands are +/// not yet confirmed from captured InitData. +pub(crate) const M5_26B123_POLICY_BANDS: &[LayoutBand] = &[ + band(0x0000, 0x4910, 0, Evidence::Inferred), + band(0x4910, 0x491c, 0x10, Evidence::Inferred), + band(0x491c, 0x49b8, 0x14, Evidence::Inferred), + band(0x49b8, 0x4a98, 0x18, Evidence::Inferred), + band(0x4a98, 0x12668, 0x30, Evidence::Inferred), + band(0x12668, M5_POLICY_SIZE, 0x78, Evidence::Inferred), +]; + +/// Power-policy size of 26B123 (entry 4 of its allocation size table). +pub(crate) const M5_26B123_POLICY_SIZE: usize = 0x12728; + +/// The 26B123 policy controller input words the 26A428 controller defaults do not write, at 26B123 +/// offsets: +/// +/// - 0x492c, the word of the 4-byte insert at 26A428 +0x491c: 5000. +/// - 0x4a14 (26A428 0x49fc, which the J714 defaults write as 500): 1600 for every M5. +/// +/// The values are not yet confirmed from captured InitData. +pub(crate) const M5_26B123_POLICY_CONTROLLER_WORDS: &[(usize, u32)] = + &[(0x492c, 5000), (0x4a14, 1600)]; + +/// The 26B123 policy controller input words for the G17D core type (M5 Ultra), at 26B123 +/// offsets: 2500 at 0x4920 and 800 at 0x4928 (26A428 0x4910 and 0x4918, 3500 and 3000 in the J714 +/// defaults), 1100 at 0x492c, 0 at 0x49e4 (26A428 0x49cc, 1 in the J714 defaults) and 1600 at +/// 0x4a14. The values are not yet confirmed from captured InitData. +pub(crate) const M5_26B123_POLICY_CONTROLLER_WORDS_G17D: &[(usize, u32)] = &[ + (0x4920, 2500), + (0x4928, 800), + (0x492c, 1100), + (0x49e4, 0), + (0x4a14, 1600), +]; + +/// The HW-data core type of the G17D (M5 Ultra) parts. +const M5_CORE_TYPE_G17D: u32 = 0x1a; + +/// Where one firmware build keeps the power-policy words the M5 encoders write at their 26A428 +/// offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct M5PolicyMap { + /// Allocation size (entry 4 of the build's allocation size table). + pub(crate) size: usize, + /// 26A428 offset -> this build's offset; empty for 26A428 itself. + pub(crate) bands: &'static [LayoutBand], + /// Words of this build's controller configuration the 26A428 controller defaults do not + /// write, at this build's offsets: for core types with words of their own, then for every + /// other core type. + pub(crate) controller_words_by_core: &'static [(u32, &'static [(usize, u32)])], + pub(crate) controller_words: &'static [(usize, u32)], +} + +impl M5PolicyMap { + /// This build's offset of the 26A428 policy offset `offset`. + pub(crate) const fn relocate(&self, offset: usize) -> Option { + if !self.bands.is_empty() { + return relocate_by_bands(self.bands, offset); + } + if offset < self.size { + Some(offset) + } else { + None + } + } + + /// This build's controller-configuration words for `board`. + fn controller_words_for(&self, board: &Board) -> &'static [(usize, u32)] { + for &(core, words) in self.controller_words_by_core { + if core == board.core_revision.0 { + return words; + } + } + self.controller_words + } +} + +pub(crate) const M5_26A428_POLICY: M5PolicyMap = M5PolicyMap { + size: M5_POLICY_SIZE, + bands: &[], + controller_words_by_core: &[], + controller_words: &[], +}; + +pub(crate) const M5_26B123_POLICY: M5PolicyMap = M5PolicyMap { + size: M5_26B123_POLICY_SIZE, + bands: M5_26B123_POLICY_BANDS, + controller_words_by_core: &[(M5_CORE_TYPE_G17D, M5_26B123_POLICY_CONTROLLER_WORDS_G17D)], + controller_words: M5_26B123_POLICY_CONTROLLER_WORDS, +}; + +/// The power-policy map of an M5 firmware build. 27.2 (26B123) and 27.3 (26C5035r) share one. +pub(crate) const fn m5_policy(layouts: M5Layouts) -> &'static M5PolicyMap { + match layouts { + M5Layouts::V26A428 => &M5_26A428_POLICY, + _ => &M5_26B123_POLICY, + } +} + +/// The power-policy allocation size of an M5 firmware build. +pub(crate) const fn m5_policy_size(layouts: M5Layouts) -> usize { + m5_policy(layouts).size +} + +/// Whether `bands` lay `source` bytes out over `target` bytes in place: contiguous from 0 to +/// `source`, with shifts that never shrink, ending at `target`. +const fn expands_in_place(bands: &[LayoutBand], source: usize, target: usize) -> bool { + let mut index = 0; + let mut end = 0; + let mut delta: isize = 0; + while index < bands.len() { + let band = bands[index]; + if band.start != end || band.end <= band.start || band.delta < delta { + return false; + } + end = band.end; + delta = band.delta; + index += 1; + } + end == source && (end as isize + delta) as usize == target +} + +const _: () = { + assert!(expands_in_place( + M5_26B123_POLICY_BANDS, + M5_POLICY_SIZE, + M5_26B123_POLICY_SIZE + )); + assert!(matches!( + M5_26B123_POLICY.relocate(M5_POLICY_STATE_PTR), + Some(0x8284) + )); + assert!(matches!( + M5_26B123_POLICY.relocate(M5_POLICY_AON_PTD_PTR), + Some(0x11a4c) + )); + assert!(matches!( + M5_26B123_POLICY.relocate(M5_POLICY_MAXIMUM_POWER), + Some(0xa18) + )); +}; + +/// Lays out the power policy the M5 encoders wrote at 26A428 offsets into `policy[..0x126b0]` +/// as `map` says, in place: every band moves, highest first, and the bytes no band covers are +/// zeroed. With `controller_defaults`, the build's controller-configuration words for `board` +/// follow. For 26A428 nothing changes. +pub(crate) fn relayout_m5_policy( + map: &M5PolicyMap, + board: &Board, + controller_defaults: bool, + policy: &mut [u8], +) -> Result<(), InitdataError> { + if policy.len() != map.size { + return Err(InitdataError::BufferSize); + } + if map.bands.is_empty() { + return Ok(()); + } + if !expands_in_place(map.bands, M5_POLICY_SIZE, map.size) { + return Err(InitdataError::BufferSize); + } + // A band's target lies over its own source and the higher bands, which have moved already. + for band in map.bands.iter().rev() { + let target = band + .start + .checked_add_signed(band.delta) + .ok_or(InitdataError::BufferSize)?; + policy.copy_within(band.start..band.end, target); + } + let mut end = 0; + for band in map.bands { + let target = band + .start + .checked_add_signed(band.delta) + .ok_or(InitdataError::BufferSize)?; + policy[end..target].fill(0); + end = target + (band.end - band.start); + } + policy[end..].fill(0); + if controller_defaults { + for &(offset, value) in map.controller_words_for(board) { + if offset + .checked_add(4) + .is_none_or(|word_end| word_end > policy.len()) + { + return Err(InitdataError::BufferSize); + } + put_u32(policy, offset, value); + } + } + Ok(()) +} + pub(crate) const J714_GPU_POWER_SAMPLE_PERIOD_AIC_CLOCKS: u32 = 200_000; pub(crate) const J714_GPU_PERF_BASE_PSTATE: u32 = 1; From e94ca5789b7c7c824ada3f57b7608d6a633276a0 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:48:23 -0500 Subject: [PATCH 06/11] drm/asahi: g17: m5: lay the power policy out for the firmware build The M5 graph's policy encoders (encode_m5_bundle_policy() and the boards' controller defaults) write the 26A428 layout. Keep the build's policy map in InitData, set through set_m5_policy_layouts() from the identified firmware's M5Layouts, and lay the policy out for it in build_m5() once the controller defaults have run: the 26B123 layout moves every word above 0x4910 and adds the 26B123 controller words where the board's defaults applied. The maximum-power word read for region C (0xa18) does not move. The relayout stays within the build's own policy allocation: a policy larger than the space before block_68 of the shared layout is refused. The diagnostic policy read is bounded by the build's policy size. The map defaults to 26A428, so the 26A428 graph is unchanged. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/initdata.rs | 3 +++ drivers/gpu/drm/asahi/g17/initdata/m5.rs | 28 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 2dd5c515b1539..3d113982d6413 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -174,6 +174,8 @@ pub(super) struct InitData { m5: bool, /// The layout of the M5 graph, once [`InitData::build_m5`] has chosen it. m5_layout: &'static super::fw::m5::initdata::M5Layout, + /// Power-policy layout of the M5 firmware build. + m5_policy: &'static crate::g17::fw::m5::initdata::M5PolicyMap, outer_credits: Option>, // Live publication uses interior writes under the device mutex. This owner is Send but not // Sync, so safe host code cannot issue those writes concurrently through shared references. @@ -287,6 +289,7 @@ impl InitData { Self { m5: false, m5_layout: &super::fw::m5::initdata::M5_LAYOUT_26A428, + m5_policy: &crate::g17::fw::m5::initdata::M5_26A428_POLICY, outer_credits: None, _single_publisher: core::marker::PhantomData, aliases: KVec::new(), diff --git a/drivers/gpu/drm/asahi/g17/initdata/m5.rs b/drivers/gpu/drm/asahi/g17/initdata/m5.rs index 19b3725827357..a987ee8a5b0c0 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/m5.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/m5.rs @@ -38,7 +38,16 @@ impl InitData { ring(0), ring(1), ring(2), ring(3), page(canonical.start)] } - /// Diagnostic-only read of an aligned word within the existing policy allocation. + /// Selects the power-policy layout of the M5 firmware build `build_m5` encodes for. + pub(in crate::g17) fn set_m5_policy_layouts( + &mut self, + layouts: crate::g17::fw::variant::M5Layouts, + ) { + self.m5_policy = m::m5_policy(layouts); + } + + /// Diagnostic-only read of an aligned word within the existing policy allocation, at the + /// firmware build's own offset. /// Callers hold the device state mutex, retaining this mapping for the entire sample. pub(crate) fn m5_policy_telemetry_word(&self, offset: usize) -> Result { let layout = self.m5_layout; @@ -199,13 +208,28 @@ impl InitData { calibration.as_ref().is_ok_and(|words| words.as_slice() == expected) } }; - if let Some(defaults) = board.controller_defaults.filter(|_| calibrated) { + let controller_defaults = board.controller_defaults.filter(|_| calibrated); + if let Some(defaults) = controller_defaults { defaults(self.object_mut(self.bundle)?.bytes_mut()).map_err(|_| EINVAL)?; let maximum = self.object(self.bundle)?.word(shared.policy + m::M5_POLICY_MAXIMUM_POWER)? .load(Ordering::Relaxed); m::set_m5_region_maximum_power(self.object_mut(self.region_c)?.bytes_mut(), maximum) .map_err(|_| EINVAL)?; } + // The policy encoders write the 26A428 layout; a build with another one gets it laid out + // in place, within its own allocation. + let policy = self.m5_policy; + let policy_end = shared.policy.checked_add(policy.size).ok_or(EINVAL)?; + if policy_end > shared.block_68 { + return Err(EINVAL); + } + m::relayout_m5_policy( + policy, + board, + controller_defaults.is_some(), + &mut self.object_mut(self.bundle)?.bytes_mut()[shared.policy..policy_end], + ) + .map_err(|_| EINVAL)?; if let Some(microseconds) = board.idle_standby_us { m::set_m5_region_idle_standby(self.object_mut(self.region_c)?.bytes_mut(), microseconds) .map_err(|_| EINVAL)?; From 4655d9f16ba68141da81cd5914ba6cad6bbe90e8 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:28:51 -0500 Subject: [PATCH 07/11] drm/asahi: g17: m5: map the 26B123 (macOS 27.2) status config The 26B123 M5 firmware takes a 0x790-byte status config instead of 0x780 (allocation size table entry 13); 27.3 beta 1 (26C5035r) has the same layout. Everything up to +0x5b4 keeps its offset. From +0x5b4 the object holds two controller blocks, at +0x5b4 and +0x610, of 0x5c bytes each. 26B123 inserts two words into each block, an override slot at block +0x28 and its value at block +0x58, so a block grows to 0x64 bytes, and everything after the second block moves by 0x10: the link word and pointer (0x774/0x778 -> 0x784/0x788) and the submission history (0x73c/0x74c/0x750 -> 0x74c/0x75c/0x760). As bands in 26A428 offsets: [0, 0x5dc) +0, [0x5dc, 0x608) +4, [0x608, 0x638) +8, [0x638, 0x664) +0xc, [0x664, 0x780) +0x10. The new words are 0x5dc and 0x640 (override slots, zero-filled) and 0x60c (5000) and 0x670 (0), the values. The layout and these values are not yet confirmed from captured InitData. Add M5StatusConfigMap with the 26A428 identity map and the 26B123 map, encode the primary status-B span through it, and read the submission history through the map the graph was built with. The 26A428 status config is unchanged: the map path writes the same 0x780 bytes as write_relocated_words() with COMMON26_STATUS_CONFIG_BANDS. The 26A428 words the driver writes from 0x5a4 are not changed here; the 26B123 map moves them like every other word. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 153 +++++++++++++++++++- drivers/gpu/drm/asahi/g17/initdata.rs | 3 + drivers/gpu/drm/asahi/g17/initdata/m5.rs | 17 ++- 3 files changed, 161 insertions(+), 12 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index da543f71fb317..893bbacc3d050 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -1466,6 +1466,32 @@ pub(crate) const COMMON26_STATUS_CONFIG_BANDS: &[LayoutBand] = &[ band(0x5cc, 0x628, 0x18, Evidence::ProducerChecked), ]; +/// Status config 26A428 -> 26B123 (macOS 27.2; 27.3 beta 1 has the same layout), in 26A428 +/// offsets. The status config grows from 0x780 to 0x790 bytes (allocation size table entry 13). +/// +/// From +0x5b4 the object holds two controller blocks, at +0x5b4 and +0x610, of 0x5c bytes +/// each. 26B123 inserts two words into each block, an override slot at block +0x28 and its +/// value at block +0x58, so a block grows to 0x64 bytes and everything after the second one +/// moves by 0x10, the link word and pointer (0x774/0x778 -> 0x784/0x788) and the submission +/// history (0x73c/0x74c/0x750 -> 0x74c/0x75c/0x760) included. The bands are +/// not yet confirmed from captured InitData. +pub(crate) const M5_26B123_STATUS_CONFIG_BANDS: &[LayoutBand] = &[ + band(0x000, 0x5dc, 0, Evidence::Inferred), + band(0x5dc, 0x608, 0x4, Evidence::Inferred), + band(0x608, 0x638, 0x8, Evidence::Inferred), + band(0x638, 0x664, 0xc, Evidence::Inferred), + band(0x664, M5_STATUS_CONFIG_SIZE, 0x10, Evidence::Inferred), +]; + +/// Status-config size of 26B123 (entry 13 of its allocation size table). +pub(crate) const M5_26B123_STATUS_CONFIG_SIZE: usize = 0x790; + +/// The words 26B123 has and 26A428 does not, at 26B123 offsets: the override slot of each +/// controller block (0x5dc and 0x640, zero) and its value (0x60c = 5000 and 0x670 = 0). The +/// values are not yet confirmed from captured InitData. +pub(crate) const M5_26B123_STATUS_CONFIG_INSERTED: &[(usize, u32)] = + &[(0x5dc, 0), (0x60c, 5000), (0x640, 0), (0x670, 0)]; + fn write_relocated_words( words: &[(usize, u32)], source_base: usize, @@ -1977,6 +2003,50 @@ pub(crate) const M5_STATUS_CONFIG_LINK_WORD: usize = 0x774; pub(crate) const M5_STATUS_CONFIG_LINK_PTR: usize = 0x778; +/// Submission-history period and slot count (two u64), its generation word and its per-slot +/// bytes in the status config, at 26A428 offsets. +pub(crate) const M5_STATUS_CONFIG_HISTORY: usize = 0x73c; +pub(crate) const M5_STATUS_CONFIG_HISTORY_GENERATION: usize = 0x74c; +pub(crate) const M5_STATUS_CONFIG_HISTORY_SLOTS: usize = 0x750; + +/// Where one firmware build keeps the status-config fields the driver knows by their 26A428 +/// offsets. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct M5StatusConfigMap { + /// Allocation size (entry 13 of the build's allocation size table). + pub(crate) size: usize, + /// 26A428 offset -> this build's offset; empty for 26A428 itself. + pub(crate) bands: &'static [LayoutBand], + /// Words this build has and 26A428 does not, at this build's offsets. + pub(crate) inserted: &'static [(usize, u32)], +} + +impl M5StatusConfigMap { + /// This build's offset of the 26A428 status-config offset `offset`. + pub(crate) const fn relocate(&self, offset: usize) -> Option { + if !self.bands.is_empty() { + return relocate_by_bands(self.bands, offset); + } + if offset < self.size { + Some(offset) + } else { + None + } + } +} + +pub(crate) const M5_26A428_STATUS_CONFIG: M5StatusConfigMap = M5StatusConfigMap { + size: M5_STATUS_CONFIG_SIZE, + bands: &[], + inserted: &[], +}; + +pub(crate) const M5_26B123_STATUS_CONFIG: M5StatusConfigMap = M5StatusConfigMap { + size: M5_26B123_STATUS_CONFIG_SIZE, + bands: M5_26B123_STATUS_CONFIG_BANDS, + inserted: M5_26B123_STATUS_CONFIG_INSERTED, +}; + pub(crate) const M5_HW_DATA_INSERT: usize = 0x267c; pub(crate) const M5_HW_DATA_INSERT_LEN: usize = 8; @@ -2879,23 +2949,90 @@ pub(crate) fn encode_m5_primary_status_b( if out.len() != layout.status_b_span() { return Err(InitdataError::BufferSize); } + encode_m5_primary_status_b_for( + &M5_26A428_STATUS_CONFIG, + layout.status_config_in_status_b(), + fwctl_state, + fwctl_ring, + status_b, + out, + ) +} + +/// Encodes the primary status-B span (`out`, from status B to the primary status A) of a build +/// whose status config sits at `config` in the span and is laid out as `map` says. +pub(crate) fn encode_m5_primary_status_b_for( + map: &M5StatusConfigMap, + config: usize, + fwctl_state: u64, + fwctl_ring: u64, + status_b: u64, + out: &mut [u8], +) -> Result<(), InitdataError> { + let end = config + .checked_add(map.size) + .ok_or(InitdataError::BufferSize)?; + if end > out.len() { + return Err(InitdataError::BufferSize); + } + let link_word = map + .relocate(M5_STATUS_CONFIG_LINK_WORD) + .ok_or(InitdataError::BufferSize)?; + let link_ptr = map + .relocate(M5_STATUS_CONFIG_LINK_PTR) + .ok_or(InitdataError::BufferSize)?; required_address(fwctl_state)?; required_address(fwctl_ring)?; required_address(status_b)?; - let config = layout.status_config_in_status_b(); out.fill(0); encode_status_block(&mut out[..STATUS_BLOCK_SIZE])?; put_u64(out, COMMON26_STATUS_B_FWCTL_STATE, fwctl_state); put_u64(out, COMMON26_STATUS_B_FWCTL_RING, fwctl_ring); put_u32(out, COMMON26_STATUS_B_CONFIG_HEADER, 1); put_u32(out, COMMON26_STATUS_B_LINK_WORD, 0); - put_u64(out, COMMON26_STATUS_B_LINK_PTR, - status_b + (config + M5_STATUS_CONFIG_LINK_WORD) as u64); - write_relocated_words(M5_25G76_STATUS_CONFIG_WORDS, 0, COMMON26_STATUS_CONFIG_BANDS, - &mut out[config..config + layout.status_config_size()])?; - put_u32(out, config + M5_STATUS_CONFIG_LINK_WORD, 1); - put_u64(out, config + M5_STATUS_CONFIG_LINK_PTR, - status_b + COMMON26_STATUS_B_LINK_WORD as u64); + put_u64( + out, + COMMON26_STATUS_B_LINK_PTR, + status_b + (config + link_word) as u64, + ); + write_m5_status_config_words(map, &mut out[config..end])?; + put_u32(out, config + link_word, 1); + put_u64( + out, + config + link_ptr, + status_b + COMMON26_STATUS_B_LINK_WORD as u64, + ); + Ok(()) +} + +/// Writes the 25G76 status-config words through the 26A428 bands and then `map`, followed by +/// the words the build inserted. For 26A428 this is +/// `write_relocated_words(M5_25G76_STATUS_CONFIG_WORDS, 0, COMMON26_STATUS_CONFIG_BANDS, out)`. +fn write_m5_status_config_words( + map: &M5StatusConfigMap, + out: &mut [u8], +) -> Result<(), InitdataError> { + if out.len() != map.size { + return Err(InitdataError::BufferSize); + } + for &(offset, value) in M5_25G76_STATUS_CONFIG_WORDS { + for (index, byte) in value.to_le_bytes().into_iter().enumerate() { + if byte == 0 { + continue; + } + let target = relocate_by_bands(COMMON26_STATUS_CONFIG_BANDS, offset + index) + .and_then(|source| map.relocate(source)); + if let Some(target) = target { + *out.get_mut(target).ok_or(InitdataError::BufferSize)? = byte; + } + } + } + for &(offset, value) in map.inserted { + if offset.checked_add(4).is_none_or(|end| end > out.len()) { + return Err(InitdataError::BufferSize); + } + put_u32(out, offset, value); + } Ok(()) } diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 3d113982d6413..c0aa7b7308fd2 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -176,6 +176,8 @@ pub(super) struct InitData { m5_layout: &'static super::fw::m5::initdata::M5Layout, /// Power-policy layout of the M5 firmware build. m5_policy: &'static crate::g17::fw::m5::initdata::M5PolicyMap, + /// Status-config layout of the M5 firmware build. + m5_status_config: &'static crate::g17::fw::m5::initdata::M5StatusConfigMap, outer_credits: Option>, // Live publication uses interior writes under the device mutex. This owner is Send but not // Sync, so safe host code cannot issue those writes concurrently through shared references. @@ -290,6 +292,7 @@ impl InitData { m5: false, m5_layout: &super::fw::m5::initdata::M5_LAYOUT_26A428, m5_policy: &crate::g17::fw::m5::initdata::M5_26A428_POLICY, + m5_status_config: &crate::g17::fw::m5::initdata::M5_26A428_STATUS_CONFIG, outer_credits: None, _single_publisher: core::marker::PhantomData, aliases: KVec::new(), diff --git a/drivers/gpu/drm/asahi/g17/initdata/m5.rs b/drivers/gpu/drm/asahi/g17/initdata/m5.rs index a987ee8a5b0c0..644ca596a8004 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/m5.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/m5.rs @@ -63,12 +63,21 @@ impl InitData { if !self.m5 { return Err(EINVAL); } let private = self.object(self.cluster)?; let base = self.m5_layout.private.status_config; + let field = |offset| { + self.m5_status_config + .relocate(offset) + .map(|at| base + at) + .ok_or(EINVAL) + }; + let history = field(m::M5_STATUS_CONFIG_HISTORY)?; + let generation_at = field(m::M5_STATUS_CONFIG_HISTORY_GENERATION)?; + let slots = field(m::M5_STATUS_CONFIG_HISTORY_SLOTS)?; for _ in 0..64 { - let generation = private.word(base + 0x74c)?.load(Ordering::Acquire); + let generation = private.word(generation_at)?.load(Ordering::Acquire); if generation & 1 != 0 { core::hint::spin_loop(); continue; } - let config = private.read_words::<4>(base + 0x73c)?; + let config = private.read_words::<4>(history)?; fence(Ordering::Acquire); - if generation != private.word(base + 0x74c)?.load(Ordering::Acquire) { + if generation != private.word(generation_at)?.load(Ordering::Acquire) { core::hint::spin_loop(); continue; } let period = u64::from(config[0]) | u64::from(config[1]) << 32; @@ -79,7 +88,7 @@ impl InitData { // One slot per 25000 ticks of the period. let index = (ticks % period) / 25_000; if index >= count { return Err(ERANGE); } - let byte = private.pointer(base + 0x750 + index as usize, 1)?; + let byte = private.pointer(slots + index as usize, 1)?; // SAFETY: retained, bounds-checked WC byte, written as a single byte // after reading the even, stable generation, as the firmware expects. unsafe { From bac98090845a4589329edceab82dcbd8d856d4e8 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:29:59 -0500 Subject: [PATCH 08/11] drm/asahi: g17: m5: choose the status-config map from the root profile Give RootProfile the status-config map of its M5 graph and have build_m5() pick the profile before it encodes the private cluster, so the status-B span, the link word and pointer and the submission-history reader all follow the profile's layout. Every existing profile keeps the 26A428 map, so the graph is unchanged. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 30 +++++++++++++++++---- drivers/gpu/drm/asahi/g17/initdata/m5.rs | 3 ++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index 893bbacc3d050..45bcd5ba2223e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -156,6 +156,18 @@ impl RootProfile { _ => WORK_RING_BUNDLE_OFFSETS, } } + + /// Status-config layout of the M5 graph built for this profile. + pub(crate) const fn m5_status_config(self) -> &'static M5StatusConfigMap { + match self { + Self::J714G17S26A428 + | Self::J714G17S26B123 + | Self::J714G17S26C5035 + | Self::J700G17P + | Self::J813G17G25G76 + | Self::J873G18G26A428 => &M5_26A428_STATUS_CONFIG, + } + } } const INITDATA_ADDR_MASK: u64 = 0x0000_0fff_ffff_ffff; @@ -3037,12 +3049,15 @@ fn write_m5_status_config_words( } pub(crate) fn encode_m5_private_cluster( - m5_layout: &M5Layout, + profile: RootProfile, private_base: u64, fwctl_state: u64, out: &mut [u8], ) -> Result<(), InitdataError> { - let layout = m5_layout.private; + let layout = profile + .m5_layout() + .ok_or(InitdataError::InterfaceMismatch)? + .private; if out.len() != layout.total { return Err(InitdataError::BufferSize); } @@ -3062,9 +3077,14 @@ pub(crate) fn encode_m5_private_cluster( out.fill(0); let status_b = layout.status_b; let status_a = layout.roles[0].status_a; - encode_m5_primary_status_b(m5_layout, fwctl_state, fwctl_ring, - private_base + status_b as u64, - &mut out[status_b..status_a])?; + encode_m5_primary_status_b_for( + profile.m5_status_config(), + layout.status_config - status_b, + fwctl_state, + fwctl_ring, + private_base + status_b as u64, + &mut out[status_b..status_a], + )?; for role in layout.roles { encode_status_block(&mut out[role.status_a..role.status_a + STATUS_BLOCK_SIZE])?; } diff --git a/drivers/gpu/drm/asahi/g17/initdata/m5.rs b/drivers/gpu/drm/asahi/g17/initdata/m5.rs index 644ca596a8004..07d136bfd6704 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/m5.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/m5.rs @@ -166,7 +166,8 @@ impl InitData { m::encode_common26_aon_ptd(board, &mut self.object_mut(aon)?.bytes_mut()[..m::M5_AON_PTD_SIZE]) .map_err(|_| EINVAL)?; m::encode_m5_region_c(self.object_mut(self.region_c)?.bytes_mut()).map_err(|_| EINVAL)?; - m::encode_m5_private_cluster(layout, PRIVATE, FWCTL, + self.m5_status_config = profile.m5_status_config(); + m::encode_m5_private_cluster(profile, PRIVATE, FWCTL, &mut self.object_mut(self.cluster)?.bytes_mut()[..private.total]).map_err(|_| EINVAL)?; let windows = board.register_windows(); let windows = &windows[..board.register_window_count()]; From 91082437193f5f17df91c3ed7a73fc2135ca5004 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:53:22 -0500 Subject: [PATCH 09/11] drm/asahi: g17: m5: use the 26B123 status config for the 27.2 and 27.3 profiles The 26B123 (macOS 27.2) firmware takes the 0x790-byte status config: two words more in each controller block and the tail, link word and submission history 0x10 higher. Return M5_26B123_STATUS_CONFIG for the 26B123 root profile, and for the 26C5035 (macOS 27.3 beta 1) profile, whose status config is the same as 26B123's. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index 45bcd5ba2223e..21903848c5139 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -160,12 +160,11 @@ impl RootProfile { /// Status-config layout of the M5 graph built for this profile. pub(crate) const fn m5_status_config(self) -> &'static M5StatusConfigMap { match self { - Self::J714G17S26A428 - | Self::J714G17S26B123 - | Self::J714G17S26C5035 - | Self::J700G17P - | Self::J813G17G25G76 - | Self::J873G18G26A428 => &M5_26A428_STATUS_CONFIG, + Self::J714G17S26B123 => &M5_26B123_STATUS_CONFIG, + Self::J714G17S26C5035 => &M5_26B123_STATUS_CONFIG, + Self::J714G17S26A428 | Self::J700G17P | Self::J813G17G25G76 | Self::J873G18G26A428 => { + &M5_26A428_STATUS_CONFIG + } } } } From d8ca53c82e5273fe8f49d9cf13720ea91b946bc8 Mon Sep 17 00:00:00 2001 From: Rolando Trevino Date: Sat, 7 Nov 2026 12:55:22 -0500 Subject: [PATCH 10/11] drm/asahi: g17: m5: take the policy and status-config layouts from the firmware build build_m5() now selects the power-policy map from the same M5 layouts the root profile comes from, so a 26B123 or 26C5035 firmware gets its policy laid out to 0x12728 bytes and its 0x790-byte status config in the same graph. The policy and status-config sizes of 26B123 are entries 4 and 13 of its allocation size table, and a compile-time check holds every M5 build's policy and status-config maps to its own size table, with the policy at the offset the controller defaults write it. The controller defaults keep writing the 26A428 policy, which the relayout then moves; name that slice once. The private cluster encoder takes the root profile and derives both the cluster layout and the status-config map from it, which leaves the 26A428-only status-B wrapper without callers. With every 27.2+ layout in place, Gpu::new() now refuses only a build that names no M5 layouts, so the M5 Pro's 27.2 (26B123) and 27.3 beta 1 (26C5035r) builds run their graphs. 26A428 builds, the only ones the M5 Max and M5 Ultra identify, take the identity maps and build the same bytes as before this series. The 27.2+ layouts and values of this series are not yet confirmed from captured InitData; the comments say which. What has been observed is the following. An earlier revision of this series, with J716s board tables and a native-address mailbox compatible that are not part of this one, ran on a J716s (16-inch M5 Pro) with the 27.3 beta 1 boot firmware, a device tree that describes the GPU and a loader that hands it over (none of them part of this series): aurora 2300000000.gpu: GPU firmware: 27.3 (26C5035r), G17S aurora 2300000000.gpu: RTKit: Initializing (protocol version 12) aurora 2300000000.gpu: M5 primary initdata acknowledged aurora 2300000000.gpu: M5 secondary initdata acknowledged aurora 2300000000.gpu: M5 firmware opening records retired [drm] Initialized aurora 1.0.0 for 2300000000.gpu on minor 1 with no coprocessor crash, SError, DART or UAT fault for the minutes the system ran, and later through a 25-minute desktop session on the GPU with no GPU errors. That shows the firmware accepting a graph built from these layouts; it does not confirm any single value. Not exercised: the 27.2 firmware on hardware, the forced-idle sleep path, the policy controller words (that board wrote no controller defaults), the M5 Max and M5 Ultra on 27.2+. Signed-off-by: Rolando Trevino Assisted-by: LLM Claude --- drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs | 78 +++++++++++++-------- drivers/gpu/drm/asahi/g17/fw/variant.rs | 4 +- drivers/gpu/drm/asahi/g17/initdata/m5.rs | 4 +- drivers/gpu/drm/asahi/g17/mod.rs | 6 +- 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs index 21903848c5139..761624e3fcf09 100644 --- a/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/m5/initdata.rs @@ -1495,7 +1495,8 @@ pub(crate) const M5_26B123_STATUS_CONFIG_BANDS: &[LayoutBand] = &[ ]; /// Status-config size of 26B123 (entry 13 of its allocation size table). -pub(crate) const M5_26B123_STATUS_CONFIG_SIZE: usize = 0x790; +pub(crate) const M5_26B123_STATUS_CONFIG_SIZE: usize = + ABI_ALLOCATION_SIZES_26B123_M5[ALLOC_STATUS_CONFIG]; /// The words 26B123 has and 26A428 does not, at 26B123 offsets: the override slot of each /// controller block (0x5dc and 0x640, zero) and its value (0x60c = 5000 and 0x670 = 0). The @@ -1914,10 +1915,41 @@ const _: () = { assert!(M5_LAYOUT_26B123.private.total == M5_LAYOUT_26A428.private.total); // The kick-shared pointer and the last word the 26C5035r layout moves. let kick = M5_KICK_SHARED_POINTER; - assert!(matches!(M5_LAYOUT_26A428.hw_data_offset(kick), Some(0x27dc))); - assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(kick), Some(0x27e0))); - assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(0x2846), Some(0x284a))); - assert!(matches!(M5_LAYOUT_26C5035.hw_data_offset(0x271c), Some(0x271c))); + assert!(matches!( + M5_LAYOUT_26A428.hw_data_offset(kick), + Some(0x27dc) + )); + assert!(matches!( + M5_LAYOUT_26C5035.hw_data_offset(kick), + Some(0x27e0) + )); + assert!(matches!( + M5_LAYOUT_26C5035.hw_data_offset(0x2846), + Some(0x284a) + )); + assert!(matches!( + M5_LAYOUT_26C5035.hw_data_offset(0x271c), + Some(0x271c) + )); +}; + +const _: () = { + use super::super::variant::M5Layouts; + // Every M5 build's policy map and status-config map fill the allocations of its size table, + // and the policy starts where the encoders write its 26A428 form. + let builds = [M5Layouts::V26A428, M5Layouts::V26B123, M5Layouts::V26C5035]; + let mut index = 0; + while index < builds.len() { + let profile = RootProfile::for_m5_layouts(builds[index]); + let layout = match profile.m5_layout() { + Some(layout) => layout, + None => panic!("an M5 build without an M5 layout"), + }; + assert!(m5_policy_size(builds[index]) == layout.policy_size()); + assert!(profile.m5_status_config().size == layout.status_config_size()); + assert!(layout.shared.policy == M5_SHARED_LAYOUT.policy); + index += 1; + } }; pub(crate) const M5_SHARED_LAYOUT: SharedClusterLayout = M5_LAYOUT_26A428.shared; @@ -2256,7 +2288,7 @@ pub(crate) const M5_26B123_POLICY_BANDS: &[LayoutBand] = &[ ]; /// Power-policy size of 26B123 (entry 4 of its allocation size table). -pub(crate) const M5_26B123_POLICY_SIZE: usize = 0x12728; +pub(crate) const M5_26B123_POLICY_SIZE: usize = ABI_ALLOCATION_SIZES_26B123_M5[ALLOC_POLICY]; /// The 26B123 policy controller input words the 26A428 controller defaults do not write, at 26B123 /// offsets: @@ -2950,26 +2982,6 @@ pub(crate) fn encode_m5_region_c(out: &mut [u8]) -> Result<(), InitdataError> { Ok(()) } -pub(crate) fn encode_m5_primary_status_b( - layout: &M5Layout, - fwctl_state: u64, - fwctl_ring: u64, - status_b: u64, - out: &mut [u8], -) -> Result<(), InitdataError> { - if out.len() != layout.status_b_span() { - return Err(InitdataError::BufferSize); - } - encode_m5_primary_status_b_for( - &M5_26A428_STATUS_CONFIG, - layout.status_config_in_status_b(), - fwctl_state, - fwctl_ring, - status_b, - out, - ) -} - /// Encodes the primary status-B span (`out`, from status B to the primary status A) of a build /// whose status config sits at `config` in the span and is laid out as `map` says. pub(crate) fn encode_m5_primary_status_b_for( @@ -3156,6 +3168,14 @@ pub(crate) fn encode_m5_bundle_policy( Ok(()) } +/// The power policy in `out` (a bundle) in its 26A428 form, which the controller defaults write +/// and [`relayout_m5_policy`] then lays out for the firmware build. Every M5 layout starts the +/// policy at the same offset. +fn m5_policy_source(out: &mut [u8]) -> Result<&mut [u8], InitdataError> { + out.get_mut(M5_SHARED_LAYOUT.policy..M5_SHARED_LAYOUT.policy + M5_POLICY_SIZE) + .ok_or(InitdataError::BufferSize) +} + /// Initial controller inputs for J714 with the matched calibration. pub(crate) fn encode_m5_policy_controller_defaults(out: &mut [u8]) -> Result<(), InitdataError> { if out.len() < M5_SHARED_PREFIX_SIZE { return Err(InitdataError::BufferSize); } @@ -3174,7 +3194,7 @@ pub(crate) fn encode_m5_policy_controller_defaults(out: &mut [u8]) -> Result<(), 0x42b6ec22, 0x42c80000, 0x00000000, 0x00000000, ]; for (index, &value) in RELATIVE_POWER_AUX.iter().enumerate() { put_u32(out, 0x1a64 + index * 4, value); } - let policy = &mut out[M5_SHARED_LAYOUT.policy..M5_SHARED_LAYOUT.policy + M5_POLICY_SIZE]; + let policy = m5_policy_source(out)?; const INITIAL: &[(usize, u32)] = &[ (0xd4, 0x3f828f5c), (0xd8, 0x3f828f5c), @@ -3425,7 +3445,7 @@ pub(crate) fn encode_m5_policy_controller_defaults(out: &mut [u8]) -> Result<(), /// tags. pub(crate) fn encode_m5_policy_controller_defaults_j716c(out: &mut [u8]) -> Result<(), InitdataError> { encode_m5_policy_controller_defaults(out)?; - let policy = &mut out[M5_SHARED_LAYOUT.policy..M5_SHARED_LAYOUT.policy + M5_POLICY_SIZE]; + let policy = m5_policy_source(out)?; const MAXIMUM_POWER: u32 = 196_098; const MAXIMUM_POWER_F32: u32 = 0x483f_8080; for offset in [M5_POLICY_MAXIMUM_POWER, 0xad0, 0x44a4, 0x44a8, 0x44ac, 0x45e4] { @@ -3464,7 +3484,7 @@ pub(crate) fn encode_m5_policy_controller_defaults_j775d(out: &mut [u8]) -> Resu 0x421c_12dd, 0x4230_6915, 0x4250_6a36, 0x427d_e40c, 0x4294_0cc0, 0x42bd_bd8e, 0x42c8_0000, ]; for (index, &value) in RELATIVE_POWER_AUX.iter().enumerate() { put_u32(out, 0x1a64 + index * 4, value); } - let policy = &mut out[M5_SHARED_LAYOUT.policy..M5_SHARED_LAYOUT.policy + M5_POLICY_SIZE]; + let policy = m5_policy_source(out)?; const MAXIMUM_POWER: u32 = 345_262; const MAXIMUM_POWER_F32: u32 = 0x48a8_95c0; for offset in [M5_POLICY_MAXIMUM_POWER, 0xad0, 0x44a4, 0x44a8, 0x44ac, 0x45e4] { diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 837ef562f7e3d..1e7489ad4a88f 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -37,7 +37,9 @@ pub(crate) enum InitGraph { } /// The InitData layouts of an M5 firmware build ([`super::m5::initdata`]). The 27.2 and later -/// layouts are not yet confirmed from captured InitData. +/// layouts are not yet confirmed from captured InitData. Observed so far: the 27.3 beta 1 +/// firmware (26C5035r) on a J716s acknowledged the primary and secondary InitData built with the +/// `V26C5035` layouts and ran without a coprocessor fault; the 27.2 firmware has not run. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum M5Layouts { /// The 26A428 graph: version word 0x0c8e_0339_8728_04c0 and its allocation sizes. diff --git a/drivers/gpu/drm/asahi/g17/initdata/m5.rs b/drivers/gpu/drm/asahi/g17/initdata/m5.rs index 07d136bfd6704..4947fec911120 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/m5.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/m5.rs @@ -112,10 +112,12 @@ impl InitData { ) -> Result { use mmu::{PROT_FW_SHARED_RW as RW, PROT_FW_SHARED_RO as RO, PROT_GPU_SHARED_RW as GPU}; - let profile = m::RootProfile::for_m5_layouts(variant.m5_layouts.ok_or(EINVAL)?); + let layouts = variant.m5_layouts.ok_or(EINVAL)?; + let profile = m::RootProfile::for_m5_layouts(layouts); let layout = profile.m5_layout().ok_or(EINVAL)?; self.m5 = true; self.m5_layout = layout; + self.set_m5_policy_layouts(layouts); // TA, fragment and compute share the compute work ring per priority; every // preparation's claim on it is counted from the first publication on. self.outer_credits = Some(super::outer_credit::Credits::new()?); diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 2ed19e4fc64d9..5b3b0d08c9a1b 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1488,9 +1488,9 @@ impl Gpu { } let m5 = cfg.profile.is_m5(); if m5 { - // The M5 graph builds only the 26A428 InitData layouts. A firmware that checks - // another layout is refused here, before anything is written to the GPU. - if variant.m5_layouts != Some(fw::variant::M5Layouts::V26A428) { + // The M5 graph builds the InitData layouts the firmware build names. A build that + // names none is refused here, before anything is written to the GPU. + if variant.m5_layouts.is_none() { dev_err!( dev.as_ref(), "No M5 InitData layouts for this GPU firmware build ({:?})\n", From 4ac2c69dd0b8298e0cddac8b8515c4138587d54e Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Wed, 11 Nov 2026 12:00:39 -0700 Subject: [PATCH 11/11] drm/asahi: t6050: identify the 27.2 (26B8125) G17S firmware of the K114/K116 Applies on top of aurora-silicon/linux PR #1173. The 16-inch M5 Pro OLED (K116s, Mac17,12) restores from UniversalMac_27.2_26B8125_Restore.ipsw, not from the 26B123 image of the Mac17,8 that #1173 pins. Its Firmware/agx/armfw_g17x.im4p and armfw1_g17x.im4p carry a010 (G17S) images whose LC_UUIDs are the uuid of the K116s ADT's iop-gfx-nub and iop-gfx1-nub (8A2891FC-D479-37E8-895D- 71B4D88ECB51, 58FF964E-F74B-31C2-8179-3837D6ADA9C2), so this is what iBoot loads. TEXT 0x50000 + 0x54000, DATA 0x184000, the GKTS/ECAP records and the _rtk_tunables/_rtk_patchbay sections at 26B123's offsets, but the fwid digest differs: 26B8125 G17S af68d034a3f006b43dab661b825f12598cb86e4d2a0e3844f14fa5059f38f6cb 26B123 G17S 2b4613c62a8a007f22a94101702c9ac6c4e90f2e123f3b36fbe6ffea913f0fd9 (check_fwid.py's algorithm.) Without this entry the driver refuses the K116s firmware before any GPU write. It names V26B123_M5: the AGX kext of the 26B8125 kernelcache builds root version word 0x0c8e_4339_8728_04c0 and its u64[24] allocation size table is ABI_ALLOCATION_SIZES_26B123_M5 entry for entry (HW data 0x2850, policy 0x12728, status config 0x790), and both firmware roles build the same version word. The interior offsets of the policy and the status config were not compared against 26B123's and no 27.2 firmware has run this graph on hardware. Signed-off-by: Jens Axboe --- drivers/gpu/drm/asahi/hw/t6050.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/gpu/drm/asahi/hw/t6050.rs b/drivers/gpu/drm/asahi/hw/t6050.rs index 686e68f3c080b..87c856f0d5fe0 100644 --- a/drivers/gpu/drm/asahi/hw/t6050.rs +++ b/drivers/gpu/drm/asahi/hw/t6050.rs @@ -75,6 +75,37 @@ pub(crate) const CONFIG: Config = Config { variant: &crate::g17::fw::variant::V26C5035_M5, config: None, }, + // The 27.2 build of the Mac17,10 to Mac17,13 restore image (K114, K116), which iBoot + // loads on the K116s: the a010 images whose Mach-O UUIDs are the ADT's iop-gfx-nub and + // iop-gfx1-nub uuid. Its records and sections sit where 26B123's do; its kext builds the + // 26B123 version word and allocation sizes. + crate::g17::fw::variant::Build { + os_version: [27, 2, 0], + name: "27.2 (26B8125), G17S", + images: &[crate::fwid::FirmwareBuild { + name: "26B8125 for T6050 G17S, primary then secondary", + text_size: 0xa4000, + data_size: 0x184000, + entropy: &[[0x49ea4, 0x49f87], [0x9d92c, 0x9da0f]], + boot_filled: &[ + (0x224, 4), + (0x23c, 8), + (0x49764, 0x740), + (0x49ea4, 0x231), + (0x50224, 4), + (0x5023c, 8), + (0x9d1ec, 0x740), + (0x9d92c, 0x231), + ], + text_sha256: [ + 0xaf, 0x68, 0xd0, 0x34, 0xa3, 0xf0, 0x06, 0xb4, 0x3d, 0xab, 0x66, 0x1b, 0x82, + 0x5f, 0x12, 0x59, 0x8c, 0xb8, 0x6e, 0x4d, 0x2a, 0x0e, 0x38, 0x44, 0xf1, 0x4f, + 0xa5, 0x05, 0x9f, 0x38, 0xf6, 0xcb, + ], + }], + variant: &crate::g17::fw::variant::V26B123_M5, + config: None, + }, crate::g17::fw::variant::Build { os_version: [27, 2, 0], name: "27.2 (26B123), G17S",