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
57 changes: 55 additions & 2 deletions drivers/gpu/drm/asahi/m3/m3_adt_config.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 @@ -260,7 +260,14 @@

/// The runtime backend's InitData contents: the generated object images and the
/// performance-state policy.
pub(crate) struct ExtendedInputs {
pub(crate) power: hw::PwrConfig,
pub(crate) sram_uv: KVec<u32>,
pub(crate) bases: super::m3_mmio_26::RegisterBases,
}

pub(crate) struct Contents {
pub(crate) extended: Option<ExtendedInputs>,
/// Generated images, per owner; None for owners that stay zero. Only the three image
/// owners' images are uploaded; the records' are checked.
images: KVec<Option<KVVec<u8>>>,
Expand Down Expand Up @@ -304,6 +311,10 @@
experiment: Option<&Experiment>,
) -> Result<Self> {
let dev = pdev.as_ref();
if firmware.profile() == super::m3_profile::Profile::Extended {
if experiment.is_some() { return Err(EINVAL); }
return Self::extended(dev, soc);
}
let param = m3_params::initdata_param();
check_version(dev, firmware, experiment)?;
let iomaps = match experiment {
Expand Down Expand Up @@ -370,12 +381,54 @@
);
let read_only_slots = experiment.map(|e| e.read_only_slots()).unwrap_or(0);
let hwdata_object = soc.hwdata_object;
Ok(Contents { images, pstates, iomaps, table, read_only_slots, hwdata_object })
Ok(Contents { extended: None, images, pstates, iomaps, table, read_only_slots, hwdata_object })
}

fn extended(dev: &device::Device, soc: &Soc) -> Result<Self> {
let cfg = soc.hwcfg.ok_or(ENODEV)?;
if soc.chip_id != 0x8122 || cfg.chip_id != 0x8122 { return Err(ENODEV); }
let power = hw::PwrConfig::load(dev, cfg)?;
let tables = initdata::PStateTables::new(&power, true, None, false)?;
if tables.len != 9 || tables.max != 8 || tables.max_frequency_khz(&power) != 1_338_000 {
return Err(EINVAL);
}
let node = dev.of_node().ok_or(ENODEV)?;
let opps = node.parse_phandle(c_str!("operating-points-v2"), 0).ok_or(EINVAL)?;
let mut sram_uv = KVec::new();
for opp in opps.children() {
let values: KVec<u32> = opp.get_property(c_str!("opp-microvolt-sram"))?;
if values.len() != 1 { return Err(EINVAL); }
sram_uv.push(values[0], GFP_KERNEL)?;
}
if sram_uv.len() != power.perf_states.len() { return Err(EINVAL); }
let block = |index: usize| -> Result<u64> {
Ok(cfg.io_mappings.get(index).and_then(|value| value.as_ref()).ok_or(EINVAL)?.base as u64)
};
let bases = super::m3_mmio_26::RegisterBases {
gpu: soc.windows.sgx, interrupt: block(2)?,
memory: block(11)?.checked_add(0x1c4000).ok_or(EOVERFLOW)?,
telemetry_write: block(18)?, telemetry_read: block(19)?,
neural: block(25)?, metrology: block(26)?,
};
let mut freqs = [0; 16];
for (index, value) in freqs.iter_mut().enumerate().take(tables.len) {
*value = tables.primary_state(&power, index).freq_hz / 1_000_000;
}
// This profile enables the native firmware's thermal/PID controllers.
// The legacy host-only thermal governor must not replace those inputs.
let pstates = PstatePolicy { table_max: 8, table_max_mhz: 1338,
max: 8, safe: 8, boot: 1, max_mhz: 1338, safe_mhz: 1338,
boot_mhz: freqs[1], freqs, thermal: ThermalMode::Off };
let mut images = KVec::new();
for _ in 0..super::m3_init_26::COUNT { images.push(None, GFP_KERNEL)?; }
Ok(Self { extended: Some(ExtendedInputs { power, sram_uv, bases }),
images, pstates, iomaps: &[], table: (8, 1338), read_only_slots: 0, hwdata_object: None })
}

