Skip to content
Closed
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
2 changes: 2 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -2723,7 +2723,9 @@ F: drivers/pwm/pwm-apple.c
F: drivers/reset/reset-apple-cio.c
F: drivers/rtc/rtc-macsmc.c
F: drivers/soc/apple/*
F: tools/asahi/m3-uapi-*
F: tools/asahi/pmp-bootargs-tests.rs
F: tools/asahi/test-m3-uapi.py
F: drivers/spi/spi-apple.c
F: drivers/spmi/spmi-apple-controller.c
F: drivers/thunderbolt/.kunitconfig.apple-dpin
Expand Down
9 changes: 8 additions & 1 deletion drivers/gpu/drm/apple/j613_dcp_activate.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ bool apple_t8122_25g83_board(void)
return ok;
}

/* Match the PMGR provider: automatic domains can be clock-gated while active. */
static bool j613_power_state_active(u32 state)
{
return FIELD_GET(GENMASK(7, 4), state) == 15 ||
((state & BIT(28)) && FIELD_GET(GENMASK(3, 0), state) == 15);
}

/* These are DT domain nodes, carrying the PMGR offsets, not copied registers. */
static int j613_power_ready(void)
{
Expand Down Expand Up @@ -82,7 +89,7 @@ static int j613_power_ready(void)
return -ENOMEM;
state = readl(pmgr + offset);
iounmap(pmgr);
if (FIELD_GET(GENMASK(7, 4), state) != 15)
if (!j613_power_state_active(state))
return -EBUSY;
}
return 0;
Expand Down
17 changes: 14 additions & 3 deletions drivers/gpu/drm/asahi/m3/m3_client.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

//! Safety checks for clients of the M3 (G15) render node.
Expand Down Expand Up @@ -35,7 +35,7 @@
const AGX3_PARAMS_SIZE: u64 =
(core::mem::offset_of!(uapi::drm_asahi_params_global, gpu_hal_generation)
+ core::mem::size_of::<u32>()) as u64;
const _: () = assert!(AGX3_PARAMS_SIZE == 600);
const _: () = assert!(AGX3_PARAMS_SIZE == super::m3_profile::PARAMS_REQUIRED);

/// Refused clients that are logged per boot; later refusals only count.
const REFUSALS_LOGGED: u32 = 16;
Expand All @@ -45,10 +45,19 @@
/// [`AGX3_PARAMS_SIZE`] bytes (so it knows this GPU runs AGX3 command streams), and pass
/// VM_CREATE flags 0 (legacy HAL). `size` is the largest GET_PARAMS size the file asked for.
pub(crate) fn admit_vm(device: &kernel::device::Device, size: u64, flags: u32) -> Result {
admit_vm_for(device, super::m3_profile::Profile::Legacy, size, flags)
}

