Skip to content
Merged
16 changes: 16 additions & 0 deletions drivers/gpu/drm/asahi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ config DRM_ASAHI_DEBUG_ALLOCATOR
Say N unless you are debugging firmware structures or porting to a
new firmware version.

config DRM_ASAHI_M3_T8122
bool "M3 MacBook Air GPU runtime (T8122, 14.8.3 firmware)"
depends on DRM_ASAHI
# The runtime adopts the firmware's UAT through the G15 backend's handoff helpers.
depends on DRM_ASAHI_G15
select APPLE_T8122_GPU_PMP_LINK
select CRYPTO_LIB_SHA256
default n
help
Build the M3 runtime that starts the GPU of the M3 MacBook Air
(J613, J615) on the 14.8.3 GPU firmware the boot loader leaves
loaded. It starts only with asahi.t8122_start=1 on the kernel
command line, after the boot loader handed the GPU over; without
it the T8122 probe is refused as before. Other GPUs are not
affected.

config DRM_ASAHI_G15
bool "Native G15 firmware backend (M3 Pro/Max)"
depends on DRM_ASAHI
Expand Down
7 changes: 7 additions & 0 deletions drivers/gpu/drm/asahi/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ pub(crate) enum DebugFlags {
DeviceControlCh = 21,
FwCtlCh = 22,

// Host submission/completion timestamps without verbose firmware tracing.
SubmitTiming = 23,
// Low-volume M3 command/geometry totals without per-IRQ printk.
M3SubmitSummary = 24,
// Per-slot retired timestamps, without enabling firmware ktrace.
M3PassTiming = 25,

// 32-35: Allocator debugging
FillAllocations = 32,
DebugAllocations = 33,
Expand Down
57 changes: 57 additions & 0 deletions drivers/gpu/drm/asahi/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ pub(crate) struct AsahiPlatformDriverData<'bound> {
_reg: drm::Registration<'bound, AsahiDrmDriver>,
/// The G17 GPU, whose coprocessors must be stopped when the driver is unbound.
g17: Option<Arc<g17::Gpu>>,
/// The M3 T8122 runtime, stopped on unbind like the G17 GPU.
m3: Option<M3Backend>,
/// Raw `struct device *` of the platform device. Stored once at probe
/// time so the `Drop` impl can find the sysfs file on unregister.
raw_dev: *mut bindings::device,
Expand All @@ -62,6 +64,11 @@ unsafe impl Send for AsahiPlatformDriverData<'_> {}
// SAFETY: As above.
unsafe impl Sync for AsahiPlatformDriverData<'_> {}

#[cfg(CONFIG_DRM_ASAHI_M3_T8122)]
type M3Backend = Arc<crate::m3::m3_drm::Backend>;
#[cfg(not(CONFIG_DRM_ASAHI_M3_T8122))]
type M3Backend = core::convert::Infallible;

/// Holds a reference to the top-level GPU object.
#[pin_data]
pub(crate) struct AsahiDrmRegistrationData<'drm> {
Expand Down Expand Up @@ -213,6 +220,11 @@ impl platform::Driver for AsahiPlatformDriver {
// Driver data is dropped after devres, so CPU control must happen in unbind.
gpu.shutdown();
}
#[cfg(CONFIG_DRM_ASAHI_M3_T8122)]
if let Some(m3) = this.m3.as_ref() {
// As for G17: the runtime stops its coprocessor through device resources.
m3.stop();
}
}

/// Device probe function.
Expand All @@ -227,6 +239,8 @@ impl platform::Driver for AsahiPlatformDriver {
let cfg = match info.ok_or(ENODEV)? {
ProbeConfig::Legacy(cfg) => *cfg,
ProbeConfig::G17(cfg) => return AsahiPlatformDriver::probe_g17(pdev, cfg),
#[cfg(CONFIG_DRM_ASAHI_M3_T8122)]
ProbeConfig::M3(crate::m3::Chip::T8122) => return AsahiPlatformDriver::probe_t8122(pdev),
ProbeConfig::M3(chip) => return Err(crate::m3::refuse_probe(pdev, *chip)),
};

Expand Down Expand Up @@ -297,6 +311,7 @@ impl platform::Driver for AsahiPlatformDriver {
_device: reg.device().into(),
_reg: reg,
g17: None,
m3: None,
raw_dev,
raw_stats,
stats,
Expand Down Expand Up @@ -328,6 +343,43 @@ impl AsahiPlatformDriver {
_device: reg.device().into(),
_reg: reg,
g17: None,
m3: None,
raw_dev: core::ptr::null_mut(),
raw_stats: core::ptr::null_mut(),
stats: None,
})
}

