Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Documentation/devicetree/bindings/gpu/apple,agx.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -212,13 +214,17 @@ 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
- const: pagetables
- const: handoff
- const: fw-data
- const: l2-pagetables
- const: fw-text
required:
- power-domains
- operating-points-v2
Expand Down
13 changes: 11 additions & 2 deletions arch/arm64/boot/dts/apple/t6040-j616s.dts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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>;
Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/asahi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/asahi/Makefile
Original file line number Diff line number Diff line change
@@ -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

Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/asahi/asahi_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod driver;
mod event;
mod file;
mod float;
mod fwid;
mod fw;
mod gem;
mod g17;
Expand Down
13 changes: 13 additions & 0 deletions drivers/gpu/drm/asahi/fwid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
//
// SHA-256 for the firmware identity check (fwid.rs).

#include <crypto/sha2.h>
#include <linux/types.h>

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);
}
188 changes: 188 additions & 0 deletions drivers/gpu/drm/asahi/fwid.rs
Original file line number Diff line number Diff line change
@@ -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<KVVec<u8>> {
// 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<u64>,
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(&copy).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);
}
}
2 changes: 1 addition & 1 deletion drivers/gpu/drm/asahi/g16/adt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub(crate) fn read(dev: &device::Device) -> Result<GpuAdt> {
"M4 original ADT performance source PA={:#x} bytes={:#x} sha256={} states={}\n",
source_pa,
copy.len(),
super::identity::Hex(&glue::sha256(&copy)),
crate::fwid::Hex(&crate::fwid::sha256(&copy)),
performance.count
);
let [ttbs, tables, handoff, shared_l2, text, data] = regions;
Expand Down
13 changes: 2 additions & 11 deletions drivers/gpu/drm/asahi/g16/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
8 changes: 0 additions & 8 deletions drivers/gpu/drm/asahi/g16/glue.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <crypto/sha2.h>
#include <linux/device.h>
#include <linux/atomic.h>
#include <linux/dma-map-ops.h>
Expand All @@ -23,7 +22,6 @@
#include <linux/soc/apple/pmp-telemetry.h>
#include <linux/thermal.h>

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);
Expand Down Expand Up @@ -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)
{
Expand Down
Loading