pub(crate) fn admit_vm_for(
device: &kernel::device::Device,
profile: super::m3_profile::Profile,
size: u64,
flags: u32,
) -> Result {
{
// The positive acknowledgement of the command ABI (7.1.12 `g16_profile::client_admitted`):
// GET_PARAMS coverage, and VM_CREATE flags 0 on the legacy-HAL M3 runtime.
if size >= AGX3_PARAMS_SIZE && flags == 0 {
if profile.vm_acknowledged(size, flags) {
return Ok(());
}
let refusals = REFUSALS.fetch_add(1, Ordering::Relaxed).saturating_add(1);
Expand All @@ -64,11 +73,13 @@
};
dev_info!(
device,
"M3: refusing a VM for {}[{}]: it asked GET_PARAMS for {} of the {} parameter bytes, so it cannot know this GPU runs AGX3 command streams; M3-aware userspace is needed{}\n",
"M3: refusing a VM for {}[{}]: GET_PARAMS bytes={}/{} HAL={} flags={:#x}{}\n",
name,
task.group_leader().pid(),
size,
AGX3_PARAMS_SIZE,
profile.hal(),
flags,
last
);
}
Expand Down
20 changes: 13 additions & 7 deletions drivers/gpu/drm/asahi/m3/m3_drm.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT
//! M3 through the common Asahi GEM, GPUVM and render-node frontend.
use core::ops::Range;
Expand Down Expand Up @@ -86,6 +86,7 @@
core_mask: u64,
max_frequency_khz: u32,
soc: &'static crate::m3::m3_soc::Soc,
profile: super::m3_profile::Profile,
_progress: Pin<KBox<kernel::debugfs::File<ProgressView>>>,
_activity: Pin<KBox<kernel::debugfs::File<ActivityView>>>,
_memory: Pin<KBox<kernel::debugfs::File<crate::m3::agx_memory_stats::View>>>,
Expand Down Expand Up @@ -125,6 +126,7 @@
let firmware = crate::m3::m3_firmware::identify_loaded(pdev, soc, resources, experiment)
.inspect_err(refused("the firmware identity"))?;
dev_info!(pdev.as_ref(), "M3: firmware identity accepted\n");
let profile = firmware.profile();
// InitData source and contents, before any GPU register access.
let contents = crate::m3::m3_adt_config::Contents::select(pdev, &firmware, soc, experiment)
.inspect_err(refused("the InitData contents"))?;
Expand Down Expand Up @@ -160,7 +162,7 @@
let completion_work = crate::m3::m3_completion::Completion::new(shared.clone())?;
// From here on every failure drops the backend, which stops the runtime.
let backend = Arc::new(Self { shared, health, ids: gpu::SequenceIDs::default(),
scheduler, core_mask, max_frequency_khz, soc,
scheduler, core_mask, max_frequency_khz, soc, profile,
_progress: progress, _activity: activity, _memory: memory, _timing: timing,
_completion: completion, _overlap: overlap }, GFP_KERNEL)?;
// The completion worker services this runtime from now on; the RTKit callbacks and
Expand Down Expand Up @@ -208,7 +210,9 @@
}
params.max_frequency_khz = self.max_frequency_khz;
params.usc_generation = 3;
params.gpu_hal_generation = hw::GpuHalGeneration::Legacy as u32;
params.gpu_hal_generation = self.profile.hal();
params.max_commands_per_submission = super::agx_uapi::UAPI_MAX_HARDWARE_COMMANDS;
params.max_attachments = super::agx_uapi::UAPI_MAX_ATTACHMENTS as u32;
Ok(())
}
fn supports_vm_status(&self) -> bool { true }
Expand All @@ -217,10 +221,12 @@
fn feature_flags(&self) -> u64 {
let mut flags = uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_VM_STATUS as u64
| uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_SCHEDULED_QUEUES as u64;
if self.soc.features.fragment_dependency && crate::m3::m3_params::early_tiling() {
if self.profile == super::m3_profile::Profile::Legacy
&& self.soc.features.fragment_dependency && crate::m3::m3_params::early_tiling() {
flags |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_FRAGMENT_DEPENDENCY as u64;
}
if self.soc.features.compute_wide_visibility {
if self.profile == super::m3_profile::Profile::Legacy
&& self.soc.features.compute_wide_visibility {
flags |= uapi::drm_asahi_feature_DRM_ASAHI_FEATURE_COMPUTE_WIDE_VISIBILITY as u64;
}
flags
Expand All @@ -233,17 +239,17 @@
}
fn uat_geometry(&self) -> mmu::UatGeometry { mmu::UatGeometry { ias: 42, oas: 42 } }
fn user_range(&self) -> Range<u64> {
mmu::UAT_PGSZ as u64..crate::m3::initdata::RUNTIME_UNKNOWN_PAGE
mmu::UAT_PGSZ as u64..self.profile.user_end()
}
/// The dummy page sits where InitData tells the firmware it is (unk_30), as on 7.1.12; the
/// 42-bit geometry's own unk_page() is a different address.
fn unknown_page(&self) -> u64 {
crate::m3::initdata::RUNTIME_UNKNOWN_PAGE
self.profile.user_end()
}
fn tracked_submission(&self) -> bool { true }
/// Only M3-aware userspace may create a VM (7.1.12's per-file client gate).
fn admit_vm(&self, dev: &kernel::device::Device, params_size: u64, flags: u32) -> Result {
crate::m3::m3_client::admit_vm(dev, params_size, flags)
crate::m3::m3_client::admit_vm_for(dev, self.profile, params_size, flags)
}
fn new_vm(&self, range: Range<u64>) -> Result<mmu::Vm> {
Option::as_mut(&mut *self.shared.lock()).ok_or(ENODEV)?.new_vm(self.ids.vm.next(), range)
Expand Down
146 changes: 140 additions & 6 deletions drivers/gpu/drm/asahi/m3/m3_firmware.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT


Expand Down Expand Up @@ -62,6 +62,19 @@
initdata_magic: Some(G15_V14_8_3_INITDATA),
}];

/// Image record for the extended format. The SoC's runtime selection does not use it yet.
pub(crate) static T8122_IMAGES_26: [KnownImage; 1] = [KnownImage {
name: "T8122 C0 firmware-compat 26.6.2",
uuid: None,
stkg_sha256: Some([
0x3a, 0x6b, 0x8b, 0x5e, 0x83, 0x38, 0x4d, 0x5c,
0xc1, 0x92, 0x9d, 0x91, 0xb6, 0x36, 0x40, 0xfe,
0xe8, 0xf5, 0x52, 0xb5, 0x10, 0x8c, 0x4e, 0xa6,
0x20, 0xbe, 0xdb, 0x7b, 0xbc, 0xa0, 0x31, 0x64,
]),
initdata_magic: Some(0x0c89_c357_8392_04b8),
}];

/// The same T8122 image for the start experiment (`t8122_start`, only with
/// `asahi.t8122_start=1`), where `asahi.t8122_initdata_version` may replace the version.
pub(crate) static KNOWN_IMAGES_T8122_EXPERIMENT: [KnownImage; 1] = [KnownImage {
Expand Down Expand Up @@ -117,6 +130,10 @@
/// Offset of the 8-byte tag that precedes the 8 per-boot bytes zeroed before hashing.
pub(crate) entropy_tag_offset: usize,
pub(crate) entropy_tag: [u8; 8],
/// An additional independent boot value, when required by this image.
extra_boot_value: Option<(usize, [u8; 8])>,
/// Expected boot-table location and byte count in the image header.
boot_table: Option<(u32, u32)>,
/// The firmware version, for logs.
pub(crate) version: &'static str,
}
Expand All @@ -127,6 +144,8 @@
vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0005_c000],
entropy_tag_offset: 0x58e88,
entropy_tag: *b"GKTS\x08\x00\x00\x00",
extra_boot_value: None,
boot_table: None,
version: "RTKit-2419.140.12.release",
};