/// The M3 MacBook Air (T8122): the M3 runtime, started only when its identity gate, the
/// backend selection and the start experiment (`asahi.t8122_start=1`) admit it; otherwise
/// the probe is refused as without the runtime.
#[cfg(CONFIG_DRM_ASAHI_M3_T8122)]
fn probe_t8122<'bound>(
pdev: &'bound platform::Device<Core<'_>>,
) -> Result<AsahiPlatformDriverData<'bound>> {
let soc = crate::m3::t8122_admitted(pdev)?;
// SAFETY: The admitted M3 GPU's UAT outputs 42-bit physical addresses.
unsafe { pdev.dma_set_mask_and_coherent(DmaMask::try_new(42)?)? };
let resources = regs::Resources::new(pdev)?;
let drm = drm::UnregisteredDevice::<AsahiDrmDriver>::new(pdev, Ok(()))?;
let backend = crate::m3::m3_drm::Backend::start(pdev, &drm, soc)?;
let gpu = backend.clone() as Arc<dyn gpu::Gpu>;
let data = try_pin_init!(AsahiDrmRegistrationData { gpu, resources });
// SAFETY: The registration is stored and unregistered by platform data destruction,
// which drops the runtime with it.
let reg = match unsafe { drm::Registration::new(pdev.as_ref(), drm, data, 0) } {
Ok(reg) => reg,
Err(error) => {
backend.stop();
return Err(error);
}
};
dev_info!(pdev.as_ref(), "M3: firmware ready; common DRM GEM/VM frontend registered\n");
Ok(AsahiPlatformDriverData {
_device: reg.device().into(),
_reg: reg,
g17: None,
m3: Some(backend),
raw_dev: core::ptr::null_mut(),
raw_stats: core::ptr::null_mut(),
stats: None,
Expand Down Expand Up @@ -371,6 +423,7 @@ impl AsahiPlatformDriver {
_device: reg.device().into(),
_reg: reg,
g17: Some(gpu),
m3: None,
raw_dev,
raw_stats,
stats: None,
Expand All @@ -390,5 +443,9 @@ impl Drop for AsahiPlatformDriverData<'_> {
// Also covers failure to allocate platform driver data after the probe body returned.
gpu.shutdown();
}
#[cfg(CONFIG_DRM_ASAHI_M3_T8122)]
if let Some(m3) = self.m3.as_ref() {
m3.stop();
}
}
}
16 changes: 15 additions & 1 deletion drivers/gpu/drm/asahi/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ pub(crate) struct File {
queues: xarray::XArray<Arc<Mutex<KBox<dyn queue::Queue>>>>,
// #[pin]
objects: xarray::XArray<KBox<Object>>,
/// Largest size this file has asked GET_PARAMS for the global parameters with
/// ([`gpu::Gpu::admit_vm`]).
params_size: core::sync::atomic::AtomicU64,
}

/// Convenience type alias for our DRM `File` type.
Expand Down Expand Up @@ -286,6 +289,7 @@ impl File {

(*slot).id = id;
(*slot).user_range = user_range;
addr_of_mut!((*slot).params_size).write(core::sync::atomic::AtomicU64::new(0));
Ok(())
})
}
Expand Down Expand Up @@ -386,6 +390,9 @@ impl File {
core::slice::from_raw_parts(&params as *const _ as *const u8, size)
})?;

file.inner()
.params_size
.fetch_max(data.size, core::sync::atomic::Ordering::Relaxed);
Ok(0)
}

Expand Down Expand Up @@ -454,6 +461,13 @@ impl File {
// Every other runtime here reports DRM_ASAHI_GPU_HAL_LEGACY.
return Err(EINVAL);
}
reg_data.gpu.admit_vm(
device.as_ref().as_ref(),
file.inner()
.params_size
.load(core::sync::atomic::Ordering::Relaxed),
data.flags,
)?;

let kernel_range = data.kernel_start..data.kernel_end;
let user_range = file.inner().user_range.clone();
Expand Down Expand Up @@ -535,7 +549,7 @@ impl File {
}
let dummy_mapping = dummy_obj.map_at(
&vm,
gpu.uat_geometry().unk_page(),
gpu.unknown_page(),
mmu::PROT_GPU_SHARED_RW,
true,
)?;
Expand Down
5 changes: 5 additions & 0 deletions drivers/gpu/drm/asahi/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ impl F32 {
F32(u)
}

