From aeffd0e53b4218633bd1f35fcdfe9e27188a5922 Mon Sep 17 00:00:00 2001 From: Naeem Malik Date: Wed, 28 Oct 2026 04:06:37 +0530 Subject: [PATCH 1/3] drm/asahi: g15: t6031: build the J516C board data without a board file The J516C hardware record took 32 words from asahi/t6031-j516c-board.bin, a per-machine file that installs do not ship, and the driver compared the three images it built against the file before starting the firmware. A J516C without the file could not start the GPU. Take those 32 words from sources every J516C has, as the T6030 path does with its templates: - The six GPU sensor rows (18 words) are entries of the ADT's /arm-io/aon-ptd ptd_data_table, which the boot loader already exports (the phram "adt" region the T6030 path reads): the "RT" entries of kind g (four), r and f. The device tree does not carry the table. Any other count is refused. - The maximum power, the relative powers of DVFS states 1..7 and the MTR fast-die and alarm masks (14 words) are not in the device tree or the ADT. They become the J516C reference machine's values (board::record::J516C), as the T6030 templates carry their reference machine's. The device tree's operating-point powers stay unused; they are a boot-loader stand-in. hw::Board::Blob becomes hw::Board::DeviceTree(&Reference): no firmware request, no file digest, no file cross-check. The firmware identity pins and the apple,j516c machine match are unchanged. board/blob.rs and the RUST_FW_LOADER_ABSTRACTIONS select go. The off-tree J516C fixture test now builds the record from a saved device tree and ADT with the reference words and checks it against the machine's own record: byte-identical except the 42 words shown unneeded earlier. g15-zero-words-tests.py follows the new sources (14 reference + 18 sensor row + 42 zero words = the original 74); its hw.rs and prepare_board() pins are refreshed for the Board change, the T6030 arms are unchanged. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Naeem Malik --- drivers/gpu/drm/asahi/Kconfig | 1 - drivers/gpu/drm/asahi/g15/adt.rs | 57 +++- drivers/gpu/drm/asahi/g15/board.rs | 35 +- drivers/gpu/drm/asahi/g15/board/blob.rs | 113 ------ drivers/gpu/drm/asahi/g15/board/record.rs | 397 ++++++++++++++-------- drivers/gpu/drm/asahi/g15/board/words.rs | 5 +- drivers/gpu/drm/asahi/g15/hw.rs | 14 +- drivers/gpu/drm/asahi/g15/hw/t6031.rs | 24 +- drivers/gpu/drm/asahi/g15/initdata.rs | 63 +--- tools/asahi/g15-zero-words-tests.py | 91 +++-- 10 files changed, 398 insertions(+), 402 deletions(-) delete mode 100644 drivers/gpu/drm/asahi/g15/board/blob.rs diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig index 45e907b5af86de..0e989189206bad 100644 --- a/drivers/gpu/drm/asahi/Kconfig +++ b/drivers/gpu/drm/asahi/Kconfig @@ -68,7 +68,6 @@ config DRM_ASAHI_G15 depends on DRM_APPLE=y || (DRM_APPLE=m && DRM_ASAHI=m) select APPLE_T6030_GPU_PMP_LINK select CRYPTO_LIB_SHA256 - select RUST_FW_LOADER_ABSTRACTIONS select DRM_ASAHI_UAT_ADOPTION default n help diff --git a/drivers/gpu/drm/asahi/g15/adt.rs b/drivers/gpu/drm/asahi/g15/adt.rs index aece4f083c0ad4..83807d985866a8 100644 --- a/drivers/gpu/drm/asahi/g15/adt.rs +++ b/drivers/gpu/drm/asahi/g15/adt.rs @@ -30,10 +30,13 @@ //! one cell per cluster). The same admission applies, without the DRAM bounds, and the four UAT //! regions may lie in System RAM: they are reserved-memory nodes without `no-map`, which the //! kernel maps, so the driver reaches them through its own mapping ([`super::regs::Mapping`]). +//! The GPU's sensor rows, which the node does not carry, come from the same ADT export as on +//! T6030: `/arm-io/aon-ptd` `ptd_data_table` ([`super::board::record::sensor_rows`]). use core::ops::Range; use kernel::{c_str, device, prelude::*}; +use super::board::record; use super::boot::glue; use super::hw; @@ -57,12 +60,13 @@ pub(crate) struct GpuAdt { pub(crate) perf_mhz: [u32; PERF_STATES], pub(crate) perf_mv: [u32; PERF_STATES], pub(crate) sram_mv: [u32; PERF_STATES], - /// Per-cluster core mV of each state, rounded up from the device tree's microvolts, how - /// many voltage cells the node has (1 on the ADT path), and the state's power in mW (0 on - /// the ADT path). + /// Per-cluster core mV of each state, rounded up from the device tree's microvolts, and how + /// many voltage cells the node has (1 on the ADT path). pub(crate) cluster_mv: [[u32; CLUSTERS]; PERF_STATES], pub(crate) cluster_cells: usize, - pub(crate) perf_mw: [u32; PERF_STATES], + /// The GPU's six sensor rows of the ADT's `aon-ptd` table (T6031; zero on the T6030 path, + /// whose templates carry their own). + pub(crate) sensor_rows: [[u8; 12]; record::SENSOR_ROW_COUNT], } /// Voltage cells the layout keeps per state. @@ -328,7 +332,7 @@ fn parse(dev: &device::Device, adt: &Adt<'_>) -> Result { sram_mv: [0; PERF_STATES], cluster_mv: [[0; CLUSTERS]; PERF_STATES], cluster_cells: 1, - perf_mw: [0; PERF_STATES], + sensor_rows: [[0; 12]; record::SENSOR_ROW_COUNT], }; collect_perf(dev, adt, sgx, &mut out)?; for i in 0..PERF_STATES { @@ -437,11 +441,11 @@ fn from_device_tree(dev: &device::Device) -> Result { sram_mv: [0; PERF_STATES], cluster_mv: [[0; CLUSTERS]; PERF_STATES], cluster_cells: 0, - perf_mw: [0; PERF_STATES], + sensor_rows: [[0; 12]; record::SENSOR_ROW_COUNT], }; // Operating points: MHz, the highest cluster voltage of each and the clusters' voltages - // (rounded up) and power for the hardware record; the SRAM voltage is not in the device - // tree. + // (rounded up) for the hardware record; the SRAM voltage is not in the device tree, and + // the powers are a boot-loader stand-in. let opps = node .parse_phandle(c_str!("operating-points-v2"), 0) .ok_or(EINVAL) @@ -450,7 +454,6 @@ fn from_device_tree(dev: &device::Device) -> Result { for opp in opps.children() { let hz: u64 = opp.get_property(c_str!("opp-hz"))?; let uv: KVec = opp.get_property(c_str!("opp-microvolt"))?; - let uw: Option = opp.get_opt_property(c_str!("opp-microwatt"))?; let mv = uv.iter().max().copied().unwrap_or(0) / 1000; if uv.is_empty() || uv.len() > CLUSTERS || (count > 0 && uv.len() != out.cluster_cells) { dev_err!(dev, "GPU operating point {} has {} voltage cells\n", count, uv.len()); @@ -467,7 +470,6 @@ fn from_device_tree(dev: &device::Device) -> Result { out.cluster_mv[count][c] = v.div_ceil(1000); } out.cluster_cells = uv.len(); - out.perf_mw[count] = uw.unwrap_or(0) / 1000; } count += 1; } @@ -480,19 +482,44 @@ fn from_device_tree(dev: &device::Device) -> Result { ); return Err(EINVAL); } + out.sensor_rows = sensor_rows(dev)?; Ok(out) } +/// A private copy of the boot loader's ADT export. +fn copy() -> Result> { + let (start, size) = glue::adt_phram()?; + if size == 0 || size > 0x200000 || start & 0x3fff != 0 { + return Err(EINVAL); + } + super::identity::copy_physical(start, usize::try_from(size)?) +} + +/// The GPU's six sensor rows: `/arm-io/aon-ptd` `ptd_data_table` of the ADT export +/// ([`record::sensor_rows`]), which the device tree does not carry. +fn sensor_rows(dev: &device::Device) -> Result<[[u8; 12]; record::SENSOR_ROW_COUNT]> { + let copy = + copy().inspect_err(|_| dev_err!(dev, "no usable ADT export for the GPU sensor rows\n"))?; + let adt = Adt { data: © }; + let table = adt + .node_end(0, 0) + .and_then(|_| adt.child(0, "arm-io")) + .and_then(|armio| adt.child(armio, "aon-ptd")) + .and_then(|ptd| adt.prop(ptd, "ptd_data_table")) + .ok_or(EINVAL) + .inspect_err(|_| dev_err!(dev, "ADT has no aon-ptd ptd_data_table\n"))?; + record::sensor_rows(table).map_err(|why| { + dev_err!(dev, "ADT aon-ptd: {}\n", why); + EINVAL + }) +} + /// Reads and admits the GPU's regions, segments and performance states (read-only), from the /// source the SoC configuration names. pub(crate) fn read(dev: &device::Device, cfg: &super::Config) -> Result { if cfg.platform == hw::Platform::DeviceTree { return from_device_tree(dev); } - let (start, size) = glue::adt_phram()?; - if size == 0 || size > 0x200000 || start & 0x3fff != 0 { - return Err(EINVAL); - } - let copy = super::identity::copy_physical(start, usize::try_from(size)?)?; + let copy = copy()?; parse(dev, &Adt { data: © }) } diff --git a/drivers/gpu/drm/asahi/g15/board.rs b/drivers/gpu/drm/asahi/g15/board.rs index da0a7408575bfb..f9682b2ead10c2 100644 --- a/drivers/gpu/drm/asahi/g15/board.rs +++ b/drivers/gpu/drm/asahi/g15/board.rs @@ -10,11 +10,11 @@ //! against its hash at boot. //! //! A SoC whose board data comes from its device tree ([`super::hw::Platform::DeviceTree`]) gets -//! its hardware record from [`hardware_record`] ([`record`]) and its Globals and Power from -//! [`device_tree`]: the G15S templates with the GPU node's power words and the hardware -//! record's maximum power ([`words`]). +//! its hardware record from [`hardware_record`] ([`record`]: the GPU node, the ADT's sensor +//! rows and the SoC's reference words) and its Globals and Power from [`device_tree`]: the G15S +//! templates with the GPU node's power words and the hardware record's maximum power +//! ([`words`]). -pub(crate) mod blob; pub(crate) mod data; pub(crate) mod record; pub(crate) mod template; @@ -98,7 +98,6 @@ fn aux_opps( mhz, mv: [uv[0].div_ceil(1000), 0, 0, 0], cells: 1, - mw: 0, }; n += 1; } @@ -110,13 +109,13 @@ fn aux_opps( } /// The hardware record of a device-tree SoC ([`record`]): built from the GPU node's properties -/// and operating-point tables, the layout's IOMapping table and the ADT regions, with the -/// words the device tree does not carry copied from `file`, the machine's own record. +/// and operating-point tables, the layout's IOMapping table, the ADT regions and sensor rows, +/// and the SoC's `reference` words, which neither carries. pub(crate) fn hardware_record( dev: &device::Device, cfg: &super::Config, adt: &GpuAdt, - file: &[u8], + reference: &record::Reference, ) -> Result> { let layout = cfg.layout; let node = dev.of_node().ok_or(ENODEV)?; @@ -136,7 +135,6 @@ pub(crate) fn hardware_record( mhz: adt.perf_mhz[i], mv: adt.cluster_mv[i], cells: adt.cluster_cells, - mw: adt.perf_mw[i], }; } let mut cs = [record::Opp::default(); 16]; @@ -159,12 +157,6 @@ pub(crate) fn hardware_record( writable, }; } - let max_power_mw = u32::from_le_bytes( - file.get(record::MAX_POWER..record::MAX_POWER + 4) - .ok_or(EINVAL)? - .try_into() - .map_err(|_| EINVAL)?, - ); let inputs = record::Inputs { props, zones: &zones, @@ -177,15 +169,14 @@ pub(crate) fn hardware_record( usc_base: hw::client::COMPACT_WINDOW.start, user_end: hw::USER_RANGE.end, timestamps: va::TIMESTAMPS, - max_power_mw, + sensor_rows: adt.sensor_rows, + reference, }; let mut out = KVVec::from_elem(0u8, record::SIZE, GFP_KERNEL)?; - record::from_file(&mut out, file) - .and_then(|()| record::build(&mut out, &inputs)) - .map_err(|why| { - dev_err!(dev, "hardware record from the device tree: {}\n", why); - EINVAL - })?; + record::build(&mut out, &inputs).map_err(|why| { + dev_err!(dev, "hardware record from the device tree: {}\n", why); + EINVAL + })?; Ok(out) } diff --git a/drivers/gpu/drm/asahi/g15/board/blob.rs b/drivers/gpu/drm/asahi/g15/board/blob.rs deleted file mode 100644 index 04cff8bbaa3d99..00000000000000 --- a/drivers/gpu/drm/asahi/g15/board/blob.rs +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT - -//! Board images from a firmware file. -//! -//! A SoC whose board images are not built-in templates names a firmware file -//! ([`super::super::hw::Board::Blob`]), built into the kernel with `CONFIG_EXTRA_FIRMWARE` or -//! loaded from `/lib/firmware`, and pins its SHA-256. The file holds the three images as the -//! firmware of that machine accepted them; the build then writes the pointers and policy words -//! it owns into them (TTBAT, IOMapping VAs, the timestamp arena, the idle policy). -//! -//! Layout, little-endian: the magic `T6031BRD`, u32 format version (1), u32 image count (3), -//! then per image `u32 kind, u32 size` with kinds 0 (HardwareData), 1 (Globals) and -//! 2 (Power), then the images' bytes in that order. Sizes must match the SoC's layout. - -use kernel::{device, firmware::Firmware, prelude::*}; - -use super::super::boot::glue; -use super::super::identity::Hex; - -const MAGIC: &[u8; 8] = b"T6031BRD"; -const VERSION: u32 = 1; -const IMAGES: usize = 3; - -/// The three images. -pub(crate) struct Images { - pub(crate) hardware: KVVec, - pub(crate) globals: KVVec, - pub(crate) power: KVVec, -} - -fn le32(b: &[u8], off: usize) -> Result { - Ok(u32::from_le_bytes( - b.get(off..off + 4) - .ok_or(EINVAL)? - .try_into() - .map_err(|_| EINVAL)?, - )) -} - -fn copy(b: &[u8], range: core::ops::Range) -> Result> { - let src = b.get(range).ok_or(EINVAL)?; - let mut out = KVVec::from_elem(0u8, src.len(), GFP_KERNEL)?; - out.copy_from_slice(src); - Ok(out) -} - -/// Requests `name`, verifies its digest against `sha256` and splits it into the images, each -/// of the size `sizes` gives for its kind. -pub(crate) fn load( - dev: &device::Device, - name: &CStr, - sha256: &[u8; 32], - sizes: [usize; IMAGES], -) -> Result { - let fw = Firmware::request(name, dev).inspect_err(|e| { - dev_err!(dev, "board images {}: not available ({:?})\n", name, e) - })?; - let data = fw.data(); - let digest = glue::sha256(data); - if digest != *sha256 { - dev_err!( - dev, - "board images {}: sha256 {} is not the pinned one\n", - name, - Hex(&digest) - ); - return Err(EINVAL); - } - if data.len() < 16 || &data[..8] != MAGIC || le32(data, 8)? != VERSION { - dev_err!(dev, "board images {}: unknown header\n", name); - return Err(EINVAL); - } - if le32(data, 12)? as usize != IMAGES { - dev_err!(dev, "board images {}: not {} images\n", name, IMAGES); - return Err(EINVAL); - } - let mut at = 16 + IMAGES * 8; - let mut images: [Option>; IMAGES] = [None, None, None]; - for i in 0..IMAGES { - let kind = le32(data, 16 + i * 8)? as usize; - let size = le32(data, 16 + i * 8 + 4)? as usize; - if kind != i || size != sizes[i] { - dev_err!( - dev, - "board images {}: image {} is kind {} of {} bytes, need {}\n", - name, - i, - kind, - size, - sizes[i] - ); - return Err(EINVAL); - } - images[i] = Some(copy(data, at..at + size)?); - at += size; - } - if at != data.len() { - dev_err!(dev, "board images {}: trailing bytes\n", name); - return Err(EINVAL); - } - let [hardware, globals, power] = images; - dev_info!( - dev, - "board images {}: sha256 {} accepted\n", - name, - Hex(&digest) - ); - Ok(Images { - hardware: hardware.ok_or(EINVAL)?, - globals: globals.ok_or(EINVAL)?, - power: power.ok_or(EINVAL)?, - }) -} diff --git a/drivers/gpu/drm/asahi/g15/board/record.rs b/drivers/gpu/drm/asahi/g15/board/record.rs index 959bc6a4ab0a2c..119a6920643914 100644 --- a/drivers/gpu/drm/asahi/g15/board/record.rs +++ b/drivers/gpu/drm/asahi/g15/board/record.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only OR MIT -//! The J516C hardware record (0xb480 bytes) from the device tree. +//! The J516C hardware record (0xb480 bytes) from the device tree and the ADT. //! //! The record is HardwareDataB (identity, IOMapping records, the DVFS tables) at 0, HardwareDataA //! (the power, performance and thermal controllers) at [`HWDATA_A`], and the G15C tail (sensor @@ -13,12 +13,14 @@ //! state cap and base state; //! - a layout constant of the 2419 firmware's G15C record: flags, the IOMapping flag words, //! the identity words, fixed factors (65536, 95, 100, 1.02, 0.8/0.2, 625, 11000, 125, 5); -//! - a word the device tree does not carry ([`FROM_FILE`]), copied from the machine's board file -//! by [`from_file`] and listed with its rule in the project notes: the maximum power -//! (HardwareDataA +0x6a4; every other maximum-power word derives from it), the MTR sensor -//! masks, the relative-power table (the device tree's operating-point powers are a boot-loader -//! stand-in) and the tail's six sensor rows; -//! - a word the file carries and the firmware was shown not to need ([`UNNEEDED`]): zero. +//! - the tail's six sensor rows: entries of the ADT's `aon-ptd` sensor table ([`sensor_rows`]); +//! - a word neither carries ([`REFERENCE_WORDS`]): the value of the J516C reference machine +//! ([`Reference`]), as the T6030 templates carry their reference machine's: the maximum +//! power (HardwareDataA +0x6a4; every other maximum-power word derives from it), the MTR +//! sensor masks and the relative-power table (the device tree's operating-point powers are a +//! boot-loader stand-in); +//! - a word the reference machine's record carries and the firmware was shown not to need +//! ([`UNNEEDED`]): zero. //! //! HardwareDataA has the G15S shape of the T6030 templates with insertions ([`a`]): 8 bytes //! before the fast-die controller (+0x8bc), 16-wide instead of 8-wide coefficient rows (+0x200 @@ -152,14 +154,13 @@ const SE_KP1: usize = 40; const SE_RESET: usize = 41; const SE_TARGET: usize = 42; -/// One operating point: MHz, the clusters' voltages in mV (rounded up from microvolts), how -/// many voltage cells the node had, and mW. +/// One operating point: MHz, the clusters' voltages in mV (rounded up from microvolts) and how +/// many voltage cells the node had. #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] pub(crate) struct Opp { pub(crate) mhz: u32, pub(crate) mv: [u32; CLUSTERS], pub(crate) cells: usize, - pub(crate) mw: u32, } impl Opp { @@ -198,40 +199,63 @@ pub(crate) struct Inputs<'a> { pub(crate) usc_base: u64, pub(crate) user_end: u64, pub(crate) timestamps: u64, - /// The maximum GPU power in mW ([`MAX_POWER`], a word the device tree does not carry). + /// The six sensor rows of the ADT's `aon-ptd` table ([`sensor_rows`]). + pub(crate) sensor_rows: [[u8; 12]; SENSOR_ROW_COUNT], + /// The words neither the device tree nor the ADT carries. + pub(crate) reference: &'a Reference, +} + +/// The record's words that neither the device tree nor the ADT carries, as one machine's +/// driver wrote them ([`REFERENCE_WORDS`]). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) struct Reference { + /// The maximum GPU power in mW ([`MAX_POWER`]). pub(crate) max_power_mw: u32, + /// The relative powers of DVFS states 1..7, percent of the top state's (state 8 is 100). + pub(crate) relative_power: [u32; RELATIVE_STATES], + /// The MTR fast-die sensor mask (written twice) and alarm mask, as the u64 at each place. + /// The device tree's `apple,fast-die0-sensor-mask` is another value. + pub(crate) fast_die_mask: u64, + pub(crate) alarm_mask: u64, } +/// DVFS states with a relative-power word of their own. +pub(crate) const RELATIVE_STATES: usize = 7; + +/// The J516C reference machine's words (its 2419 firmware driver's values on that machine). +/// Another J516C gets them as a J514S gets the T6030 templates' machine's: the operating +/// points, voltages, gains and sensor rows are its own, these are the reference machine's. +pub(crate) const J516C: Reference = Reference { + max_power_mw: 116739, + relative_power: [11, 23, 33, 44, 55, 71, 90], + fast_die_mask: 0x802a002b_802a002b, + alarm_mask: 0x902a102b_802a002b, +}; + /// HardwareDataA's maximum-power word (= `fw::initdata::hwdata::MAX_POWER_A`). pub(crate) const MAX_POWER: usize = HWDATA_A + 0x6a4; +const RELATIVE_POWER: usize = 0x1060; +const FAST_DIE_MASK: usize = HWDATA_A + 0x8ac; +const FAST_DIE_MASK_2: usize = HWDATA_A + 0x1490; +const ALARM_MASK: usize = HWDATA_A + 0x24a8; -/// Words the device tree does not carry, copied from the board file. Record offsets. +/// The record words [`Reference`] supplies. Record offsets. +#[cfg(test)] #[rustfmt::skip] -pub(crate) const FROM_FILE: [usize; 32] = [ - // The relative powers of states 1..7 (state 8 is 100): the device tree's operating-point - // powers are a boot-loader stand-in. +pub(crate) const REFERENCE_WORDS: [usize; 14] = [ 0x1060, 0x1064, 0x1068, 0x106c, 0x1070, 0x1074, 0x1078, - // HardwareDataA: the maximum power, the MTR fast-die masks (+0x8ac, +0x1490) and the alarm - // mask (+0x24a8); the device tree's apple,fast-die0-sensor-mask is another value. MAX_POWER, - HWDATA_A + 0x8ac, HWDATA_A + 0x8b0, - HWDATA_A + 0x1490, HWDATA_A + 0x1494, - HWDATA_A + 0x24a8, HWDATA_A + 0x24ac, - // The tail's six sensor rows (key, id, kind; 12 bytes each; the boot loader does not export - // the aon-ptd table). - HWDATA_A + 0x6c58, HWDATA_A + 0x6c5c, HWDATA_A + 0x6c60, - HWDATA_A + 0x6c64, HWDATA_A + 0x6c68, HWDATA_A + 0x6c6c, - HWDATA_A + 0x6c70, HWDATA_A + 0x6c74, HWDATA_A + 0x6c78, - HWDATA_A + 0x6c7c, HWDATA_A + 0x6c80, HWDATA_A + 0x6c84, - HWDATA_A + 0x6c88, HWDATA_A + 0x6c8c, HWDATA_A + 0x6c90, - HWDATA_A + 0x6c94, HWDATA_A + 0x6c98, HWDATA_A + 0x6c9c, + FAST_DIE_MASK, FAST_DIE_MASK + 4, + FAST_DIE_MASK_2, FAST_DIE_MASK_2 + 4, + ALARM_MASK, ALARM_MASK + 4, ]; -/// Words the board file carries that the record leaves zero: the firmware ran, passed every -/// check and reached its top state without them (one boot each). The cross-check accepts the -/// file's value there. 27 words in the HardwareDataB +0x38..+0x638 block and two all-ones words +/// Words the reference machine's record carries that this one leaves zero: the firmware ran, +/// passed every check and reached its top state without them (one boot each). 27 words in +/// the HardwareDataB +0x38..+0x638 block and two all-ones words /// after it (zero in the G15S templates); the tail's thirteen readings (a single at /// HardwareDataA +0x6d34, twelve singles after +0x6d80). +#[cfg(test)] #[rustfmt::skip] pub(crate) const UNNEEDED: [usize; 42] = [ 0xe0, 0xe8, 0xf4, 0xf8, 0xfc, 0x100, 0x104, 0x108, 0x10c, 0x2d8, 0x2dc, 0x2e0, 0x2e4, @@ -247,7 +271,7 @@ pub(crate) const UNNEEDED: [usize; 42] = [ /// rows whose kind byte is `g`, their presence mask, then the `r` and `f` rows' keys with a /// count of one each. const SENSOR_ROWS: usize = HWDATA_A + 0x6c58; -const SENSOR_ROW_COUNT: usize = 6; +pub(crate) const SENSOR_ROW_COUNT: usize = 6; const SENSOR_G_KEYS: usize = HWDATA_A + 0x6cdc; const SENSOR_G_MASK: usize = HWDATA_A + 0x6cfc; const SENSOR_R_KEY: usize = HWDATA_A + 0x6d04; @@ -492,15 +516,13 @@ fn hwdata_b(out: &mut [u8], i: &Inputs<'_>, t: &Tables) -> R<()> { put32(out, 0x10dc + 4 * k, t.primary[k] as u32)?; put32(out, 0x111c + 4 * k, t.secondary[k] as u32)?; if k > 0 { - // Relative power (a FROM_FILE word on the J516C) and relative boost frequency. - if top.mw != 0 { - put32( - out, - 0x105c + 4 * k, - u32::try_from(100 * u64::from(ps.mw) / u64::from(top.mw)) - .map_err(|_| "a relative power does not fit its u32 word")?, - )?; - } + // Relative power (the reference machine's) and relative boost frequency. + let relative = match i.reference.relative_power.get(k - 1) { + Some(&r) if k < t.top() => r, + None if k == t.top() => 100, + _ => return Err("the reference relative powers are for another state count"), + }; + put32(out, RELATIVE_POWER - 4 + 4 * k, relative)?; if ps.mhz > base.mhz { let span = u64::from(top.mhz - base.mhz); put32( @@ -565,7 +587,7 @@ fn hwdata_a(out: &mut [u8], i: &Inputs<'_>, t: &Tables) -> R<()> { |gain: usize| f32_mul(p[gain], period_s).ok_or("a gain is not a finite normal single"); let max_scaled = 100 * t.top() as u32; let base_scaled = mul(100, p[BASE_PSTATE])?; - let max_power = i.max_power_mw; + let max_power = i.reference.max_power_mw; let max_power_f = f32_from_u32(max_power); let mut w = |off: usize, v: u32| put32(out, a(off), v); w(0x4, p[AIC_CLKS])?; @@ -644,7 +666,7 @@ fn hwdata_a(out: &mut [u8], i: &Inputs<'_>, t: &Tables) -> R<()> { w(0x864, max_scaled)?; w(0x86c, F_0_8)?; w(0x870, F_0_2)?; - // Fast-die controller (its sensor mask is a FROM_FILE word). + // Fast-die controller (its sensor mask follows below). w(0x8bc, 4)?; w(0x8d0, F_1)?; w(0x8d8, per_period(FD_KI)?)?; @@ -737,15 +759,46 @@ fn hwdata_a(out: &mut [u8], i: &Inputs<'_>, t: &Tables) -> R<()> { w(SE + 0x11c, F_65536)?; w(SE + 0x12c, 40)?; w(SE + 0x130, max_scaled)?; - // Past the second sensor mask (FROM_FILE), the alarm mask (FROM_FILE) and the end flag. + // Past the second sensor mask, the alarm mask and the end flag. w(0x1290, 125)?; w(0x1294, 1)?; w(0x1aa0, 1)?; w(0x3aa4, F_5)?; + // The MTR sensor masks: the fast-die mask at both places, then the alarm mask. + put64(out, FAST_DIE_MASK, i.reference.fast_die_mask)?; + put64(out, FAST_DIE_MASK_2, i.reference.fast_die_mask)?; + put64(out, ALARM_MASK, i.reference.alarm_mask)?; Ok(()) } -/// The tail's sensor keys from the six rows (which [`from_file`] fills). +/// The six sensor rows of the ADT's `aon-ptd` `ptd_data_table`: 12-byte entries of a +/// little-endian four-character key (`RT0g` is stored as `"RT0g"`) and two words. The rows are +/// the `RT` entries of kind `g` (four, in table order), then the one of kind `r` and the one +/// of kind `f`; any other count is refused rather than guessed. +pub(crate) fn sensor_rows(table: &[u8]) -> R<[[u8; 12]; SENSOR_ROW_COUNT]> { + if table.is_empty() || table.len() % 12 != 0 { + return Err("the aon-ptd sensor table is not whole 12-byte entries"); + } + let mut rows = [[0u8; 12]; SENSOR_ROW_COUNT]; + let mut n = 0; + for (kind, wanted) in [(b'g', 4), (b'r', 1), (b'f', 1)] { + let mut found = 0; + for e in table.chunks_exact(12) { + if e[..2] == *b"RT" && e[3] == kind { + let row = rows.get_mut(n).ok_or("too many GPU sensor rows in aon-ptd")?; + row.copy_from_slice(e); + n += 1; + found += 1; + } + } + if found != wanted { + return Err("aon-ptd does not hold four g, one r and one f GPU sensor row"); + } + } + Ok(rows) +} + +/// The tail's sensor keys from the six rows. fn tail(out: &mut [u8]) -> R<()> { let mut g = 0usize; for row in 0..SENSOR_ROW_COUNT { @@ -775,16 +828,10 @@ fn tail(out: &mut [u8]) -> R<()> { Ok(()) } -/// Copies the [`FROM_FILE`] words of `file` (the board file's record) into `out`. -pub(crate) fn from_file(out: &mut [u8], file: &[u8]) -> R<()> { - for &at in FROM_FILE.iter() { - put32(out, at, get32(file, at)?)?; - } - Ok(()) -} - -/// The first word at which the built record differs from the file's, except the [`UNNEEDED`] -/// words, where the file's value is accepted as long as the built word is zero. +/// The first word at which a built record differs from a machine's own record (a host test's +/// fixture), except the [`UNNEEDED`] words, where the machine's value is accepted as long as +/// the built word is zero. +#[cfg(test)] pub(crate) fn cross_check(built: &[u8], file: &[u8]) -> Option<(usize, u32, u32)> { let word = |s: &[u8], at: usize| -> u32 { let mut w = [0u8; 4]; @@ -799,23 +846,20 @@ pub(crate) fn cross_check(built: &[u8], file: &[u8]) -> Option<(usize, u32, u32) .find(|&(at, b, f)| b != f && !(b == 0 && UNNEEDED.contains(&at))) } -/// Builds the record into `out` (zeroed first) from the device-tree inputs; the [`FROM_FILE`] -/// words must have been copied in first ([`from_file`]) or are left as they are. +/// Builds the record into `out` (zeroed first) from the inputs. pub(crate) fn build(out: &mut [u8], i: &Inputs<'_>) -> R<()> { if out.len() != SIZE { return Err("record of another size"); } - let mut keep = [0u32; FROM_FILE.len()]; - for (k, &at) in FROM_FILE.iter().enumerate() { - keep[k] = get32(out, at)?; - } out.fill(0); let t = tables(i.main)?; hwdata_b(out, i, &t)?; hwdata_a(out, i, &t)?; - // The kept words win over what the rules put there (the relative powers, the masks). - for (k, &at) in FROM_FILE.iter().enumerate() { - put32(out, at, keep[k])?; + for (k, row) in i.sensor_rows.iter().enumerate() { + let at = SENSOR_ROWS + 12 * k; + out.get_mut(at..at + 12) + .ok_or("record too short")? + .copy_from_slice(row); } tail(out) } @@ -828,18 +872,13 @@ mod tests { get32(b, o).unwrap() } - fn opp(mhz: u32, mv: [u32; 4], mw: u32) -> Opp { - Opp { - mhz, - mv, - cells: 4, - mw, - } + fn opp(mhz: u32, mv: [u32; 4]) -> Opp { + Opp { mhz, mv, cells: 4 } } /// Operating points shaped like the J516C's: eight voltages, the upper five with two states. fn main_opps() -> Vec { - let mut v = vec![opp(0, [125; 4], 0)]; + let mut v = vec![opp(0, [125; 4])]; let groups: [(u32, [u32; 4], &[u32]); 8] = [ (605, [605, 605, 615, 615], &[338]), (680, [680, 680, 665, 665], &[618]), @@ -852,7 +891,7 @@ mod tests { ]; for (_, mv, mhz) in groups { for &m in mhz { - v.push(opp(m, mv, m * 40)); + v.push(opp(m, mv)); } } v @@ -888,6 +927,27 @@ mod tests { p } + /// Arbitrary reference words, distinct from the J516C's. + const TEST_REFERENCE: Reference = Reference { + max_power_mw: 54321, + relative_power: [0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006], + fast_die_mask: 0x1111_2222_3333_4444, + alarm_mask: 0x5555_6666_7777_8888, + }; + + /// A sensor row: the key's characters in storage order, then two words. + fn row(key: &[u8; 4], id: u32, kind: u32) -> [u8; 12] { + let mut r = [0u8; 12]; + r[..4].copy_from_slice(key); + r[4..8].copy_from_slice(&id.to_le_bytes()); + r[8..].copy_from_slice(&kind.to_le_bytes()); + r + } + + fn rows() -> [[u8; 12]; SENSOR_ROW_COUNT] { + [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"].map(|k| row(k, 0x10001, 0x1401)) + } + fn inputs<'a>(main: &'a [Opp], cs: &'a [Opp], slots: &'a [IoSlot]) -> Inputs<'a> { Inputs { props: props(), @@ -901,7 +961,8 @@ mod tests { usc_base: 0x10_0000_0000, user_end: 0x2ff_ffff_8000, timestamps: 0xfffffc2ffc000000, - max_power_mw: 54321, + sensor_rows: rows(), + reference: &TEST_REFERENCE, } } @@ -927,9 +988,12 @@ mod tests { #[test] fn unneeded() { - // No UNNEEDED word is a FROM_FILE word; the cross-check accepts the file's value there - // only against a zero built word, and nothing else. - assert!(UNNEEDED.iter().all(|at| !FROM_FILE.contains(at))); + // No UNNEEDED word is a reference or sensor-row word; the cross-check accepts the + // file's value there only against a zero built word, and nothing else. + let rows = SENSOR_ROWS..SENSOR_ROWS + 12 * SENSOR_ROW_COUNT; + assert!(UNNEEDED + .iter() + .all(|at| !REFERENCE_WORDS.contains(at) && !rows.contains(at))); assert!(UNNEEDED.iter().all(|&at| at % 4 == 0 && at + 4 <= SIZE)); let mut file = vec![0u8; SIZE]; for (k, &at) in UNNEEDED.iter().enumerate() { @@ -957,9 +1021,10 @@ mod tests { assert_eq!(a(0x1aa0), HWDATA_A + 0x24b8); assert_eq!(a(0x3aa4), HWDATA_A + 0x64bc); assert_eq!(MAX_POWER, a(0x6a4)); - // FROM_FILE is sorted, unique and inside the record. - assert!(FROM_FILE.windows(2).all(|w| w[0] < w[1])); - assert!(FROM_FILE.iter().all(|&o| o % 4 == 0 && o + 4 <= SIZE)); + // REFERENCE_WORDS is sorted, unique and inside the record. + assert!(REFERENCE_WORDS.windows(2).all(|w| w[0] < w[1])); + assert!(REFERENCE_WORDS.iter().all(|&o| o % 4 == 0 && o + 4 <= SIZE)); + assert!(SENSOR_ROWS + 12 * SENSOR_ROW_COUNT <= SENSOR_G_KEYS); } #[test] @@ -995,7 +1060,6 @@ mod tests { mhz, mv: [mv, 0, 0, 0], cells: 1, - mw: 0, }) .collect(); let slots = [ @@ -1017,20 +1081,23 @@ mod tests { }, ]; let i = inputs(&main, &cs, &slots); - let mut r = vec![0u8; SIZE]; - // FROM_FILE words survive the build; the sensor rows feed the tail. - for (k, &at) in FROM_FILE.iter().enumerate() { - put32(&mut r, at, 0x1000 + k as u32).unwrap(); - } - // The file's maximum power is what the input repeats. - put32(&mut r, MAX_POWER, 54321).unwrap(); - let rows = [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"]; - for (k, row) in rows.iter().enumerate() { - put32(&mut r, SENSOR_ROWS + 12 * k, u32::from_le_bytes(**row)).unwrap(); - } + // Whatever the buffer held before is not part of the record. + let mut r = vec![0x5au8; SIZE]; build(&mut r, &i).unwrap(); let good = r.clone(); - assert_eq!(get(&r, 0x1060), 0x1000); + // The reference words: relative powers of states 1..7, the maximum power, the masks. + for k in 0..RELATIVE_STATES { + assert_eq!(get(&r, 0x1060 + 4 * k), 0x1000 + k as u32); + } + assert_eq!(get(&r, FAST_DIE_MASK), 0x3333_4444); + assert_eq!(get(&r, FAST_DIE_MASK + 4), 0x1111_2222); + assert_eq!(get(&r, FAST_DIE_MASK_2), 0x3333_4444); + assert_eq!(get(&r, FAST_DIE_MASK_2 + 4), 0x1111_2222); + assert_eq!(get(&r, ALARM_MASK), 0x7777_8888); + assert_eq!(get(&r, ALARM_MASK + 4), 0x5555_6666); + // The sensor rows as given. + assert_eq!(&r[SENSOR_ROWS..SENSOR_ROWS + 12], &rows()[0]); + assert_eq!(&r[SENSOR_ROWS + 60..SENSOR_ROWS + 72], &rows()[5]); assert_eq!(get(&r, 0x30f0), 0); // Identity, IOMapping records, flags. assert_eq!(get(&r, 0xa28), 0x6031); @@ -1099,8 +1166,15 @@ mod tests { let mut j = inputs(&main, &cs, &slots); j.empty_writable = &[0]; assert!(build(&mut r, &j).is_err()); + let zero = Reference { + max_power_mw: 0, + ..TEST_REFERENCE + }; + let mut j = inputs(&main, &cs, &slots); + j.reference = &zero; + assert!(build(&mut r, &j).is_err()); let mut j = inputs(&main, &cs, &slots); - j.max_power_mw = 0; + j.sensor_rows[0] = row(b"RT0x", 1, 1); assert!(build(&mut r, &j).is_err()); let mut j = inputs(&main, &cs, &slots); j.zones = &[100, 101, 1]; @@ -1127,34 +1201,49 @@ mod tests { let mut j = inputs(&main, &cs, &slots); j.zones = &[5000, 200, 0x4000_0000]; assert_eq!(refused(&j), Err(OVERFLOW)); - let mut skewed = main.clone(); - skewed[1].mw = u32::MAX; - skewed[13].mw = 1; + // The seven relative powers fit eight states above state 0 and nothing else. + let fewer = &main[..PERF_STATES - 2]; + assert_eq!(tables(fewer).unwrap().top(), 7); assert_eq!( - refused(&inputs(&skewed, &cs, &slots)), - Err("a relative power does not fit its u32 word") + refused(&inputs(fewer, &cs, &slots)), + Err("the reference relative powers are for another state count") ); - // The admission cross-check as initdata::blob() composes it: the FROM_FILE words from - // the file, the rest built, then the first differing word against the file. A file the - // inputs reproduce passes; a rule word the file holds differently is found at its - // offset; a FROM_FILE word follows the file and is never a difference. - let first_difference = super::super::words::first_difference; - let checked = |file: &[u8]| { - let mut built = vec![0u8; SIZE]; - from_file(&mut built, file).unwrap(); - build(&mut built, &i).unwrap(); - (first_difference(&built, file), built) - }; - assert_eq!(checked(&good).0, None); - let mut file = good.clone(); - put32(&mut file, a(0x4c), 801).unwrap(); - assert_eq!(checked(&file).0, Some((a(0x4c), 800, 801))); - let mut file = good.clone(); - assert!(FROM_FILE.contains(&0x1064)); - put32(&mut file, 0x1064, get(&good, 0x1064) ^ 0x5a).unwrap(); - let (difference, built) = checked(&file); - assert_eq!(difference, None); - assert_eq!(get(&built, 0x1064), get(&good, 0x1064) ^ 0x5a); + } + + #[test] + fn sensor_rows_from_the_ptd_table() { + // A table shaped like the J516C's: other sensors around the GPU's, the f row first. + let mut table = Vec::new(); + for r in [ + row(b"YD0C", 0x5e0001, 0x1040405), + row(b"RT0a", 0x6b0001, 0x13), + row(b"RT0f", 0xcb0001, 0x1601), + row(b"RE0g", 0xaa0001, 0xff01), + row(b"RT0g", 0xab0001, 0x1401), + row(b"RT1g", 0x8d0001, 0x1401), + row(b"RT2g", 0x8b0001, 0x1401), + row(b"RT3g", 0xad0001, 0x1401), + row(b"RT0p", 0x6d0001, 0x13), + row(b"RT0r", 0xeb0001, 0x1401), + row(b"XEXE", 0x3c0001, 0xff00), + ] { + table.extend_from_slice(&r); + } + let got = sensor_rows(&table).unwrap(); + let keys: Vec<&[u8]> = got.iter().map(|r| &r[..4]).collect(); + assert_eq!(keys, [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"]); + assert_eq!(got[5], row(b"RT0f", 0xcb0001, 0x1601)); + assert_eq!(got[4], row(b"RT0r", 0xeb0001, 0x1401)); + // Refused: no table, a partial entry, a missing row, a fifth g row, a second f row. + assert!(sensor_rows(&[]).is_err()); + assert!(sensor_rows(&table[..table.len() - 1]).is_err()); + assert!(sensor_rows(&table[12..table.len() - 12]).is_ok()); + assert!(sensor_rows(&table[..9 * 12]).is_err()); + for extra in [row(b"RT4g", 1, 1), row(b"RT1f", 1, 1)] { + let mut t = table.clone(); + t.extend_from_slice(&extra); + assert!(sensor_rows(&t).is_err()); + } } // --- The fixture: a saved copy of a J516C's device tree and its private board file. --- @@ -1236,14 +1325,12 @@ mod tests { for (c, m) in mv.iter_mut().enumerate().take(cells) { *m = be32(uv, 4 * c).div_ceil(1000); } - let mw = prop(props, "opp-microwatt").map_or(0, |w| be32(w, 0) / 1000); ( p.clone(), Opp { mhz: (hz / 1_000_000) as u32, mv, cells, - mw, }, ) }) @@ -1262,16 +1349,8 @@ mod tests { mhz: 24, mv: [635, 0, 0, 0], cells: 1, - mw: 0, }]; let mut seed = vec![0u8; SIZE]; - put32(&mut seed, MAX_POWER, 54321).unwrap(); - for (k, key) in [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"] - .iter() - .enumerate() - { - put32(&mut seed, SENSOR_ROWS + 12 * k, u32::from_le_bytes(**key)).unwrap(); - } assert_eq!(build(&mut seed, &inputs(&main, &cs, &[])), Ok(())); for prop in 0..PROPS.len() { for value in [ @@ -1289,16 +1368,59 @@ mod tests { } } - /// Builder on the saved device tree + the file's FROM_FILE words == the file's hardware - /// record, byte for byte. Needs `G15_J516C_FIXTURES=` with `j516c.dtb` and - /// `t6031-j516c-board.bin` (off the tree): ignored by default, `--ignored` runs it. + /// A property of an ADT node found by its path of `name`s from the root: nodes of u32 + /// property and child counts, properties of a 32-byte name, a u32 size (top bit a flag) + /// and the value padded to 4 bytes. + fn adt_prop<'a>(adt: &'a [u8], path: &[&str], name: &str) -> Option<&'a [u8]> { + fn le(b: &[u8], o: usize) -> usize { + u32::from_le_bytes(b[o..o + 4].try_into().unwrap()) as usize + } + // The node at `at`: its properties, its children's offsets and the offset past it. + fn node(adt: &[u8], at: usize) -> (Vec<(&[u8], &[u8])>, Vec, usize) { + let (np, nc) = (le(adt, at), le(adt, at + 4)); + let mut o = at + 8; + let mut props = Vec::new(); + for _ in 0..np { + let n = &adt[o..o + 32]; + let n = &n[..n.iter().position(|&b| b == 0).unwrap_or(32)]; + let size = le(adt, o + 32) & 0x7fff_ffff; + props.push((n, &adt[o + 36..o + 36 + size])); + o += 36 + size.next_multiple_of(4); + } + let mut children = Vec::new(); + for _ in 0..nc { + children.push(o); + o = node(adt, o).2; + } + (props, children, o) + } + let mut at = 0; + for want in path { + let (_, children, _) = node(adt, at); + let named = |c: usize| { + let (props, _, _) = node(adt, c); + props.iter().any(|(n, v)| { + *n == b"name" && v.split(|&b| b == 0).next() == Some(want.as_bytes()) + }) + }; + at = *children.iter().find(|&&c| named(c))?; + } + node(adt, at).0.into_iter().find(|(n, _)| *n == name.as_bytes()).map(|(_, v)| v) + } + + /// Builder on the saved device tree and ADT with the J516C reference words == the J516C + /// reference machine's own hardware record, byte for byte except the [`UNNEEDED`] words. + /// Needs `G15_J516C_FIXTURES=` with `j516c.dtb` (`/sys/firmware/fdt`), `adt.bin` (the + /// boot loader's ADT) and `t6031-j516c-board.bin` (that machine's record), all off the + /// tree: ignored by default, `--ignored` runs it. #[test] #[ignore = "needs the off-tree J516C fixtures (G15_J516C_FIXTURES)"] fn j516c_fixture() { let dir = std::env::var_os("G15_J516C_FIXTURES") - .expect("G15_J516C_FIXTURES names the directory with j516c.dtb and the board file"); + .expect("G15_J516C_FIXTURES names the directory with j516c.dtb, adt.bin, the record"); let dir = std::path::Path::new(&dir); let dtb = std::fs::read(dir.join("j516c.dtb")).unwrap(); + let adt = std::fs::read(dir.join("adt.bin")).unwrap(); let blob = std::fs::read(dir.join("t6031-j516c-board.bin")).unwrap(); assert_eq!(&blob[..8], b"T6031BRD"); let at = 16 + 3 * 8; @@ -1389,10 +1511,13 @@ mod tests { usc_base: 0x10_0000_0000, user_end: 0x2ff_ffff_8000, timestamps: 0xfffffc2ffc000000, - max_power_mw: get(file, MAX_POWER), + sensor_rows: sensor_rows( + adt_prop(&adt, &["arm-io", "aon-ptd"], "ptd_data_table").expect("aon-ptd table"), + ) + .unwrap(), + reference: &J516C, }; let mut built = vec![0u8; SIZE]; - from_file(&mut built, file).unwrap(); build(&mut built, &i).unwrap(); assert_eq!(cross_check(&built, file), None, "(offset, built, file)"); for &at in UNNEEDED.iter() { diff --git a/drivers/gpu/drm/asahi/g15/board/words.rs b/drivers/gpu/drm/asahi/g15/board/words.rs index 4c5082465d428f..f879d9b6716728 100644 --- a/drivers/gpu/drm/asahi/g15/board/words.rs +++ b/drivers/gpu/drm/asahi/g15/board/words.rs @@ -235,6 +235,7 @@ pub(crate) fn power( /// The first word (4-byte aligned offset) at which `a` and `b` differ, with both values; a /// missing word reads 0. `None` when they are equal. +#[cfg(test)] pub(crate) fn first_difference(a: &[u8], b: &[u8]) -> Option<(usize, u32, u32)> { let word = |s: &[u8], at: usize| -> u32 { let mut w = [0u8; 4]; @@ -584,7 +585,7 @@ mod tests { /// Builder on the saved device tree == the private file's Globals and Power, byte for byte. /// Needs `G15_J516C_FIXTURES=` with `j516c.dtb` (`/sys/firmware/fdt` of the machine) - /// and `t6031-j516c-board.bin` (its board file); both stay off the tree, so the test is + /// and `t6031-j516c-board.bin` (its own images); both stay off the tree, so the test is /// ignored by default (`--ignored` runs it) and fails, rather than passes, without them. #[test] #[ignore = "needs the off-tree J516C fixtures (G15_J516C_FIXTURES)"] @@ -595,7 +596,7 @@ mod tests { let dtb = std::fs::read(dir.join("j516c.dtb")).unwrap(); let blob = std::fs::read(dir.join("t6031-j516c-board.bin")).unwrap(); - // The board file: magic, version, count, 3 x (kind, size), images (board/blob.rs). + // The machine's images: magic, version, count, 3 x (kind, size), then the images. assert_eq!(&blob[..8], b"T6031BRD"); assert_eq!(get32(&blob, 8), 1); assert_eq!(get32(&blob, 12), 3); diff --git a/drivers/gpu/drm/asahi/g15/hw.rs b/drivers/gpu/drm/asahi/g15/hw.rs index 24ff3628eca811..fd8771d9d3e65a 100644 --- a/drivers/gpu/drm/asahi/g15/hw.rs +++ b/drivers/gpu/drm/asahi/g15/hw.rs @@ -47,15 +47,11 @@ pub(crate) enum Platform { pub(crate) enum Board { /// The hash-checked T6030 templates ([`super::board`]). Templates, - /// The three images built from the device tree ([`super::board::record`], - /// [`super::board::device_tree`]) and cross-checked against a firmware file holding the - /// machine's own ([`super::board::blob`], pinned by its SHA-256), which also supplies the - /// record's words the device tree does not carry ([`super::board::record::FROM_FILE`]); a - /// difference refuses admission, before any firmware is touched. - Blob { - name: &'static kernel::str::CStr, - sha256: [u8; 32], - }, + /// The three images built from the device tree and the ADT's sensor rows + /// ([`super::board::record`], [`super::board::device_tree`]), with the SoC's reference + /// words for what neither carries ([`super::board::record::Reference`]). A value the + /// builder refuses stops admission, before any firmware is touched. + DeviceTree(&'static super::board::record::Reference), /// No board data: admission refuses the SoC before anything firmware-visible. None, } diff --git a/drivers/gpu/drm/asahi/g15/hw/t6031.rs b/drivers/gpu/drm/asahi/g15/hw/t6031.rs index 4af32f6068cd7a..8bac33989db648 100644 --- a/drivers/gpu/drm/asahi/g15/hw/t6031.rs +++ b/drivers/gpu/drm/asahi/g15/hw/t6031.rs @@ -8,9 +8,8 @@ //! device tree node the boot loader fills, not an ADT export), the GPU power (its power domain; //! there is no PMP vote), the initdata root version, a longer HardwareData record (0xb480 //! bytes, its fourth view at +0xb400) placed aligned and ASC-cached, the IOMapping table (17 -//! slots at the T6031 register blocks) and the board images, built from the device tree and -//! checked against this machine's firmware file, which supplies the record's non-device-tree -//! words. Every +//! slots at the T6031 register blocks) and the board images, built from the device tree, the +//! ADT's sensor rows and the J516C reference words. Every //! value was measured on a J516C: the SGX identification //! registers, the loaded firmware segment, the device tree the boot loader hands over and the //! initdata of that machine. @@ -117,18 +116,6 @@ pub(crate) const LAYOUT: Layout = Layout { record_empty_writable: &RECORD_EMPTY_WRITABLE, }; -/// The J516C board file (`asahi/t6031-j516c-board.bin`, built into the kernel): the hardware -/// record, Globals and the power block that this machine's firmware accepted, as the earlier -/// J516C driver uploaded them (its device-tree-built records with the per-board words of this -/// machine applied). Kept out of the tree; pinned by its SHA-256. The three images are built -/// from the device tree and must match the file's; the file supplies the record's words the -/// device tree does not carry (`board::record::FROM_FILE`). -const BOARD_SHA256: [u8; 32] = [ - 0x4e, 0x03, 0xf2, 0x9f, 0xa4, 0x47, 0xdc, 0xeb, 0xfd, 0x4d, 0x1c, 0x22, 0x5a, 0x64, 0x7b, - 0xfa, 0x31, 0xb3, 0x63, 0x45, 0x8f, 0x28, 0x33, 0x82, 0x3a, 0x5c, 0xa8, 0xa1, 0x0e, 0xdf, - 0xf3, 0xfc, -]; - pub(crate) const CONFIG: Config = Config { name: "T6031 G15C", chip_id: 0x6031, @@ -172,15 +159,12 @@ pub(crate) const CONFIG: Config = Config { ], }, ], - // The pinned board images and measured layout belong to the J516C, not + // The reference words and measured layout belong to the J516C, not // every T6031 board (in particular, not the 14-inch J514C). machine: kernel::c_str!("apple,j516c"), platform: super::Platform::DeviceTree, layout: &LAYOUT, - board: super::Board::Blob { - name: kernel::c_str!("asahi/t6031-j516c-board.bin"), - sha256: BOARD_SHA256, - }, + board: super::Board::DeviceTree(&super::super::board::record::J516C), // Four clusters: the saved-state pointers at +0x2980 of the preemption buffer (0xd80 plus // 0x700 per cluster), UMA PTC 0x6000400020, DUPM 0x3c000000140 (32 N | 96 N << 32 for // N = 10 cores per cluster) and 0x1a0e9 = 0, with the saved-state pointers and the diff --git a/drivers/gpu/drm/asahi/g15/initdata.rs b/drivers/gpu/drm/asahi/g15/initdata.rs index f3f5232e92a28f..c085d455f7a409 100644 --- a/drivers/gpu/drm/asahi/g15/initdata.rs +++ b/drivers/gpu/drm/asahi/g15/initdata.rs @@ -160,58 +160,23 @@ fn templates( /// HardwareDataA word holding the system counter at initdata creation. const HWDATA_A_INIT_TIMESTAMP: usize = 0x424c; -/// Board images of a SoC with a firmware file. The hardware record ([`board::hardware_record`]), -/// Globals and Power ([`board::device_tree`]) are built from the device tree; the file, this -/// machine's own, supplies the record's words the device tree does not carry and is otherwise -/// a cross-check: admission ([`prepare_board`], before any firmware is touched) is refused at -/// the first differing word. Returns the images and the top operating-point frequency in kHz. -fn blob( +/// Board images of a device-tree SoC: the hardware record ([`board::hardware_record`]) from the +/// GPU node, the ADT's sensor rows and the SoC's `reference` words, Globals and Power +/// ([`board::device_tree`]) from the GPU node and that record. Returns the images and the top +/// operating-point frequency in kHz. +fn device_tree_board( dev: &device::Device, cfg: &Config, adt: &GpuAdt, - name: &CStr, - sha256: &[u8; 32], + reference: &board::record::Reference, ) -> Result<(KVVec, KVVec, KVVec, u32)> { - let layout = cfg.layout; - let images = board::blob::load( - dev, - name, - sha256, - [layout.hwdata_size, globals::SIZE, power::SIZE], - )?; - let hw_img = board::hardware_record(dev, cfg, adt, &images.hardware)?; + let hw_img = board::hardware_record(dev, cfg, adt, reference)?; let (glob, pwr, max_power_mw) = board::device_tree(dev, &hw_img)?; - for (what, built, file) in [ - ("hardware record", &hw_img, &images.hardware), - ("Globals", &glob, &images.globals), - ("Power", &pwr, &images.power), - ] { - let diff = if what == "hardware record" { - board::record::cross_check(built, file) - } else { - board::words::first_difference(built, file) - }; - if let Some((at, b, f)) = diff { - dev_err!( - dev, - "board data: device-tree {} differs from {} at +{:#x}: built {:#x}, file {:#x}\n", - what, - name, - at, - b, - f - ); - return Err(EINVAL); - } - } let max_mhz = adt.perf_mhz.iter().copied().max().unwrap_or(0); dev_info!( dev, - "board data: {} images from the device tree match {} ({} record words from the file, {} file words left zero), {} mW maximum power, {} MHz top operating point\n", + "board data: {} images from the device tree and the ADT sensor rows, reference maximum power {} mW and MTR masks, {} MHz top operating point\n", cfg.name, - name, - board::record::FROM_FILE.len(), - board::record::UNNEEDED.len(), max_power_mw, max_mhz ); @@ -234,7 +199,7 @@ pub(crate) fn prepare_board( ) -> Result { match cfg.board { hw::Board::Templates => templates(dev, adt), - hw::Board::Blob { name, sha256 } => blob(dev, cfg, adt, name, &sha256), + hw::Board::DeviceTree(reference) => device_tree_board(dev, cfg, adt, reference), hw::Board::None => Err(ENODEV), } } @@ -296,7 +261,7 @@ pub(crate) fn build( // Board images, verified, then the owned replacements. let (mut hw_img, mut glob, pwr, max_khz) = board_images; - if matches!(cfg.board, hw::Board::Blob { .. }) { + if matches!(cfg.board, hw::Board::DeviceTree(_)) { // This timestamp belongs to initdata creation, not board-data preflight. let now: u64; // SAFETY: Reading the architectural counter has no side effects. @@ -318,10 +283,10 @@ pub(crate) fn build( } put64(&mut hw_img, hwdata::TIMESTAMPS, va::TIMESTAMPS)?; put32(&mut glob, globals::UNK_980, 100)?; - // The device tree's standby time (a machine with a firmware file; the templates' machine + // The device tree's standby time (a device-tree SoC; the templates' machine // has none). let dt_standby = match cfg.board { - hw::Board::Blob { .. } => { + hw::Board::DeviceTree(_) => { let prop = board::words::PROPS[board::words::STANDBY_TIMER]; match dev .of_node() @@ -369,8 +334,8 @@ pub(crate) fn build( globals::IDLE_PREDICTIVE, hw::tuning::IDLE_PREDICTIVE as u32, )?; - // HardwareDataA at +0x4580: maintained S8 request (the templates' machine; a board image - // from a file carries its own machine's words). + // HardwareDataA at +0x4580: maintained S8 request (the templates' machine; a record built + // from the device tree carries its own state words). if cfg.board == hw::Board::Templates { for (offset, value) in [ (0x2c, 8), diff --git a/tools/asahi/g15-zero-words-tests.py b/tools/asahi/g15-zero-words-tests.py index 68b65b9a6b73a2..1439289b1b3247 100644 --- a/tools/asahi/g15-zero-words-tests.py +++ b/tools/asahi/g15-zero-words-tests.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0-only OR MIT -"""Test the actual J516C record builder's narrow zero-word exemption. +"""Test the actual J516C record builder's narrow zero-word exemption and its word sources. Uses synthetic public data and temporary host copies of production Rust modules. No GPU, firmware file, private fixture, kernel build or hardware access. The @@ -9,8 +9,11 @@ and again when the initial PB posting became a module parameter (the tuning constant PB_BLOCKS removed), and when the T6030/T6031 tables were gated on CONFIG_DRM_ASAHI_G15 for the T8122 runtime's UAT adoption (two #[cfg] lines; -the tables are unchanged). Its T6030 configuration module is independently -pinned to the original bytes. +the tables are unchanged), and when the T6031 board source became the +device tree with reference words instead of a board file (the Board enum; +the T6030 tables are unchanged). Its T6030 configuration module is +independently pinned to the original bytes. prepare_board() is pinned too; +it was refreshed for the same Board change (its T6030 arm is unchanged). """ import argparse @@ -26,13 +29,13 @@ GOLDEN_FILES = { "board/data.rs": "7c5ff1077ca3296cbfc9e7144294162b6dea78a3774c38eeed4b0f59a14d7a5c", "board/template.rs": "286872f5474a67534f214e83e659253299f85143f3dc59035fa80894022e2549", - "hw.rs": "aa043d11bad177a0aa38755d14159786ee3313c4f8432f5ed56d6967882d9f01", + "hw.rs": "4d34b69f904c6eb864b7d127763480c01a5e1476a8974184364fa4ba3a90ed63", "power/idle.rs": "4928ecf7699b3ca85ff5143eb6e996efd40d47321e29a238a02c8873bc9f102c", } GOLDEN_FUNCTIONS = { ("hw.rs", "pub(crate) mod t6030 {"): "0079ae08fbe0e51cbf14e48711981a81048d1a1d3fc5495ab8d5c376283a760c", ("initdata.rs", "fn templates("): "c3115c9926d3d7d93317cb8510e93b04f30b0c256811d7137568e822dd93b8ee", - ("initdata.rs", "pub(crate) fn prepare_board("): "c59f968f125397efc63f75a49d3a8543c15c1fd4d50362f15683563398e64bc6", + ("initdata.rs", "pub(crate) fn prepare_board("): "ebef64ab87ef7b1f994eb7fa2a2424d05bc4fc9e88cbfbf18e51570bdfc4222d", ("board.rs", "pub(crate) fn from_adt("): "cecd0b28db5bd94d3b75c1bbb5f35204e8bf0699f74e5b3d08228c59025f6ac2", } @@ -53,7 +56,7 @@ def function(source, marker): use super::*; // Frozen pre-5131caff FROM_FILE offsets from SiliconForest 9f09c85f8d3b. - // This list is deliberately independent of both new lists. + // This list is deliberately independent of the three new lists. const OLD: [usize; 74] = [ 224,232,244,248,252,256,260,264,268,728,732,736,740,744,748, 992,1000,1012,1016,1024,1036,1496,1500,1504,1508,1512,1516, @@ -64,17 +67,32 @@ def function(source, marker): 45908,45912,45932,45940,45948,45956, ]; + const REF: Reference = Reference { + max_power_mw: 54321, + relative_power: [0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007], + fast_die_mask: 0x2111_2222_2333_2444, + alarm_mask: 0x2555_2666_2777_2888, + }; + + fn rows() -> [[u8; 12]; SENSOR_ROW_COUNT] { + [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"].map(|k| { + let mut r = [0x24u8; 12]; + r[..4].copy_from_slice(k); + r + }) + } + + fn row_words() -> Vec { + (SENSOR_ROWS..SENSOR_ROWS + 12 * SENSOR_ROW_COUNT).step_by(4).collect() + } + fn synthetic() -> (Vec, Vec, [Opp; 1], [u32; PROPS.len()]) { let mut file = vec![0u8; SIZE]; for (n, &at) in OLD.iter().enumerate() { put32(&mut file, at, 0x13570000 | (n as u32 + 1)).unwrap(); } - put32(&mut file, MAX_POWER, 54321).unwrap(); - for (n, key) in [b"RT0g", b"RT1g", b"RT2g", b"RT3g", b"RT0r", b"RT0f"].iter().enumerate() { - put32(&mut file, SENSOR_ROWS + 12*n, u32::from_le_bytes(**key)).unwrap(); - } // Public, synthetic OPPs: state0 off; eight voltage groups, five paired. - let mut main = vec![Opp {mhz:0, mv:[125;4], cells:4, mw:0}]; + let mut main = vec![Opp {mhz:0, mv:[125;4], cells:4}]; for (mv, freqs) in [ (600, &[300u32][..]), (650, &[500][..]), (700, &[700][..]), (750, &[800,900][..]), (800, &[950,1000][..]), @@ -82,10 +100,10 @@ def function(source, marker): (950, &[1250,1300][..]), ] { for &mhz in freqs { - main.push(Opp {mhz, mv:[mv;4], cells:4, mw:mhz*40}); + main.push(Opp {mhz, mv:[mv;4], cells:4}); } } - let cs = [Opp {mhz:24, mv:[635,0,0,0], cells:1, mw:0}]; + let cs = [Opp {mhz:24, mv:[635,0,0,0], cells:1}]; let mut props = [1u32; PROPS.len()]; props[PERIOD_MS] = 8; props[AVG_TC_MS] = 32; @@ -102,14 +120,16 @@ def function(source, marker): fn inputs<'a>(main: &'a [Opp], cs: &'a [Opp], props: [u32; PROPS.len()]) -> Inputs<'a> { Inputs {props, zones:&[], main, cs, afr:cs, slots:&[], empty_writable:&[], ttbat:0x100000, usc_base:0x1000000000, user_end:0x2000000000, - timestamps:0xfffffc2ffc000000, max_power_mw:54321} + timestamps:0xfffffc2ffc000000, sensor_rows: rows(), reference: &REF} } #[test] fn old74_is_exact_disjoint_partition() { - assert_eq!(FROM_FILE.len(), 32); + assert_eq!(REFERENCE_WORDS.len(), 14); + assert_eq!(row_words().len(), 18); assert_eq!(UNNEEDED.len(), 42); - let mut union = FROM_FILE.to_vec(); + let mut union = REFERENCE_WORDS.to_vec(); + union.extend(row_words()); union.extend_from_slice(&UNNEEDED); union.sort_unstable(); assert!(union.windows(2).all(|w| w[0] < w[1]), "duplicate or overlapping offsets"); @@ -119,30 +139,30 @@ def function(source, marker): } #[test] - fn actual_from_file_copies_only_retained32() { - let (file, _, _, _) = synthetic(); - let mut out = vec![0u8; SIZE]; - from_file(&mut out, &file).unwrap(); - for at in (0..SIZE).step_by(4) { - let expected = if FROM_FILE.contains(&at) {get32(&file, at).unwrap()} else {0}; - assert_eq!(get32(&out, at).unwrap(), expected, "copy offset +{at:#x}"); - } - } - - #[test] - fn actual_builder_leaves42_zero_and_preserves32() { + fn actual_builder_leaves42_zero_and_writes_the_32_from_their_sources() { let (file, main, cs, props) = synthetic(); - let mut out = vec![0u8; SIZE]; - from_file(&mut out, &file).unwrap(); + // Whatever the buffer held is not carried into the record. + let mut out = file.clone(); build(&mut out, &inputs(&main, &cs, props)).unwrap(); for &at in UNNEEDED.iter() { assert_ne!(get32(&file, at).unwrap(), 0, "fixture must exercise a real difference"); assert_eq!(get32(&out, at).unwrap(), 0, "built offset +{at:#x}"); } - for &at in FROM_FILE.iter() { - assert_eq!(get32(&out, at).unwrap(), get32(&file, at).unwrap(), "retained +{at:#x}"); + let mut reference = Vec::new(); + for r in REF.relative_power { reference.push(r); } + reference.push(REF.max_power_mw); + for m in [REF.fast_die_mask, REF.fast_die_mask, REF.alarm_mask] { + reference.push(m as u32); + reference.push((m >> 32) as u32); + } + for (&at, &want) in REFERENCE_WORDS.iter().zip(reference.iter()) { + assert_eq!(get32(&out, at).unwrap(), want, "reference +{at:#x}"); + } + let flat: Vec = rows().concat(); + for (k, &at) in row_words().iter().enumerate() { + assert_eq!(&out[at..at + 4], &flat[4 * k..4 * k + 4], "sensor row +{at:#x}"); } - // Model a matching board file with nonzero legacy values only at the42. + // Model a matching machine record with nonzero legacy values only at the42. let mut matching = out.clone(); for &at in UNNEEDED.iter() { put32(&mut matching, at, get32(&file, at).unwrap()).unwrap(); @@ -172,7 +192,7 @@ def function(source, marker): MUTANTS = { "broad-cross-check-exemption": ("cross_check", "UNNEEDED.contains(&at)", "true"), "nonzero-built-exempted": ("cross_check", "b == 0 && UNNEEDED.contains(&at)", "UNNEEDED.contains(&at)"), - "legacy-copy-reintroduced": ("from_file", "FROM_FILE.iter()", "FROM_FILE.iter().chain(UNNEEDED.iter())"), + "stale-buffer-kept": ("build", "out.fill(0);", ""), } @@ -250,7 +270,8 @@ def run(name, record): mutant = run(name, changed) if not mutant.returncode: raise RuntimeError("mutant survived: " + name) - regression = ("actual_from_file_copies_only_retained32" if func == "from_file" + regression = ("actual_builder_leaves42_zero_and_writes_the_32_from_their_sources" + if func == "build" else "actual_cross_check_exempts_only_zero_at_exact42") if regression + " ... FAILED" not in mutant.stdout: raise RuntimeError("new regression did not catch mutant: " + name) From 23a7bd131f1b9978d23038bdd61f46479bc3b51d Mon Sep 17 00:00:00 2001 From: Naeem Malik Date: Wed, 28 Oct 2026 05:02:38 +0530 Subject: [PATCH 2/3] tools/asahi: g15-maintained-handoff-tests: define the sensor-row count The handoff test compiles GpuAdt and the T6030 ADT parser from adt.rs on the host. GpuAdt now carries the T6031 sensor rows, sized by board::record::SENSOR_ROW_COUNT, which the generated source did not define, so the test no longer compiled. Take that production constant from board/record.rs into a generated record module, and hash board/record.rs with the other sources in the receipt. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Naeem Malik --- tools/asahi/g15-maintained-handoff-tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/asahi/g15-maintained-handoff-tests.py b/tools/asahi/g15-maintained-handoff-tests.py index 4dd2961d3a8ace..0c07c2eda3065e 100644 --- a/tools/asahi/g15-maintained-handoff-tests.py +++ b/tools/asahi/g15-maintained-handoff-tests.py @@ -156,13 +156,16 @@ def main(): p = argparse.ArgumentParser(description=__doc__); p.add_argument('--output', type=Path); p.add_argument('--mutations', action='store_true'); a=p.parse_args() out=(a.output or Path(tempfile.mkdtemp(prefix='g15-maintained-handoff-'))).resolve(); out.mkdir(parents=True, exist_ok=True) adt=(G15/'adt.rs').read_text(); boot=(G15/'boot.rs').read_text(); hw=(G15/'hw.rs').read_text() + record=(G15/'board/record.rs').read_text() helpers='\n'.join(item(adt,m) for m in ["pub(crate) struct GpuAdt", "struct Adt<'a>", 'fn le32(', 'fn le64(', 'fn strncmp_eq(', "impl Adt<'_>", 'fn collect_perf(', 'fn parse(', 'fn admit_regions(']) constants='\n'.join(re.search(r'pub\(crate\) const '+n+r':[^;]+;', hw).group(0) for n in ['PAGE','PA_MASK','TTBS_SIZE','TABLES_SIZE','HANDOFF_SIZE','SHARED_L2_SIZE','UPPER']) + # GpuAdt carries the T6031 sensor rows, sized by the record builder's production constant. + record_constants=re.search(r'pub\(crate\) const SENSOR_ROW_COUNT:[^;]+;', record).group(0) start=hw.index(' firmware: &['); start=hw.index('[',start); end=start+1; depth=1 while depth: depth+=(hw[end]=='[')-(hw[end]==']'); end+=1 firmware='static BUILDS: &[FirmwareBuild] = &'+hw[start:end]+';\n'+item(hw,'pub(crate) struct FirmwareBuild') constructor=item(boot,' fn new(dev: &device::Device, shared: &Arc)') - source=PREFIX+'\nmod hw {\n'+constants+'\n}\n'+helpers+'\n'+firmware+'\nimpl Rtkit {\n'+constructor+'\n}\n'+CASES + source=PREFIX+'\nmod hw {\n'+constants+'\n}\nmod record {\n'+record_constants+'\n}\n'+helpers+'\n'+firmware+'\nimpl Rtkit {\n'+constructor+'\n}\n'+CASES variants={'actual':source} if a.mutations: variants['skip-owner-check']=source.replace('if !glue::reserved_uat(i as u32, r.start, r.end - r.start) {','if false {') @@ -170,7 +173,7 @@ def main(): variants['drop-reserved-ram']=source.replace('Some(dram_base..dram_base + dram_size), true)?;', 'Some(dram_base..dram_base + dram_size), false)?;') variants['require-obsolete-b0-profile']=source.replace(' FirmwareBuild {\n name: "J516S B0', ' #[cfg(CONFIG_SILICONFOREST_NATIVE_GPU)]\n FirmwareBuild {\n name: "J516S B0') variants['named-mailbox']=source.replace('cookie, core::ptr::null(), 0, &RTKIT_OPS', 'cookie, c"mbox".as_ptr(), 0, &RTKIT_OPS') - receipt={'scope':__doc__,'sources':{p:hashlib.sha256(t.encode()).hexdigest() for p,t in [('adt.rs',adt),('boot.rs',boot),('hw.rs',hw)]},'variants':{}} + receipt={'scope':__doc__,'sources':{p:hashlib.sha256(t.encode()).hexdigest() for p,t in [('adt.rs',adt),('boot.rs',boot),('hw.rs',hw),('board/record.rs',record)]},'variants':{}} for name,text in variants.items(): if name!='actual' and text==source: raise SystemExit('NOT_APPLIED '+name) src=out/(name+'.rs');src.write_text(text); binary=out/name From 79f789160c73e1413cf817a1fb4c0beb8ac914f7 Mon Sep 17 00:00:00 2001 From: Naeem Malik Date: Wed, 28 Oct 2026 06:55:55 +0530 Subject: [PATCH 3/3] drm/asahi: g15: t6031: log the J516C hardware record digest Log the SHA-256 of the hardware record built from the device tree, the ADT sensor rows and the reference words, as the T6030 templates path does for a record that differs from its reference. A report from another J516C then shows at once whether its record matches. Also bring the prepare_board() comment up to date: the T6031 arm no longer has a digest to check or a firmware file to request. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Naeem Malik --- drivers/gpu/drm/asahi/g15/initdata.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g15/initdata.rs b/drivers/gpu/drm/asahi/g15/initdata.rs index c085d455f7a409..e53cb44eff43ae 100644 --- a/drivers/gpu/drm/asahi/g15/initdata.rs +++ b/drivers/gpu/drm/asahi/g15/initdata.rs @@ -180,6 +180,7 @@ fn device_tree_board( max_power_mw, max_mhz ); + dev_info!(dev, "board data sha256 {}\n", Hex(&glue::sha256(&hw_img))); let max_khz = max_mhz.checked_mul(1000).ok_or_else(|| { dev_err!(dev, "board data: top operating point {} MHz does not fit in kHz\n", max_mhz); EINVAL @@ -190,8 +191,9 @@ fn device_tree_board( /// Verified board bytes, prepared before any GPU firmware is started. pub(crate) type BoardImages = (KVVec, KVVec, KVVec, u32); -/// Check availability, digest, layout and machine-derived tables before firmware start. -/// Keep the verified bytes so a later firmware request cannot change the admitted data. +/// Build and check the board images before any GPU firmware is started: the templates against +/// their digests and layout, or the device-tree record from the GPU node, the ADT and the SoC's +/// reference words. pub(crate) fn prepare_board( dev: &device::Device, cfg: &Config,