Expand All @@ -137,23 +156,73 @@
vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0006_4000],
entropy_tag_offset: 0x5d54a,
entropy_tag: *b"GKTS\x08\x00\x00\x00",
extra_boot_value: None,
boot_table: None,
version: "RTKit-2419.140.12.release",
};

pub(crate) const TEXT_SHA256: [u8; 32] = [
0x11, 0xe4, 0x9f, 0x75, 0xb, 0x67, 0x1a, 0x2b, 0x13, 0xd0, 0x92, 0xdb, 0x1c, 0xbc, 0xa3, 0xa8, 0x77, 0x55, 0x86, 0xd6, 0xb1, 0xe3, 0xaa, 0xe8, 0xa6, 0xa4, 0x64, 0xa7, 0x80, 0x2, 0x93, 0xd5];

/// T8122 C0, firmware-compat 26.6.2. Selection remains separate from runtime admission.
pub(crate) static T8122_LAYOUT_26: Layout = Layout {
text_size: 393216,
data_size: 1228800,
vas: [0xffff_fc00_0000_0000, 0xffff_fc00_0006_0000],
entropy_tag_offset: 380492,
entropy_tag: *b"GKTS\x08\x00\x00\x00",
extra_boot_value: Some((380719, *b"ECAP\x08\x00\x00\x00")),
boot_table: Some((380492, 561)),
version: "RTKit-3255.160.4.release",
};

