Skip to content
Closed
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
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