/// Check the contents before they are uploaded.
pub(crate) fn check_for_upload(&self) -> Result {
if self.images.len() == storage::COUNT {
let count = if self.extended.is_some() { super::m3_init_26::COUNT } else { storage::COUNT };
if self.images.len() == count {
Ok(())
} else {
Err(EINVAL)
Expand Down
2 changes: 1 addition & 1 deletion drivers/gpu/drm/asahi/m3/m3_board.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

//! Board facts for the M3 runtime, read from the hardware and the device tree instead of being
Expand All @@ -13,7 +13,7 @@
use crate::m3::m3_soc::Soc;

/// Whether the root node's compatible list contains `compatible`.
fn board_is(compatible: &[u8]) -> bool {
pub(crate) fn board_is(compatible: &[u8]) -> bool {
let Some(root) = kernel::of::root() else {
return false;
};
Expand Down
182 changes: 182 additions & 0 deletions drivers/gpu/drm/asahi/m3/m3_channel_format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

//! Bounded firmware channel records. Publication and retirement belong to Config.

use super::m3_init_layout::Error;

pub(crate) const CONTROL_BYTES: usize = 64;
pub(crate) const RECEIVE_BYTES: usize = 72;
pub(crate) const SLOTS: u32 = 256;

#[derive(Clone, Copy, Debug)]
pub(crate) enum Action {
Initialize,
Register {
pipe: u32,
engine: u32,
},
Idle {
enabled: bool,
},
Release {
group: u8,
index: u8,
weight: u8,
address: u64,
},
}

impl Action {
/// The first two actions precede the first generation-bearing publication.
pub(crate) fn generation(self, published: u32, consumed: u32) -> Result<u32, Error> {
match self {
Self::Initialize | Self::Register { .. } => {
if published != 0 || consumed != 0 {
return Err(Error::Address);
}
Ok(0)
}
_ => published.max(consumed).checked_add(1).ok_or(Error::Address),
}
}

pub(crate) fn encode(self, output: &mut [u8]) -> Result<(), Error> {
let mut words = [0u32; 3];
let mut release = None;
match self {
Self::Initialize => words[0] = 0x16,
Self::Register { pipe, engine } => {
if pipe >= 3 || !(1..=2).contains(&engine) {
return Err(Error::Address);
}
words = [0x1b, pipe, engine];
}
Self::Idle { enabled } => words = [0x0a, enabled.into(), 0],
Self::Release {
group,
index,
weight,
address,
} => {
if index == 255
|| weight == 255
|| address < 0xffff_fc00_0000_0000
|| address & 7 != 0
{
return Err(Error::Address);
}
words[0] = 0x14;
words[2] = u32::from_le_bytes([group, index, weight, 4]);
release = Some(address);
}
}
let bytes = output.get_mut(..CONTROL_BYTES).ok_or(Error::Size)?;
bytes.fill(0);
for (index, word) in words.iter().enumerate() {
bytes[index * 4..index * 4 + 4].copy_from_slice(&word.to_le_bytes());
}
if let Some(address) = release {
bytes[12..20].copy_from_slice(&address.to_le_bytes());
}
Ok(())
}
}

/// An empty ring is required before publishing a synchronous control transaction.
/// Both consumers must retire it before the next control action may be published.
pub(crate) fn idle_cursor(cursors: [u32; 3]) -> Result<u32, Error> {
if cursors.iter().any(|value| *value >= SLOTS) {
return Err(Error::Bounds);
}
if cursors[0] != cursors[2] || cursors[1] != cursors[2] {
return Err(Error::Address);
}
Ok(cursors[2])
}

pub(crate) fn payload_offset(thread: usize, slot: u32) -> Result<usize, Error> {
if thread >= 9 {
return Err(Error::Bounds);
}
Ok((thread * 256 + (slot & 255) as usize) * 0xd8)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn initialization_records_have_only_their_defined_words() {
for action in [Action::Initialize, Action::Register { pipe: 2, engine: 1 }] {
let mut output = [0xa5; CONTROL_BYTES + 8];
action.encode(&mut output).unwrap();
assert_eq!(&output[12..CONTROL_BYTES], &[0; CONTROL_BYTES - 12]);
assert_eq!(&output[CONTROL_BYTES..], &[0xa5; 8]);
assert_eq!(action.generation(0, 0), Ok(0));
assert!(action.generation(1, 0).is_err());
}
}

#[test]
fn invalid_actions_preserve_the_entire_output() {
for action in [
Action::Register { pipe: 3, engine: 1 },
Action::Register { pipe: 0, engine: 0 },
Action::Release {
group: 0,
index: 255,
weight: 1,
address: 0xffff_fc21_0000_0000,
},
Action::Release {
group: 0,
index: 0,
weight: 1,
address: 8,
},
] {
let mut output = [0xa5; CONTROL_BYTES];
assert!(action.encode(&mut output).is_err());
assert_eq!(output, [0xa5; CONTROL_BYTES]);
}
}

#[test]
fn generations_follow_consumption_and_never_wrap() {
let action = Action::Idle { enabled: true };
assert_eq!(action.generation(3, 5), Ok(6));
assert_eq!(action.generation(5, 3), Ok(6));
assert!(action.generation(u32::MAX, 0).is_err());
assert!(action.generation(0, u32::MAX).is_err());
let mut output = [0; CONTROL_BYTES];
action.encode(&mut output).unwrap();
assert_eq!(&output[..8], &[10, 0, 0, 0, 1, 0, 0, 0]);
}

#[test]
fn both_consumers_and_all_indices_are_checked() {
assert_eq!(idle_cursor([255, 255, 255]), Ok(255));
for cursors in [[0, 1, 1], [1, 0, 1], [256, 256, 256]] {
assert!(idle_cursor(cursors).is_err());
}
assert_eq!(payload_offset(8, 0x101), Ok((8 * 256 + 1) * 0xd8));
assert!(payload_offset(9, 0).is_err());
}

#[test]
fn release_uses_the_packed_owned_pointer() {
let action = Action::Release {
group: 1,
index: 2,
weight: 3,
address: 0xffff_fc21_0400_0000,
};
let mut output = [0; CONTROL_BYTES];
action.encode(&mut output).unwrap();
assert_eq!(&output[8..12], &[1, 2, 3, 4]);
assert_eq!(
u64::from_le_bytes(output[12..20].try_into().unwrap()),
0xffff_fc21_0400_0000
);
}
}
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
Loading
Loading