fn normalize_boot_entropy(layout: &Layout, text: &mut [u8]) -> bool {
let tag = layout.entropy_tag_offset;
if text.len() != layout.text_size as usize {
return false;
}
let Some(end) = tag.checked_add(16) else { return false; };
let Some(entropy) = text.get_mut(tag..end) else { return false; };
if entropy[..8] != layout.entropy_tag {
return false;
if let Some((offset, count)) = layout.boot_table {
let Some(header) = text.get(0x22c..0x234) else {
return false;
};
if header[..4] != offset.to_le_bytes() || header[4..] != count.to_le_bytes() {
return false;
}
let Some(end) = (offset as usize).checked_add(count as usize) else {
return false;
};
if text.get(offset as usize..end).is_none() {
return false;
}
}
let fields = [
Some((layout.entropy_tag_offset, layout.entropy_tag)),
layout.extra_boot_value,
];
// Validate every field before touching even the first boot value.
for (offset, tag) in fields.into_iter().flatten() {
let Some(end) = offset.checked_add(16) else {
return false;
};
let Some(field) = text.get(offset..end) else {
return false;
};
if field[..8] != tag {
return false;
}
if let Some((base, count)) = layout.boot_table {
if offset < base as usize || end > base as usize + count as usize {
return false;
}
}
}
if let Some((other, _)) = layout.extra_boot_value {
if other < layout.entropy_tag_offset + 16 && layout.entropy_tag_offset < other + 16 {
return false;
}
}
for (offset, _) in fields.into_iter().flatten() {
text[offset + 8..offset + 16].fill(0);
}
entropy[8..].fill(0);
true
}

Expand All @@ -167,6 +236,14 @@
}

impl Firmware {
pub(crate) fn profile(&self) -> super::m3_profile::Profile {
if core::ptr::eq(self.layout, &T8122_LAYOUT_26) {
super::m3_profile::Profile::Extended
} else {
super::m3_profile::Profile::Legacy
}
}

pub(crate) const fn version(&self) -> &'static str {
self.layout.version
}
Expand Down Expand Up @@ -393,4 +470,61 @@
assert!(!normalize_boot_entropy(&layout, &mut text));
assert_eq!(text, vec![0xa5; 15]);
}

fn image_26() -> Vec<u8> {
let mut image = vec![0x5a; 393216];
image[0x22c..0x230].copy_from_slice(&380492u32.to_le_bytes());
image[0x230..0x234].copy_from_slice(&561u32.to_le_bytes());
image[380492..380500].copy_from_slice(b"GKTS\x08\x00\x00\x00");
image[380719..380727].copy_from_slice(b"ECAP\x08\x00\x00\x00");
image
}

#[test]
fn newer_image_masks_only_both_boot_values() {
let mut image = image_26();
let before = image.clone();
assert!(normalize_boot_entropy(&T8122_LAYOUT_26, &mut image));
for index in 0..image.len() {
let expected = if (380500..380508).contains(&index) || (380727..380735).contains(&index)
{
0
} else {
before[index]
};
assert_eq!(image[index], expected, "byte {index}");
}
}

#[test]
fn invalid_second_field_or_table_does_not_mask_first_value() {
for index in [0x22c, 0x230, 380492, 380496, 380719, 380723] {
let mut image = image_26();
image[index] ^= 1;
let before = image.clone();
assert!(!normalize_boot_entropy(&T8122_LAYOUT_26, &mut image));
assert_eq!(image, before);
}
let mut image = image_26();
image.pop();
let before = image.clone();
assert!(!normalize_boot_entropy(&T8122_LAYOUT_26, &mut image));
assert_eq!(image, before);
}

#[test]
fn extended_record_cannot_replace_legacy_identity() {
let newer = &T8122_IMAGES_26[0];
let digest = newer.stkg_sha256.unwrap();
assert!(newer.accepts_runtime(None, &digest));
assert_eq!(newer.initdata_magic, Some(0x0c89_c357_8392_04b8));
for index in 0..digest.len() {
let mut wrong = digest;
wrong[index] ^= 1;
assert!(!newer.accepts_runtime(None, &wrong));
}
assert!(!KNOWN_IMAGES_T8122[0].accepts_runtime(None, &digest));
assert!(!newer.accepts_runtime(KNOWN_IMAGES_T8122[0].uuid, &[0; 32]));
}

}
Loading