/// Convert to the raw IEEE 754 bits.
pub(crate) const fn to_bits(self) -> u32 {
self.0
}

// Convert a `f32` value into an F32
//
// This must ONLY be used in const context. Use the `f32!{}` macro to do it safely.
Expand Down
4 changes: 2 additions & 2 deletions drivers/gpu/drm/asahi/g15/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ mod pools;
mod power;
mod queue;
mod recovery;
mod regs;
pub(crate) mod regs;
mod slots;
mod timestamp;
mod uat;
pub(crate) mod uat;
mod vm;
mod wire;

Expand Down
15 changes: 15 additions & 0 deletions drivers/gpu/drm/asahi/g15/regs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,21 @@ impl Mapping {
pub(crate) fn phys(&self) -> u64 {
self.phys
}

/// Cleans the whole mapping to the point of coherency when it is the kernel's cacheable
/// mapping of reserved System RAM ([`Self::region`]); device and write-combined mappings
/// need nothing.
pub(crate) fn clean_poc(&self) {
if self.kind != Kind::WriteBack {
return;
}
let start = self.raw.addr();
// SAFETY: `start..start + maxsize` is this live mapping; cleaning a cacheable range has
// no other effect.
unsafe {
bindings::dcache_clean_poc(start as _, (start + self.raw.size()) as _);
}
}
}

