diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index d29b1ab0310aa..c0a32c3badd07 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -58,7 +58,9 @@ properties: - description: GPU calibration blob, preserved firmware text or firmware kernel-window page tables - - description: Shared global variables or preserved firmware data + - description: + Shared global variables, preserved firmware data or text segment of + the loaded GPU firmware memory-region-names: minItems: 3 @@ -68,7 +70,7 @@ properties: - const: handoff - enum: [hw-cal-a, shared-l2, fw-data] - enum: [hw-cal-b, fw-text, l2-pagetables] - - enum: [globals, fw-data] + - enum: [globals, fw-data, fw-text] apple,pmp: $ref: /schemas/types.yaml#/definitions/phandle @@ -212,6 +214,9 @@ allOf: - description: Region in which the GPU firmware builds the page tables of its kernel window + - description: + Text segment of the loaded GPU firmware, which identifies the + firmware build memory-region-names: items: - const: ttbs @@ -219,6 +224,7 @@ allOf: - const: handoff - const: fw-data - const: l2-pagetables + - const: fw-text required: - power-domains - operating-points-v2 diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 27aa13a9868c0..9d245b8111d6f 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -74,6 +74,15 @@ iommu-addresses = <&isp 0x100 0x0 0x0 0x2000000>; }; + /* + * Text segment of the loaded GPU firmware (ADT gfx-asc + * segment-ranges[0]); the driver identifies the build from it. + */ + gpu_fw_text: gpu-fw-text@10000be4000 { + reg = <0x100 0x00be4000 0x0 0x60000>; + no-map; + }; + /* * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it * places its crash log here. @@ -820,9 +829,9 @@ */ &gpu { memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, - <&gpu_fw_data>, <&uat_l2_pagetables>; + <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; memory-region-names = "ttbs", "pagetables", "handoff", "fw-data", - "l2-pagetables"; + "l2-pagetables", "fw-text"; operating-points-v2 = <&gpu_opp>; apple,cs-opp = <&gpu_cs_opp>; apple,afr-opp = <&gpu_afr_opp>; diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig index 954df95b6b7fa..3903ee68b162b 100644 --- a/drivers/gpu/drm/asahi/Kconfig +++ b/drivers/gpu/drm/asahi/Kconfig @@ -21,6 +21,7 @@ config DRM_ASAHI select RUST_DRM_GEM_SHMEM_HELPER select RUST_DRM_GPUVM select RUST_APPLE_RTKIT + select CRYPTO_LIB_SHA256 help DRM driver for Apple AGX GPUs (G13x, found in the M1 SoC family) @@ -29,7 +30,6 @@ config DRM_ASAHI_G16 depends on DRM_ASAHI && ARM64 depends on APPLE_PMP=y || APPLE_PMP=DRM_ASAHI depends on THERMAL_OF - select CRYPTO_LIB_SHA256 help Rust Asahi backend for the qualified J713/T8132 GPU firmware. Load asahi explicitly after the native display and GFX TVM supplier diff --git a/drivers/gpu/drm/asahi/Makefile b/drivers/gpu/drm/asahi/Makefile index 34413335fe49b..0523740332290 100644 --- a/drivers/gpu/drm/asahi/Makefile +++ b/drivers/gpu/drm/asahi/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_DRM_ASAHI) += asahi.o -asahi-y := asahi_core.o sysfs.o +asahi-y := asahi_core.o fwid.o sysfs.o asahi-$(CONFIG_DRM_ASAHI_G15) += g15/glue.o diff --git a/drivers/gpu/drm/asahi/asahi_core.rs b/drivers/gpu/drm/asahi/asahi_core.rs index dc1f46c191209..e6b4d0c9a088f 100644 --- a/drivers/gpu/drm/asahi/asahi_core.rs +++ b/drivers/gpu/drm/asahi/asahi_core.rs @@ -12,6 +12,7 @@ mod driver; mod event; mod file; mod float; +mod fwid; mod fw; mod gem; mod g17; diff --git a/drivers/gpu/drm/asahi/fwid.c b/drivers/gpu/drm/asahi/fwid.c new file mode 100644 index 0000000000000..5779380ff716a --- /dev/null +++ b/drivers/gpu/drm/asahi/fwid.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +// +// SHA-256 for the firmware identity check (fwid.rs). + +#include +#include + +void asahi_fw_sha256(const u8 *data, size_t len, u8 *out); + +void asahi_fw_sha256(const u8 *data, size_t len, u8 *out) +{ + sha256(data, len, out); +} diff --git a/drivers/gpu/drm/asahi/fwid.rs b/drivers/gpu/drm/asahi/fwid.rs new file mode 100644 index 0000000000000..36a6fa45581be --- /dev/null +++ b/drivers/gpu/drm/asahi/fwid.rs @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Exact GPU firmware identity. +//! +//! A build is the SHA-256 of the loaded TEXT with the 8 entropy bytes of its two entropy records +//! (`GKTS` and `ECAP`, each a tag, the length 8 and the bytes) zeroed, plus the TEXT and DATA +//! sizes and the records' offsets. An unknown build is refused with its +//! measured identity, so nothing is written to a firmware the driver was not written for. + +use core::ops::Range; +use kernel::{bindings, device, fmt, prelude::*}; + +/// A qualified GPU firmware build, identified by its loaded TEXT. +pub(crate) struct FirmwareBuild { + pub(crate) name: &'static str, + pub(crate) text_size: u64, + pub(crate) data_size: u64, + /// Offsets of the GKTS and ECAP entropy records in TEXT; their 8 entropy bytes are zeroed + /// before hashing. + pub(crate) entropy: [u32; 2], + pub(crate) text_sha256: [u8; 32], +} + +extern "C" { + fn asahi_fw_sha256(data: *const u8, len: usize, out: *mut u8); +} + +/// SHA-256 (`sha256()`). +pub(crate) fn sha256(data: &[u8]) -> [u8; 32] { + let mut out = [0u8; 32]; + // SAFETY: `data` is valid for its length and `out` holds the 32-byte digest. + unsafe { asahi_fw_sha256(data.as_ptr(), data.len(), out.as_mut_ptr()) }; + out +} + +/// Lowercase hex of a byte string, as `%*phN` prints it. +pub(crate) struct Hex<'a>(pub(crate) &'a [u8]); + +impl fmt::Display for Hex<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for b in self.0 { + write!(f, "{:02x}", b)?; + } + Ok(()) + } +} + +/// Copies `size` bytes at physical `phys`, a range outside the kernel's System RAM, into a +/// private copy through a temporary write-combined mapping. +pub(crate) fn copy_physical(phys: u64, size: usize) -> Result> { + // SAFETY: memremap() of a boot-loader range creates a temporary mapping; it has no other + // side effects. + let map = unsafe { bindings::memremap(phys, size, bindings::MEMREMAP_WC as _) }; + if map.is_null() { + return Err(ENOMEM); + } + let copy = KVVec::from_elem(0u8, size, GFP_KERNEL).map(|mut copy| { + // SAFETY: `map` is a live mapping of `size` bytes and `copy` holds `size` bytes; the two do + // not overlap. + unsafe { core::ptr::copy_nonoverlapping(map as *const u8, copy.as_mut_ptr(), size) }; + copy + }); + // SAFETY: `map` came from memremap() above and is not used afterwards. + unsafe { bindings::memunmap(map) }; + Ok(copy?) +} + +/// Entropy record tags, in TEXT order. +const ENTROPY_TAGS: [[u8; 4]; 2] = [*b"GKTS", *b"ECAP"]; + +/// The SHA-256 of `image` with the 8 entropy bytes of the records at `at` zeroed, or `None` if a +/// record (tag, then the length 8) is not there. `image` is left as it was. +fn digest_at(image: &mut [u8], at: [u32; 2]) -> Option<[u8; 32]> { + let mut saved = [[0u8; 8]; 2]; + for ((off, tag), save) in at.into_iter().zip(ENTROPY_TAGS).zip(saved.iter_mut()) { + let off = off as usize; + if image.get(off..off + 4) != Some(tag.as_slice()) + || image.get(off + 4..off + 8) != Some(8u32.to_le_bytes().as_slice()) + { + return None; + } + save.copy_from_slice(image.get(off + 8..off + 16)?); + } + for off in at { + image[off as usize + 8..off as usize + 16].fill(0); + } + let digest = sha256(image); + for (off, save) in at.into_iter().zip(saved) { + image[off as usize + 8..off as usize + 16].copy_from_slice(&save); + } + Some(digest) +} + +/// The first record of each tag in `image`, to report an unknown build. +fn find_entropy(image: &[u8]) -> Option<[u32; 2]> { + let mut found = [0u32; 2]; + for (slot, tag) in found.iter_mut().zip(ENTROPY_TAGS) { + let mut record = [0u8; 8]; + record[..4].copy_from_slice(&tag); + record[4..].copy_from_slice(&8u32.to_le_bytes()); + *slot = u32::try_from(image.windows(8).position(|w| w == record)?).ok()?; + } + Some(found) +} + +/// Identifies the firmware build loaded at `text` (DATA `data_size` bytes) among `builds`, +/// before any firmware-visible write. +pub(crate) fn identify( + dev: &device::Device, + text: Range, + data_size: u64, + builds: &'static [FirmwareBuild], +) -> Result<&'static FirmwareBuild> { + let size = usize::try_from(text.end - text.start)?; + let mut copy = copy_physical(text.start, size)?; + let again = copy_physical(text.start, size)?; + if copy.as_slice() != again.as_slice() { + dev_err!(dev, "GPU firmware TEXT changed while it was measured\n"); + return Err(EAGAIN); + } + for build in builds { + if build.text_size == size as u64 + && build.data_size == data_size + && digest_at(&mut copy, build.entropy) == Some(build.text_sha256) + { + dev_info!(dev, "GPU firmware build: {}\n", build.name); + return Ok(build); + } + } + let Some((entropy, digest)) = + find_entropy(©).and_then(|at| Some((at, digest_at(&mut copy, at)?))) + else { + dev_err!(dev, "unsupported GPU firmware build: TEXT {:#x} has no entropy records\n", size); + return Err(EINVAL); + }; + dev_err!( + dev, + "unsupported GPU firmware build: TEXT {:#x} DATA {:#x} entropy {:#x}/{:#x} sha256 {}\n", + size, + data_size, + entropy[0], + entropy[1], + Hex(&digest) + ); + for build in builds { + dev_err!(dev, "supported: {}\n", build.name); + } + Err(EINVAL) +} + +#[kunit_tests(asahi_fwid)] +mod tests { + use super::*; + + fn image() -> [u8; 64] { + let mut image = [0x5a; 64]; + for (off, tag) in [0usize, 32].into_iter().zip(ENTROPY_TAGS) { + image[off..off + 4].copy_from_slice(&tag); + image[off + 4..off + 8].copy_from_slice(&8u32.to_le_bytes()); + } + image + } + + #[test] + fn entropy_is_normalized_without_changing_the_image() { + let mut first = image(); + let original = first; + let digest = digest_at(&mut first, [0, 32]).unwrap(); + assert_eq!(first, original); + let mut second = first; + second[8..16].fill(0xff); + second[40..48].fill(0x01); + assert_eq!(digest_at(&mut second, [0, 32]), Some(digest)); + second[63] ^= 1; + assert_ne!(digest_at(&mut second, [0, 32]), Some(digest)); + } + + #[test] + fn malformed_records_are_refused_without_changing_the_image() { + let mut image = image(); + assert_eq!(digest_at(&mut image, [0, 56]), None); + image[36] = 7; + let original = image; + assert_eq!(digest_at(&mut image, [0, 32]), None); + assert_eq!(image, original); + assert_eq!(find_entropy(&image), None); + } +} diff --git a/drivers/gpu/drm/asahi/g16/adt.rs b/drivers/gpu/drm/asahi/g16/adt.rs index e78c12fd57252..a34ecc901f615 100644 --- a/drivers/gpu/drm/asahi/g16/adt.rs +++ b/drivers/gpu/drm/asahi/g16/adt.rs @@ -191,7 +191,7 @@ pub(crate) fn read(dev: &device::Device) -> Result { "M4 original ADT performance source PA={:#x} bytes={:#x} sha256={} states={}\n", source_pa, copy.len(), - super::identity::Hex(&glue::sha256(©)), + crate::fwid::Hex(&crate::fwid::sha256(©)), performance.count ); let [ttbs, tables, handoff, shared_l2, text, data] = regions; diff --git a/drivers/gpu/drm/asahi/g16/boot.rs b/drivers/gpu/drm/asahi/g16/boot.rs index 9d2f856d3b8aa..450d1c25268ab 100644 --- a/drivers/gpu/drm/asahi/g16/boot.rs +++ b/drivers/gpu/drm/asahi/g16/boot.rs @@ -30,7 +30,7 @@ use super::fw::initdata::control; use super::hw; use super::initdata::{self, InitData}; use super::object::Objects; -use super::{identity, regs, uat, Config, Shared}; +use super::{regs, uat, Config, Shared}; use crate::driver::AsahiDrmDevice; use crate::{mmu, regs as shared_regs}; use kernel::io::Io; @@ -41,7 +41,6 @@ pub(crate) mod glue { use kernel::{device, prelude::*}; extern "C" { - fn asahi_g16_sha256(data: *const u8, len: usize, out: *mut u8); fn asahi_g16_board() -> bool; fn asahi_g16_hold_power(dev: *mut core::ffi::c_void) -> core::ffi::c_int; fn asahi_g16_module_get(); @@ -61,14 +60,6 @@ pub(crate) mod glue { ) -> core::ffi::c_int; } - /// SHA-256 (`sha256()`). - pub(crate) fn sha256(data: &[u8]) -> [u8; 32] { - let mut out = [0u8; 32]; - // SAFETY: `data` is valid for its length and `out` holds the 32-byte digest. - unsafe { asahi_g16_sha256(data.as_ptr(), data.len(), out.as_mut_ptr()) }; - out - } - /// A qualified J713/T8132 board. pub(crate) fn board() -> bool { // SAFETY: Reads the machine compatible strings only. @@ -445,7 +436,7 @@ pub(crate) fn admit( } let adt = adt::read(dev).inspect_err(|e| dev_err!(dev, "ADT GPU regions not admitted: {:?}\n", e))?; - identity::firmware(dev, &adt, cfg.firmware) + crate::fwid::identify(dev, adt.text.clone(), adt.data.end - adt.data.start, cfg.firmware) .inspect_err(|e| dev_err!(dev, "loaded GFX TEXT identity mismatch: {:?}\n", e))?; dev_info!(dev, "M4: firmware identity accepted\n"); // Firmware objects are cleaned to the point of coherency by their DMA mapping: that needs diff --git a/drivers/gpu/drm/asahi/g16/glue.c b/drivers/gpu/drm/asahi/g16/glue.c index 3031f92f54b42..0160187a0dd2c 100644 --- a/drivers/gpu/drm/asahi/g16/glue.c +++ b/drivers/gpu/drm/asahi/g16/glue.c @@ -3,7 +3,6 @@ * G16 (T8132, M4) helpers that have no Rust bindings: static inlines, * macros and THIS_MODULE. Built only with CONFIG_DRM_ASAHI_G16. */ -#include #include #include #include @@ -23,7 +22,6 @@ #include #include -void asahi_g16_sha256(const u8 *data, size_t len, u8 *out); bool asahi_g16_board(void); int asahi_g16_hold_power(struct device *dev); void asahi_g16_module_get(void); @@ -145,12 +143,6 @@ int asahi_g16_cooling_sample(struct asahi_g16_cooling *cooling, u64 now, return ret; } -/* SHA-256 of the firmware TEXT and the board templates. */ -void asahi_g16_sha256(const u8 *data, size_t len, u8 *out) -{ - sha256(data, len, out); -} - /* Whether this is a qualified J713/T8132 board. */ bool asahi_g16_board(void) { diff --git a/drivers/gpu/drm/asahi/g16/hw.rs b/drivers/gpu/drm/asahi/g16/hw.rs index c68617064a94d..696b4bc9f4062 100644 --- a/drivers/gpu/drm/asahi/g16/hw.rs +++ b/drivers/gpu/drm/asahi/g16/hw.rs @@ -18,15 +18,7 @@ pub(crate) struct HwId { pub(crate) counts: u32, } -/// A qualified GPU firmware build, identified by its loaded TEXT. -pub(crate) struct FirmwareBuild { - pub(crate) name: &'static str, - pub(crate) text_size: u64, - pub(crate) data_size: u64, - /// Offset of the GKTS marker in TEXT; the 8 entropy bytes after it are zeroed before hashing. - pub(crate) marker: u32, - pub(crate) text_sha256: [u8; 32], -} +pub(crate) use crate::fwid::FirmwareBuild; /// The J713/T8132 configuration. Hardware admission also checks the exact TEXT image. pub(crate) mod t8132 { @@ -50,7 +42,7 @@ pub(crate) mod t8132 { name: "Apple GFX 25G83 (normalized loaded image)", text_size: 0x60000, data_size: 0x13c000, - marker: 0x5c8df, + entropy: [0x5c8df, 0x5c9c2], text_sha256: [ 0x5f, 0x65, 0xf6, 0x14, 0x58, 0x72, 0x54, 0x70, 0xb4, 0xb0, 0xc0, 0x20, 0x28, 0x47, 0x7b, 0xf2, 0xea, 0x57, 0x3a, 0xfb, 0x4e, 0x0c, 0x49, 0x6e, 0xf9, 0x95, @@ -63,7 +55,7 @@ pub(crate) mod t8132 { name: "Apple GFX 25G83 (normalized loaded image, 8-core J713)", text_size: 0x60000, data_size: 0x13c000, - marker: 0x5c8df, + entropy: [0x5c8df, 0x5c9c2], text_sha256: [ 0x3b, 0x36, 0x8e, 0xe4, 0x2d, 0xcc, 0xc6, 0x26, 0xe8, 0x49, 0x67, 0xff, 0x40, 0x87, 0x64, 0x71, 0x0a, 0x64, 0xbf, 0xf1, 0x59, 0x4f, 0x3c, 0xfd, 0x2c, 0x6e, diff --git a/drivers/gpu/drm/asahi/g16/identity.rs b/drivers/gpu/drm/asahi/g16/identity.rs deleted file mode 100644 index 466a97867a9b4..0000000000000 --- a/drivers/gpu/drm/asahi/g16/identity.rs +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT - -//! Exact firmware and hardware identity. -//! -//! The firmware build is the SHA-256 of TEXT with the 8 entropy bytes after the single -//! `GKTS\x08\0\0\0` marker zeroed, plus the segment sizes, looked up in -//! [`super::hw::t8132::CONFIG`]`.firmware`; the hardware revision is checked against its -//! `hw_ids` at start ([`super::boot`]). An unknown build or revision is refused with its measured -//! identity. The canonical object VAs, the initdata graph, the board templates and the work -//! layouts all belong to one build, so nothing is written before the build is identified. - -use kernel::{bindings, device, fmt, prelude::*}; - -use super::boot::glue; -use super::hw::FirmwareBuild; - -/// Lowercase hex of a byte string, as `%*phN` prints it. -pub(crate) struct Hex<'a>(pub(crate) &'a [u8]); - -impl fmt::Display for Hex<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for b in self.0 { - write!(f, "{:02x}", b)?; - } - Ok(()) - } -} - -/// Copies `size` bytes at physical `phys`, a range outside the kernel's System RAM, into a -/// private copy through a temporary write-combined mapping. -pub(crate) fn copy_physical(phys: u64, size: usize) -> Result> { - // SAFETY: memremap() of a boot-loader range creates a temporary mapping; it has no other - // side effects. - let map = unsafe { bindings::memremap(phys, size, bindings::MEMREMAP_WC as _) }; - if map.is_null() { - return Err(ENOMEM); - } - let copy = KVVec::from_elem(0u8, size, GFP_KERNEL).map(|mut copy| { - // SAFETY: `map` is a live mapping of `size` bytes and `copy` holds `size` bytes; the two do - // not overlap. - unsafe { core::ptr::copy_nonoverlapping(map as *const u8, copy.as_mut_ptr(), size) }; - copy - }); - // SAFETY: `map` came from memremap() above and is not used afterwards. - unsafe { bindings::memunmap(map) }; - Ok(copy?) -} - -/// Both unaligned entropy records of the qualified J713/25G83 TEXT image. -const ENTROPY: [(usize, &[u8; 4]); 2] = [(0x5c8df, b"GKTS"), (0x5c9c2, b"ECAP")]; - -/// Identifies the loaded firmware build before any firmware-visible write. -pub(crate) fn firmware( - dev: &device::Device, - adt: &super::adt::GpuAdt, - builds: &'static [FirmwareBuild], -) -> Result<&'static FirmwareBuild> { - let size = usize::try_from(adt.text.end - adt.text.start)?; - let data_size = adt.data.end - adt.data.start; - let mut copy = copy_physical(adt.text.start, size)?; - let again = copy_physical(adt.text.start, size)?; - if copy.as_slice() != again.as_slice() { - dev_err!(dev, "G16: firmware TEXT changed during stopped admission\n"); - return Err(EAGAIN); - } - for (at, tag) in ENTROPY { - if copy.get(at..at + 4) != Some(tag.as_slice()) - || copy.get(at + 4..at + 8) != Some(8u32.to_le_bytes().as_slice()) - || at + 16 > size - { - dev_err!(dev, "G16: missing firmware entropy record at {:#x}\n", at); - return Err(EINVAL); - } - copy[at + 8..at + 16].fill(0); - } - let at = ENTROPY[0].0; - let digest = glue::sha256(©); - for build in builds { - if build.text_size == size as u64 - && build.data_size == data_size - && build.marker as usize == at - && build.text_sha256 == digest - { - dev_info!(dev, "GPU firmware build: {}\n", build.name); - return Ok(build); - } - } - dev_err!( - dev, - "unsupported GPU firmware build: TEXT {:#x} DATA {:#x} GKTS {:#x} sha256 {}\n", - size, - data_size, - at, - Hex(&digest) - ); - for build in builds { - dev_err!(dev, "supported: {}\n", build.name); - } - Err(EINVAL) -} diff --git a/drivers/gpu/drm/asahi/g16/mod.rs b/drivers/gpu/drm/asahi/g16/mod.rs index 397c32f9ac2b6..c5c9829880e98 100644 --- a/drivers/gpu/drm/asahi/g16/mod.rs +++ b/drivers/gpu/drm/asahi/g16/mod.rs @@ -56,7 +56,6 @@ pub(crate) mod fw; mod gate; mod graph; pub(crate) mod hw; -mod identity; mod initdata; mod job; mod object; diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 2f7b598733368..59f1bb4714fc0 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -64,12 +64,15 @@ pub(crate) struct Variant { pub(crate) programs: super::render::RegisterPrograms, } -/// A firmware build a SoC supports, identified by the macOS release it ships with. +/// A firmware build a SoC supports, selected by the macOS release it ships with and proven by +/// its loaded TEXT. pub(crate) struct Build { /// macOS version (major, minor, patch). pub(crate) os_version: [u32; 3], /// Name, for the log. pub(crate) name: &'static str, + /// The loaded image, measured from the `fw-text` region. + pub(crate) image: crate::fwid::FirmwareBuild, /// Layouts and rules of the build. pub(crate) variant: &'static Variant, } @@ -79,8 +82,8 @@ pub(crate) enum Firmware { /// The SoC runs a single build, which is not identified (A18 Pro). Fixed(&'static Variant), /// One of these builds, selected by the macOS version that the boot loader reports in - /// `apple,firmware-version`: the release whose boot firmware loaded the GPU firmware, and - /// with it this firmware build. Any other version is refused. + /// `apple,firmware-version` (the release whose boot firmware loaded the GPU firmware) and + /// then proven by the loaded TEXT. Any other version or image is refused. Identified(&'static [Build]), } diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 2999daec47015..897a982b3728a 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -1103,6 +1103,7 @@ impl Gpu { let version = Self::os_firmware_version(dev); if let Some(build) = builds.iter().find(|build| Some(build.os_version) == version) { dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); + Self::identify_image(dev, build).map_err(|_| ENODEV)?; return Ok(build.variant); } dev_err!( @@ -1116,6 +1117,24 @@ impl Gpu { Err(ENODEV) } + /// Checks that the loaded TEXT (the `fw-text` region) is `build`'s image, with the DATA size + /// of the `fw-data` region. The version property only says which build to expect. + fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { + let node = dev.as_ref().as_ref().of_node().ok_or(EINVAL)?; + let text = node.reserved_mem_region_to_resource_byname(c_str!("fw-text"))?; + let data = node.reserved_mem_region_to_resource_byname(c_str!("fw-data"))?; + let start = text.start(); + let end = start.checked_add(text.size()).ok_or(EINVAL)?; + crate::fwid::identify( + dev.as_ref().as_ref(), + start..end, + data.size(), + core::slice::from_ref(&build.image), + ) + .inspect_err(|e| dev_err!(dev.as_ref(), "GPU firmware image not admitted: {:?}\n", e))?; + Ok(()) + } + /// The macOS version the boot loader reports for the boot firmware that loaded the GPU /// firmware: `apple,firmware-version` of the GPU node, which m1n1 writes from the ADT. fn os_firmware_version(dev: &AsahiDrmDevice) -> Option<[u32; 3]> { diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 7a4d6f288dc3d..8df2cff4f6541 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -62,6 +62,17 @@ pub(crate) const CONFIG: Config = Config { firmware: fw::variant::Firmware::Identified(&[fw::variant::Build { os_version: [26, 6, 2], name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)", + image: crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6040 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0x03, 0xdf, 0x75, 0x48, 0x4a, 0x7f, 0xdd, 0x6e, 0x4b, 0x47, 0x7f, 0x95, 0x6c, 0xaa, + 0x5b, 0x44, 0x06, 0xdf, 0xff, 0x4d, 0x19, 0x17, 0x28, 0x62, 0x7f, 0x53, 0x10, 0xe0, + 0x63, 0x87, 0x6d, 0xcd, + ], + }, variant: &fw::variant::V25G83, }]), // Measured on the 20-core M4 Pro; other core counts are refused at probe.