impl Drop for Mapping {
Expand Down
33 changes: 29 additions & 4 deletions drivers/gpu/drm/asahi/g15/uat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,41 @@ pub(crate) struct Window {
impl Window {
/// Maps the UAT handoff regions ([`Mapping::region`]).
pub(crate) fn new(adt: &super::adt::GpuAdt) -> Result<Self> {
Self::from_regions(adt.handoff.start, adt.ttbs.start, adt.tables.start, adt.shared_l2.start)
}

/// Maps the UAT handoff regions at the given physical addresses: the handoff page, the
/// TTBAT, the firmware kernel root and the shared L2 page. The M3 T8122 runtime takes them
/// from its admitted reserved-memory regions.
pub(crate) fn from_regions(handoff: u64, ttbs: u64, root: u64, shared_l2: u64) -> Result<Self> {
Ok(Self {
handoff: Mapping::region(adt.handoff.start, hw::HANDOFF_SIZE as usize)?,
ttbs: Mapping::region(adt.ttbs.start, hw::TTBS_SIZE as usize)?,
root: Mapping::region(adt.tables.start, hw::PAGE as usize)?,
shared_l2: Mapping::region(adt.shared_l2.start, hw::SHARED_L2_SIZE as usize)?,
handoff: Mapping::region(handoff, hw::HANDOFF_SIZE as usize)?,
ttbs: Mapping::region(ttbs, hw::TTBS_SIZE as usize)?,
root: Mapping::region(root, hw::PAGE as usize)?,
shared_l2: Mapping::region(shared_l2, hw::SHARED_L2_SIZE as usize)?,
image: KVec::from_elem(0u64, ENTRIES, GFP_KERNEL)?,
snapshot: KVec::from_elem(0u64, ENTRIES, GFP_KERNEL)?,
})
}

/// [`Self::sync_range`] over the whole host window, then [`Self::clean_tables`].
pub(crate) fn sync_all(&mut self, uat: &mmu::Uat) -> Result {
self.sync_inner(uat, None)?;
self.clean_tables();
Ok(())
}

/// Cleans the firmware-walked pages this window writes (the kernel root, the shared L2,
/// the TTBAT and the handoff page) to the point of coherency. Only a region that is
/// reserved System RAM, mapped cacheable, has anything to clean: the UAT walker does not
/// snoop the CPU caches. The M3 T8122 runtime calls it after [`Self::bootstrap`].
pub(crate) fn clean_tables(&self) {
for map in [&self.root, &self.shared_l2, &self.ttbs, &self.handoff] {
map.clean_poc();
}
mb();
}

/// Cold-start check: all 128 TTBAT words are zero.
pub(crate) fn ttbat_is_clear(&self) -> Result<bool> {
let t = self.ttbs.io();
Expand Down
10 changes: 10 additions & 0 deletions drivers/gpu/drm/asahi/gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@ pub(crate) trait Gpu: Send + Sync {
}
/// A userspace VM handle was closed; release backend-only registry references.
fn vm_closed(&self) {}
/// Whether a file may create a VM: `params_size` is the largest size it asked GET_PARAMS
/// for the global parameters with, `flags` its VM_CREATE flags. Every GPU but the M3
/// T8122 runtime admits any client.
fn admit_vm(&self, _dev: &kernel::device::Device, _params_size: u64, _flags: u32) -> Result {
Ok(())
}
/// GPU VA of each VM's dummy page, which must match the address the firmware was given.
fn unknown_page(&self) -> u64 {
self.uat_geometry().unk_page()
}
/// Create a new `Vm` with the given range reserved for kernel-managed objects.
fn new_vm(&self, kernel_range: Range<u64>) -> Result<mmu::Vm>;
/// Create a new user command queue.
Expand Down
71 changes: 71 additions & 0 deletions drivers/gpu/drm/asahi/m3/agx_attachments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

pub(crate) const SIZE: usize = 0x104;
pub(crate) const CAPACITY: usize = 16;

#[derive(Clone, Copy)]
pub(crate) struct Attachments(pub(crate) [u8; SIZE]);

impl Attachments {
pub(crate) const EMPTY: Self = Self([0; SIZE]);
pub(crate) fn count(&self) -> u8 { self.0[0x100] }

/// Normalize byte ranges to the firmware's 128-byte cache-line units.
/// The caller must validate the entire normalized writable GPU range.
pub(crate) fn range(address: u64, size: u64) -> Result<(u64, u64), ()> {
if address == 0 || size == 0 { return Err(()); }
let end = address.checked_add(size).and_then(|v| v.checked_add(127)).ok_or(())? & !127;
let start = address & !127;
if start == 0 || end > 1 << 42 || (end - start) >> 7 > u32::MAX as u64 {
return Err(());
}
Ok((start, end - start))
}

pub(crate) fn new(ranges: &[(u64, u64)]) -> Result<Self, ()> {
if ranges.len() > CAPACITY { return Err(()); }
let mut out = Self::EMPTY;
for (record, &(address, size)) in out.0[..0x100].chunks_exact_mut(16).zip(ranges) {
let (address, size) = Self::range(address, size)?;
record[..8].copy_from_slice(&address.to_le_bytes());
record[8..12].copy_from_slice(&((size >> 7) as u32).to_le_bytes());
record[12..14].copy_from_slice(&0x18u16.to_le_bytes());
record[14..16].copy_from_slice(&1u16.to_le_bytes());
}
out.0[0x100..].copy_from_slice(&(ranges.len() as u32).to_le_bytes());
Ok(out)
}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn covers_both_partial_edge_lines() {
let a = Attachments::new(&[(0x100007f, 2)]).unwrap();
assert_eq!(u64::from_le_bytes(a.0[..8].try_into().unwrap()), 0x1000000);
assert_eq!(u32::from_le_bytes(a.0[8..12].try_into().unwrap()), 2);
assert_eq!(&a.0[12..16], &[0x18, 0, 1, 0]);
assert_eq!(a.count(), 1);
assert!(a.0[16..0x100].iter().all(|v| *v == 0));
}
#[test]
fn rejects_unrepresentable_ranges_and_count() {
for range in [(0, 128), (128, 0), (u64::MAX - 1, 8),
((1 << 42) - 128, 129), (128, (u32::MAX as u64 + 1) << 7)] {
assert!(Attachments::new(&[range]).is_err());
}
assert!(Attachments::new(&[(128, 128); CAPACITY + 1]).is_err());
let full = Attachments::new(&[(128, 128); CAPACITY]).unwrap();
assert_eq!(full.count(), 16);
assert_eq!(&full.0[0x100..], &[16, 0, 0, 0]);
assert_eq!(Attachments::new(&[]).unwrap().0, Attachments::EMPTY.0);
}
#[test]
fn matches_native_m4_writable_buffer_descriptor() {
let encoded = Attachments::new(&[(0x100000a8000, 0x400000)]).unwrap();
assert_eq!(&encoded.0[..16], &[
0, 0x80, 0x0a, 0, 0, 1, 0, 0, 0, 0x80, 0, 0, 0x18, 0, 1, 0]);
assert_eq!(encoded.count(), 1);
}
}
9 changes: 9 additions & 0 deletions drivers/gpu/drm/asahi/m3/agx_compute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

/// Complete client CDM range and its USC address window.
#[derive(Clone, Copy)]
pub(crate) struct Control {
pub base: u64,
pub end: u64,
pub usc_base: u64,
}
Loading