Skip to content

drm/asahi: g17: add the T6040 (M4 Pro) GPU as a Neo-style target - #408

Merged
RyanTheTide merged 19 commits into
aurora-silicon:integration/aurorafrom
jgoyvaerts:m4pro/gpu-t6040
Oct 24, 2026
Merged

RyanTheTide merged 19 commits into
aurora-silicon:integration/aurorafrom
jgoyvaerts:m4pro/gpu-t6040

Conversation

@jgoyvaerts

Copy link
Copy Markdown
Contributor

The M4 Pro GPU (T6040) running macOS 26.6.2's GPU firmware (25G83), added to the G17 backend the way the A18 Pro (T8140, Neo) is: per-SoC tables in hw/t6040.rs, the firmware's initialization data as typed layouts, device-tree power domains, operating-point tables and the ASCWrap v6 mailbox. 25G83 carries the A18 Pro's firmware interface (26A428), so the T6040 runs on the existing G17 code; where the two differ, the difference is a field of the SoC config or of the firmware variant, and the T8140 keeps its values.

There is no GravityLinux code in this series. Their M4 Pro bring-up (Cody Ho, Niklas Sheth) is the source of the UAT policy the chip has been running with, credited in that commit.

What the series adds

  • mmu: optional secondary root of PublishedRoots (the T6040 firmware publishes one root).
  • g17: one firmware coprocessor (the T8140 has two: GFX and GFX1).
  • g17: firmware variants. Config::firmware lists the firmware builds a SoC supports, identified by the macOS version whose boot firmware loaded them: apple,firmware-version on the GPU node, as m1n1 provides it for the other GPUs. The T6040 accepts macOS 26.6.2 (iBoot mBoot-18000.161.10, the 25G83 firmware) and refuses anything else before touching the GPU, naming what it found; the build it accepts is logged. The T8140 keeps its single build, not identified.
  • g17: QID floor. 25G83 keeps QIDs 0-31 for itself. The render reserve then leaves 42 render pairs, so 25G83 admits and reports (UAPI max_queues) 42 logical queues instead of 58: a 43rd queue is refused when it is created instead of waiting for a pair that never comes. The T8140 keeps 58.
  • g17: per-SoC tables from Config: region C, register windows (with several instances where the firmware maps one block per die/cluster), operating points with one voltage cell per cluster, the init acknowledgement (status word or message) chosen by the variant.
  • mmu: firmware tables in no-map reserved memory. Our boot loader reserves the UAT ranges as no-map regions outside /memory, and the firmware also builds tables in an l2-pagetables region. Table accesses go through a write-back mapping of those regions. Temporary: once the loader adds the ranges to /memory, as m1n1 does, this goes.
  • mmu: per-SoC UAT policy (UatQuirks): page-table cleaning to the point of coherency (the G16 backend's clean_poc, no longer limited to G16), with the context-table slots and the handoff page; page-by-page TLB invalidation; whether the handoff lock is taken and the current slot written at init; and the native AP 0 form for GPU-only mappings. The T6040 keeps the policy it has always run with; each setting can move to the A18 Pro's value on its own once a test shows it is not needed. UatQuirks::DEFAULT is the A18 Pro policy.
  • Bindings and mailbox: apple,agx-t6040 (merged with the T8132 entries of arm64: apple: T8132/J713 (M4 MacBook Air) support and native graphics (replaces #366, #367) #400: each SoC keeps its own memory-region names), and apple,t6040-ascwrap-v6 with its driver support.
  • 25G83: the initialization layout (HwData +0xc, completion rings at 0x2620, QoS at 0x26b8, the power-state object with its fast-die target), the render lead commands, and the render and compute commands. The frozen tables (power configuration, leakage, region C, AON, and the constant part of the hardware data) are checked by hash at probe; a mismatch refuses the GPU instead of hanging it. What comes from the device (the performance tables built from its operating points, which are binned per device, and its core count) is left out of the hash.
  • The T6040 config (hw/t6040.rs) and the J616s GPU node: pmgr_gfx with the AFR and GFX power states (always on: the firmware keeps running and does not survive GFX going off), gfx_mbox, the GPU node, its OPP tables (binned per device: this unit's values until a boot loader fills them in), and the reserved memory (the firmware data segment and the UAT ranges, at the addresses iBoot uses on this unit, until a boot loader fills them in from the ADT as m1n1 does on the T8140). The PMP's GPU report entry (pmp_gfx, j616s: PMP boots and stays up (endpoint 7, pmp-gfx report) #402) gets the GFX state as its parent, and the J616s board claims it always-on next to enabling the GPU, so the PMP sees the GPU on exactly while it is powered; without a GPU node it stays unclaimed.

The UAPI reports the GPU as G16, which is what Mesa drives it as.

Defaults kept as tested

Where the T6040's values differ from the A18 Pro's, they are the ones it has run with: the UAT policy above, the firmware root borrowed as the firmware left it, no fabric bridge or gating write, no MMIO render-pair registration, no POWER_ON_DONE, the init accepted in status A, few-primitives off, ray traversal refused, no MCache. Each is a separate field so it can be tried the A18 Pro's way on its own; that is also why the 25G83 register builders keep their SCS (ray query scratch) handling although ray_traversal is off.

Hardware tested on: Apple MacBook Pro (16-inch, M4 Pro, 2024), J616s / T6040, macOS 26.6.2 firmware (25G83), booted by our own loader.

How tested: Our daily kernel 7.3.0-m4.11 (on integration/aurora 6af6603bf85) carries exactly this branch, plus our private device-tree include (with apple,firmware-version = <26 6 2> on the GPU node, since our loader does not write it yet). On it the GPU probes ("GPU firmware: macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)") and drives the desktop (Hyprland, Mesa as G16). Its predecessor 7.3.0-m4.10 ran this driver before the review fixes in this branch (the init-data hash, the recovery timestamp table, the queue limit, the reserved-table cache maintenance) and has been the desktop's GPU since this morning; the stress run was on it: glmark2 (GLES) 8003 and vkmark 12835 with Mesa's single-queue setting, 8160 and 14967 without it, all runs completing, with no GPU recovery, fault or firmware crash in the kernel log. Our earlier daily kernels drove the same firmware build with an older T6040 driver.

Not tested / limits:

  • m1n1 has no T6040 GPU setup yet: it writes neither apple,firmware-version on the GPU node nor the GPU's reserved regions and operating points, so this needs a boot loader that does. Ours keeps the regions at iBoot's addresses, which the board file carries, and has the version write in its next revision; the tested build had the version in its device tree. Without the property the driver refuses the GPU, by design.
  • The UAT policy settings, the root adoption, the bridge and gating, the MMIO registration and POWER_ON_DONE were never tried the A18 Pro's way on this chip.
  • The 26A428 path (T8140) was checked by building and by comparing its generated tables with the tip's (identical), not on hardware.
  • Ray traversal stays refused; few-primitives stays off on 25G83.
  • The new queue-limit KUnit test (42 logical queues on 25G83) was built, not run; our kernel has no debugfs, so the limit was not read back on the running system either.

Commits: 17, in order: the MMU and G17 groundwork (1-9), the bindings and mailbox (10-12), the 25G83 layout and commands (13-15), the T6040 config (16), the J616s GPU node (17).

Base: integration/aurora (6af6603bf85). Built at every commit with CONFIG_DRM_ASAHI=m and CONFIG_DRM_ASAHI_G16=y (G16 and G17 together), W=1, and with KUnit enabled so the tests build: no new warnings (the tip's own 8 fetch_update deprecations, and with KUnit an unused MM static in memacct.rs, remain). dt_binding_check (dtschema 2026.9) is clean for apple,agx.yaml and apple,ascwrap-v6.yaml; dtbs_check passes for t6040-j616s, t8140-j700 and t8132-j713 against them (the J616s tree's only report is the known nonposted-mmio on its pmp-report node).

Userspace: Mesa needs T6040 support to use the GPU; a Mesa PR follows.

🤖 Generated with Claude Code

A single-instance G17 firmware (the M4 Pro runs both roles on one
coprocessor) has no secondary kernel root. Make secondary_root_offset an
Option<u64>: the pagetables region then only needs the primary root,
mirror_secondary_root() does nothing, and the TTBAT context-1 kernel
alias is installed for every PublishedRoots firmware, since
activate_render_context needs it. T8140 keeps Some(0x40000) and behaves
as before.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The M4 Pro firmware runs both G17 roles on one coprocessor. Prepare the
runtime for such a target; T8140 keeps two instances and behaves as
before.

- Firmware.secondary is an Option<Coprocessor>, created only when the
  configuration names a secondary root; boot, init, POWER_ON_DONE, the
  opening-retire wait, stop and drop skip it when absent.
- event::Transport::roles(): event, trace and statistics service and
  the idle recheck cover only the instances that exist.
- Config::recovery_slot_keys (T8140: SLOT_KEYS) replaces the hard-coded
  physical address; None leaves recovery to the firmware's own blame.
- Config::gpu_generation is reported to userspace (T8140: G17).
- The pm_metrics client VA comes from the target's configuration instead
  of hw::t8140::CONFIG in user_aliases and render preparation.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
G17 firmware builds that share the interface version still differ in a
few queue rules. Name them in a fw::Variant that each SoC's Config
points at, instead of hard-coding the A18 Pro firmware's (26A428)
choices:

- qid_floor: the first QID the host may use; queue admission and the
  UAPI queue limit follow the render pairs it leaves;
- pointers: where the item-ring producer and the recovery target live
  in a queue's pointer block;
- mmio_pair_registration: whether render pairs are registered through
  the MMIO queue ports;
- mcache: whether render kick entries may name an MCache table;
- few_primitives and ray_traversal: whether those render and compute
  features are offered and accepted.

The variant reaches the queue-ID table, the compute and render graphs,
command validation and the feature report through the structures that
already carry per-device state. T8140 uses V26A428, which keeps every
value it had, so its behaviour does not change.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The initialization builder names hw::t8140 for the register windows,
the reserved register-map slots, the performance-table configuration
and the operating-point count. Move these references into Config so a
second G17 SoC can supply its own tables, and make the crash log
placement an enum (CrashBuffers::Fixed) that a SoC whose firmware
places its own crash log can extend.

T8140 passes its existing tables; nothing changes for it.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The performance tables of the hardware data have sixteen voltage lanes
per state. The A18 Pro lists one voltage per operating point, which the
host broadcasts to every lane of table A and stores in lane 0 of table
B.

GPUs with several clusters have one voltage per cluster, listed as one
opp-microvolt cell per cluster (as the T6031 device tree already does).
Read every cell, and store cluster n's voltage in lane n of both tables,
leaving the other lanes zero. A single cell keeps the current
broadcast, so the T8140 tables are unchanged. All operating points must
have the same number of cells.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
A register-map slot of the hardware data can describe several
instances of a block: the firmware finds instance n at va + n * size,
and the entry's total size covers all of them while its element size
is that of one instance. On SoCs with several memory controllers or
AFR blocks the instances are not contiguous in physical memory.

Give RegisterWindow an instance count and a physical stride, map each
instance separately, and fill total_size and element_size from them.
Every T8140 window has one instance, so its register map and mappings
are unchanged.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The A18 Pro firmware acknowledges the initialization root with an
INITDATA_ACK message and expects POWER_ON_DONE once both coprocessors
are initialized. Other firmware builds acknowledge by setting word
+0x14 of the primary status A to 1 and send no message.

Add fw::variant::InitAck and Variant::power_on_done. With
InitAck::Status the host polls status A for up to 3 s after handing
over the primary root, failing early if the firmware crashes; the
opening-record wait that follows is unchanged. POWER_ON_DONE is sent
only where the variant asks for it. V26A428 keeps the message and
POWER_ON_DONE, so T8140 is unchanged.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The UAT code reaches the firmware's root and kernel-window tables
through struct page, so it requires the pagetables region to be part
of the kernel's memory map; it refuses a root in no-map memory ("DT
using no-map?"). On the M4 Pro the boot loader reserves the UAT ranges
as no-map regions outside /memory, and the firmware also builds tables
in the l2-pagetables region.

Add mmu::UatQuirks::reserved_tables: the pagetables and l2-pagetables
regions are mapped write-back once (pgtable::ReservedTables), and every
table access goes through with_table(), which uses that mapping for a
page in either region and struct page otherwise. The CPU's cached lines
of both regions are cleaned and invalidated after the firmware booted,
so the host reads what the firmware wrote.

This is a temporary path: once the boot loader adds the UAT ranges to
/memory, as m1n1 does, the regions lose no-map and this commit can be
reverted. T8140 and the older GPUs use UatQuirks::DEFAULT and are
unchanged.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The M4 Pro GPU has only ever run with the UAT policy the Gravity Linux
G16 driver uses for it: every page-table and context-table change is
written back (dc cvac), TLB ranges are invalidated one page at a time,
the handoff page carries only the host header (current slot
0xffff_ffff, the lock never taken) and GPU-only client mappings are
stored as AP 0 with the access in PXN/UXN. Whether the M4 Pro needs any
of this has not been tested; the A18 Pro needs none of it.

Add one UatQuirks field per setting, so a SoC can stay on the policy it
was tested with and each setting can be moved to the A18 Pro's value
on its own after a test:

- clean_tables: clean every page-table change to the point of
  coherency, with the page-table cleaning the G16 backend already uses
  (clean_poc, no longer limited to G16), and also the handoff page and
  each context-table slot after it changes;
- page_tlbi: invalidate page by page (vae1os), or the whole ASID above
  2048 pages, instead of rvae1os;
- handoff_lock and handoff_slot: whether the legacy handoff lock is
  taken and the current slot written at initialization;
- native_gpu_permissions: the AP 0 form for GPU-only mappings of client
  and kernel-lower tables, on map and on reprotect.

The root clean of clean_poc goes through the reserved-table mapping, so
it also covers a firmware root in no-map memory.

UatQuirks::DEFAULT keeps the A18 Pro policy, so T8140, the older GPUs
and the adopted G15/G16 UAT are unchanged.

Based on findings by Gravity Linux (Cody Ho, Niklas Sheth).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The M4 Pro (T6040) GPU coprocessor is an ASCWrap v6 like the A18 Pro's:
the ADT node /arm-io/gfx-asc is compatible with iop,ascwrap-v6, its
wrapper is 0x88000 bytes with the ASC mailbox at +0x8000, and its
vector-base register follows the same layout. Unlike the A18 Pro it
runs a single firmware instance, with the GFX role.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The ASCWrap v6 provider accepts only the T8140's GPU coprocessor
windows. The M4 Pro (T6040) GPU coprocessor is the same block with one
firmware role, GFX, at wrapper 0x28a600000 and vector base 0x28a050000
(ADT /arm-io/gfx-asc).

Replace the hard-coded T8140 checks with a role table per compatible:
each role has its name, its firmware role and its fixed CPU-physical
windows, which the probe validates as before. Add the
apple,t6040-ascwrap-v6 compatible with its single role. T8140 keeps its
two roles at the same addresses.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The M4 Pro (T6040) GPU is described like the A18 Pro's: an asc and an
sgx register range, its GPU power domain and an operating-point table
with one voltage per cluster. It has a single GFX coprocessor mailbox,
operating-point tables for its CS and AFR clock domains (apple,cs-opp
and apple,afr-opp, the names the older Apple GPUs' device trees use),
and two more memory regions than the A18 Pro: fw-data, the loaded
firmware's data segment, where the firmware places its crash log, and
l2-pagetables, where the firmware builds the page tables of its kernel
window. The boot loader may report the macOS version whose boot
firmware loaded the GPU firmware in apple,firmware-version, which
identifies the firmware build.

The memory-region lists differ per compatible, so move them into the
per-compatible branches; the existing compatibles keep the same
regions and names.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The M4 Pro runs the macOS 26.6 GPU firmware (25G83,
mBoot-18000.161.10). It has the A18 Pro firmware's interface version
and the same initialization objects, with these differences:

- One coprocessor carries both roles: one main configuration with the
  secondary-only words (0x300 = 4, the power-configuration pointer), a
  zeroed page at unk_18, 0x32c and 0x344 zero, and one merged root
  naming status B, the power state and the configuration.
- The cluster is 0xd4000 bytes: status B at 0x10000 is 0xf180 bytes
  long, status A follows it, and the power state is its own page, of
  which only the fast-die target and release temperatures are set.
- The hardware data (HwData25G83) is the A18 Pro's up to 0x2554 with
  8 bytes inserted before 0x2570 and 4 more before 0x2614, so the
  completion rings move to 0x2620 and the QoS pointer to 0x26b8. It
  has the CS and AFR performance tables at 0x1a48 and 0x1b90 and
  carries one voltage lane per cluster.
- The power configuration (PowerConfig25G83) is at bundle +0x5380
  and has two blocks fewer than the A18 Pro's; the auxiliary data and
  an AON temperature-source table follow it.
- Region C has other values, now RegionCConfig in Config (T8140 keeps
  its values).
- The five views and the firmware area are separate pages.

fw::variant::InitGraph selects the graph. The 25G83 contents are
constants except for the operating points (the device tree's GPU, CS
and AFR tables), the core count, three pointers and a random word.
They were derived by evaluating, for the J616s, the hardware-data,
region C and power encoders that Gravity Linux (Niklas Sheth, Cody Ho)
wrote for this firmware, which the M4 Pro has run with since; no code
of theirs is carried. At boot the built hardware data, power
configuration and region C are hashed (FNV-1a, without the runtime
words) and compared with the hashes of the encoders' output, and the
GPU is not started if they differ.

The leakage coefficients in the power configuration are this unit's
values. They are not in the ADT, and whether they are per SoC or per
device is not known; they stay a driver constant until that is.

Where the 25G83 graph deviates from the A18 Pro's is only known from
that setup; every value is the one that runs today.

Based on findings by Gravity Linux (Cody Ho, Niklas Sheth).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The 25G83 firmware expects two commands in a render pair's item rings
that the A18 Pro firmware does not take:

- InitBufferManager (tag 5) before the first tiling descriptor that
  uses a buffer manager, naming its UAT context, buffer slot, PB state
  and tiling stamp;
- FragmentBarrier (tag 4) before every fragment descriptor, waiting
  for the paired tiling stamp in addition to the kick-entry dependency.

Add Variant::lead_commands. With it, render publication writes these
commands after each stage's QueueConfig record in its configuration
slot and publishes them as the first item of the stage, and the buffer
manager remembers that it was initialized once a tiling pass with
InitBufferManager was published. V26A428 has no lead commands.

Based on findings by Gravity Linux (Cody Ho, Niklas Sheth).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The 25G83 firmware takes render descriptors with other register
programs, and the M4 Pro's two clusters of ten cores change the
buffers whose layout follows the core layout.

Firmware-build data (Variant::programs, RegisterPrograms::V25G83):

- Tiling array of 83 entries (checkpoints 81/83/83, free-list slot
  index 79): the A18 Pro's without 0x1748 and 0x1740, with the twelve
  tiler parameter sub-buffer registers and 0x10131 = 0x88.
- Main fragment array of 91 entries (checkpoints 87/88/88, slot index
  84): with 0x160b8 = 0 after the tile state and 0x160a8 = 0x20 before
  the closing 0x16068.
- Both drop the SCS enable 0x017d9 unless the stage has shader-context
  scratch; a stage with it carries 0x017d9 = 1 as on the A18 Pro, and
  the counts and indices after it move by one.
- Tile-state base 0x36011 instead of 0x3717f: with the A18 Pro's bits
  a fragment pass more than 80 tiles wide and with more than 4096 tiles
  never completes, which includes every full-screen pass on the
  3456x2234 panel.
- The TPC object also holds the seven tiler parameter sub-buffers the
  tiling registers name.

Core-layout data:

- The deflake buffer's sub-buffers follow each cluster's control words:
  +0x20/+0x2a0 with one cluster, +0xa0/+0x820 with two, where the
  viewport words at +0x900 would land in the third sub-buffer and are
  not written (render::Deflake).
- The compute operand-state buffer holds two banks of three records per
  core and one tail per cluster: 0x44000 bytes with banks of 0x1e000 at
  a stride of 0x1e400 for two clusters of ten cores
  (compute::OperandState).

Only the measured layouts are accepted. V26A428 and one cluster keep
the A18 Pro's programs and buffers.

The 25G83 register lists come from the register programs that the
Gravity Linux G16 driver (Niklas Sheth, Cody Ho) runs on the M4 Pro;
the code is new.

Based on findings by Gravity Linux (Cody Ho, Niklas Sheth).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
Add the M4 Pro GPU to the G17 runtime. Its firmware (25G83) runs on one
ASCWrap v6 coprocessor with the GFX role, which the mailbox provider
starts and stops; the GPU power domains are ordinary PMGR domains. The
ID registers report one die of two clusters with ten cores each,
family 0xa, variant 3, revision B1.

- hw/t6040.rs: the configuration, the 25 register windows (the memory
  controller and AFR slots with four instances each), the performance
  table configuration and region C.
- fw::variant::V25G83: QIDs from 32, the producer at pointer block
  +0x50 without a recovery target, no MMIO render-pair registration, no
  MCache, lead commands, the 25G83 register programs and
  initialization graph, acknowledgment in status A and no
  POWER_ON_DONE. Few-primitives renders hang this firmware and a stalled
  ray traversal wedges the GPU without a recovery, so neither is
  offered nor accepted.
- CrashBuffers::FirmwareData: the firmware asks for its crash log at a
  physical address inside its data segment (the fw-data reserved
  region); the driver maps that range and RTKit echoes the request.
- Config::adopt_root_entries and Config::fabric_bridge: the M4 Pro
  borrows the firmware root as the firmware left it and writes neither
  the fabric bridge nor the gating register, as it has always run.
- The UABI reports the GPU as G16, which userspace drives it as.
- The firmware build is identified, not inferred from the SoC:
  Config::firmware (fw::variant::Firmware) lists the builds a SoC
  supports by the macOS version whose boot firmware loaded them, which
  the boot loader reports in apple,firmware-version on the GPU node
  (as m1n1 writes it). The T6040 accepts macOS 26.6.2 (iBoot
  mBoot-18000.161.10, the 25G83 firmware) and refuses anything else
  before touching the GPU, naming what it found; the build it accepts
  is logged. The T8140 keeps its single unidentified build.

Where the values differ from the A18 Pro's they are the ones the M4 Pro
runs with today. The UAT quirks other than the no-map tables, the root
adoption, the bridge and gating, the MMIO registration and
POWER_ON_DONE were never tested the A18 Pro's way on this chip, and
each stays a separate setting until it is.

Based on findings by Gravity Linux (Cody Ho, Niklas Sheth).

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
Describe the M4 Pro GPU as the A18 Pro's is described:

- pmgr_gfx: the PMGR block of the GPU power domains (ADT pmgr devices
  AFR 0x181 at +0x100 and GFX 0x182 at +0x110 with parent AFR), laid out
  as on the T6031. Both are always on: the GPU firmware keeps running
  and does not survive GFX going off. GPX (0x180 at +0x0) is left alone,
  as the boot loader leaves it on and nothing uses it.
- gfx_mbox: the ASCWrap v6 coprocessor mailbox with the GFX role (ADT
  gfx-asc), its send-empty and receive-not-empty interrupts.
- gpu: the asc and sgx ranges, the mailbox and the GFX power domain.
- The PMP's GPU report entry (pmp_gfx) gets the GFX domain as its
  parent, as on the T8140.

On the J616s the GPU firmware runs from boot on, with GFX always on, so
the board claims pmp_gfx always-on: the PMP sees the GPU on whenever it
is powered. Without the GPU node the entry stays unclaimed (the PMP
crash of a claim with nothing powering the GPU, fixed by commit
c4722f3).

On the J616s, add its sixteen operating points in performance-state
order with one opp-microvolt cell per cluster (ADT /arm-io/sgx
perf-states and perf-states1), the eight of its CS and AFR clock
domains (cs-perf-states, afr-perf-states), and the memory the boot
loader reserves for the GPU firmware: its data segment (fw-data),
where it places its crash log, and the UAT ranges (ttbs, pagetables,
l2-pagetables, handoff).

The operating points are binned per device: these are this unit's
values until the boot loader fills them in. The UAT ranges are no-map
until the boot loader adds them to /memory.

Binding: apple,agx-t6040 and apple,t6040-ascwrap-v6, earlier in this
series.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
Bring in the J616s USB-C ports (aurora-silicon#403). Its Type-C nodes and this
series' GPU nodes were added at the same place in t6040-j616s.dts;
both are kept.

Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
The boot loader finds the GPU node through the gpu alias, as m1n1's
dt_set_gpu() does on every other Apple SoC (t600x, t602x, t603x, t8103,
t8112, t8122, t8132, t8140 all have it). Without it a loader cannot fill
in apple,firmware-version on the T6040 GPU node, and the driver refuses
the GPU unless the device tree carries the property itself.

Fixes: 92d0217 ("arm64: dts: apple: t6040: Add the GPU")
Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
@jgoyvaerts

Copy link
Copy Markdown
Contributor Author

Pushed one fix on top: arm64: dts: apple: t6040-j616s: Add the gpu alias (b147e1f). Every other Apple SoC's device tree has gpu = &gpu;, the node a boot loader uses to fill in apple,firmware-version (m1n1's dt_set_gpu() finds it there); this series left it out, so a loader could not fill in the property and the driver refused the GPU unless the device tree carried it. Checked with our loader's DT harness: with the alias, the loader writes <26 6 2> on the GPU node of the J616s tree.

🤖 Generated with Claude Code

@fbalazsy

Copy link
Copy Markdown

Data from a second J616s (MacBook Pro 16" M4 Pro, 48 GB) for the per-unit values in the J616s GPU node. It comes from this unit's ADT, captured through m1n1 proxy and read offline. Read-only: I haven't booted this branch on it yet.

GPU operating points are binned per unit. /arm-io/sgx perf-states / perf-states1 against the opp-microvolt cells in this PR. The frequencies and state order are identical:

state MHz #408 DT (mV) this unit's ADT (mV) difference
0 0 125 / 125 125 / 125 +0 / +0
1 338 605 / 585 605 / 600 +0 / +15
2 618 655 / 640 640 / 655 -15 / +15
3 796 690 / 690 685 / 700 -5 / +10
4 924 715 / 725 720 / 730 +5 / +5
5 952 755 / 770 765 / 780 +10 / +10
6 1056 755 / 770 765 / 780 +10 / +10
7 1062 795 / 810 805 / 820 +10 / +10
8 1182 795 / 810 805 / 820 +10 / +10
9 1182 845 / 860 855 / 870 +10 / +10
10 1312 845 / 860 855 / 870 +10 / +10
11 1242 900 / 915 900 / 915 +0 / +0
12 1380 900 / 915 900 / 915 +0 / +0
13 1326 945 / 960 940 / 955 -5 / -5
14 1470 945 / 960 940 / 955 -5 / -5
15 1578 1030 / 1045 1025 / 1045 -5 / +0

At 338-1312 MHz this unit's values are mostly 5-15 mV higher. A few states are lower, and states 2 and 3 differ in opposite directions between the two cells. So this DT as merged would run this unit's GPU below its own voltages in most states. It's a concrete case for the loader filling in the OPPs (m1n1 dt_set_gpu() style), or for keeping the GPU node disabled when they weren't filled in.

Reserved memory: same addresses on this unit. In the ADT, gfx-data is 0x10001d44000 (size 0x138000), and gpu-region, gfx-shared-region, gfx-shared-l2-region and gfx-handoff are 0x10bfffb8000, 0x10bfff78000, 0x10bfff74000 and 0x10bfff70000, all matching this PR's reserved-memory nodes. This unit's GPU runs from those regions on 7.1.12 m4/aurora ("Firmware accepted the initialization graph").

Firmware version string. This unit's macOS 26.6.2 was installed through Recovery, and its os-fw-version is mBoot-18000.161.10.701.1 (system firmware mBoot-20457.1.29). m4-loader's version table only matches mBoot-18000.161.10, so a loader built from it won't recognise this as 26.6.2 or write apple,firmware-version here, and this driver will then refuse the GPU. Our loader build accepts the .701.1 suffix as the same 26.6.2 firmware. Whether its GPU firmware is byte-identical to 25G83 is something the probe-time hash in this series would answer. I'll report that once this branch has run here.

@fbalazsy

Copy link
Copy Markdown

Ran the KUnit tests of this PR at b147e1f46 under QEMU 11.1.2 (kunit.py run --arch arm64, -M virt -cpu max, TCG; GCC 16.1.1, rustc 1.99.0).

Result: Ran 93 tests: passed: 93, including the new asahi_g17_shared_capacity case a_qid_floor_admits_only_the_render_pairs_it_leaves (42 logical queues on 25G83). No oops or WARN. The build shows only the existing fetch_update deprecation warnings from rustc 1.99.

kunitconfig
CONFIG_KUNIT=y
CONFIG_RUST=y
CONFIG_ARCH_APPLE=y
CONFIG_ARM64_16K_PAGES=y
CONFIG_PM=y
CONFIG_OF=y
CONFIG_DRM=y
CONFIG_IOMMU_SUPPORT=y
CONFIG_THERMAL=y
CONFIG_THERMAL_OF=y
CONFIG_APPLE_PMP=y
CONFIG_DRM_ASAHI=y
CONFIG_DRM_ASAHI_G16=y

@RyanTheTide RyanTheTide left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. It's a big, careful series, and most of it is fine to take. We're holding it for now because of two blockers, one about attribution and one about the J616s device tree. Details below, with file:line references to the series merged onto integration/aurora 1aa2857.

Blockers

  1. Gravity attribution in the crash log mapping (commit 16, "drm/asahi: g17: add the T6040 (M4 Pro)"). In g17/mod.rs (about lines 380-393), the fw-data crash log path follows Gravity Linux's CrashBuffer::shmem_map in drivers/gpu/drm/asahi/g16.rs (gravity-m4) step for step. It takes the same reserved region by name, uses the same checked_sub/checked_add bounds against res.size(), the same Mem::try_new(res, MemFlag::WB) and the same field set, and has a reworded copy of the SAFETY comment. g16.rs is GPL-2.0-only, and our file is GPL-2.0-only OR MIT, while the PR says there is no Gravity code in the series. It's small, but our rule on Gravity code is strict. Either credit it properly (Originally-by Cody Ho / Niklas Sheth, as appropriate, with a Link, and keep it out of the dual-licensed file), or replace it with an independent design. Mapping fw-data once at boot and handing out subranges would also stop the driver remapping 0x138000 bytes on every crash log request.
  2. The J616s GPU node ships one unit's values with status = "okay" (t6040-j616s.dts, the gpu node, its OPP tables and reserved-memory). The driver refuses safely when no apple,firmware-version is present: firmware_variant() runs before the first GPU register read. But a loader that writes the version without filling in the per-unit data makes the driver run with your unit's voltages. fbalazsy's numbers show a second unit needs 5-15 mV more in most states. Ryan's J616s is on a different firmware (26.6.1) and has different reserved-memory addresses, and the regions sit at about base + 48 GiB, so a lower-memory configuration would not have them in DRAM at all. Please keep &gpu disabled in the board file, or leave the regions and OPPs out until a loader fills them from the ADT and enables the node, as m1n1 does on the other SoCs. The version property alone shouldn't count as proof that the per-unit data was filled in.

Major

  • Firmware identity (g17/mod.rs around 1080-1110). The init-data hash only covers the driver's own constants (not OPPs or the core count), so it catches drift in the code, not a different firmware build or a different unit. The .701.1 question fbalazsy raised can't be answered by it. The G16 backend already checks the SHA-256 of the loaded 25G83 TEXT (g16/hw.rs, g16/boot.rs). Reusing that check here would make this solid.
  • Leakage coefficients (fw/initdata/v25g83.rs, LEAKAGE). The commit says it isn't known whether they're per SoC or per device, and the hash then passes silently on another unit. Either take them from the DT, filled by the loader (m1n1 computes them from the ADT on other SoCs), or show that they're per SoC.
  • T8140 buffer layouts now depend on the cluster count (fw/compute.rs OperandState::new(clusters, cores), fw/render.rs Deflake::new(clusters)). The A18 Pro keeps its old layout only if it reports one cluster. Please select these through Config/Variant, so the T8140 gets its layout explicitly rather than through a hardware count read at probe.
  • No-map table path (commit 8, pgtable.rs with_table). For a table outside the two reserved regions it falls back to Page::borrow_phys_unchecked, which is undefined behaviour on a no-map system. When reserved is set, use the checked borrow and return an error. Coherency is also assumed for firmware writes to l2-pagetables after the one clean and invalidate at boot; please document why that holds. As you say, this commit should go once the loader puts the ranges in /memory.
  • A second 25G83 implementation. The G16 backend already drives 25G83 for T8132. Building the T6040 on G17's queue and render code makes sense, but the firmware identity check and the region names (shared-l2 vs the new l2-pagetables) should be shared, not duplicated.

Minor

  • mem.rs PAGE_TLBI is a global static set from Uat::new. Make it a field of the UAT, or document that it assumes one GPU.
  • apple,agx.yaml: apple,firmware-version needs minItems: 3 as well as maxItems: 3.
  • fw/initdata.rs PerfGroup::fill: nothing checks that the number of voltage cells is 1 or equals the cluster count. Check it in opps().
  • queue/render/memory.rs write_leads: fragment_id as u32 truncates.
  • fw/queue.rs PointerBlock::word() maps any offset other than 0x50 to word_40. Use an enum or assert the two valid offsets.
  • A few comments name Apple Metal driver internals (emitBarrierCommands, "the 25G83 Metal driver's default parameter-buffer sizes"). Please describe the facts instead; this is a public tree.

What's fine. I checked the T8140 path in detail. UatQuirks::DEFAULT leaves the A18 Pro policy as it was, clean_poc doesn't run on the T8140, the QID floor is 0 and max_queues stays 58, the boot order and the mailbox are unchanged, and the region C, register windows and OPP cells keep their values, apart from the cluster-count point above. The UAT policy, v25g83 layouts, region C, lead commands and register arrays read as independently written from facts, and the "Based on findings by Gravity Linux" credit covers them. I ran an exact-line match and a constant match against gravity-m4, niklassheth/linux integration/m4-7.3, gpu/g16-dev, bits/210-gpu and Gravity's PUBLIC_neo_agx_rust_driver. Only trivial lines matched, and only 2-5 of the 155 new constants appear anywhere in Gravity.

Suggested way forward. Split the series. Commits 1-7 and 10-15, plus the cluster-count and with_table fixes, can go in on their own once they've had a Neo GPU run. Commit 16 follows once the crash log attribution is resolved (ideally with the TEXT hash). Keep 17-18 back until the GPU node is loader-filled or disabled by default. Once that's done, we'll boot the J616s without the version property, check there's no SError after "PMP started", and then merge.

RyanTheTide added a commit that referenced this pull request Oct 24, 2026
drm/asahi: g17: T6040 (M4 Pro) GPU, #408 with review follow-ups (GPU disabled on J616s)
@RyanTheTide
RyanTheTide merged commit b147e1f into aurora-silicon:integration/aurora Oct 24, 2026
@RyanTheTide

Copy link
Copy Markdown
Contributor

Merged through #420 (137462e), with your commits unchanged plus our follow-ups from the review: the fw-data crash-log mapping moved to a GPL-2.0-only g17/data_segment.rs crediting Cody Ho and Niklas Sheth, the J616s GPU node kept disabled in-tree until a loader fills in the per-unit values, the core-layout buffers taken from Config, and the smaller items. Tested on the Neo (no regressions, fault test passes) and on our J616s with the GPU disabled (clean PMP start, no SError). Thanks.

@jgoyvaerts
jgoyvaerts deleted the m4pro/gpu-t6040 branch October 24, 2026 16:14
RyanTheTide pushed a commit that referenced this pull request Oct 25, 2026
…rved regions

With reserved table regions (no-map systems such as the T6040), a
page-table page outside them went through Page::borrow_phys_unchecked(),
which is undefined behaviour when the page is other no-map memory
without a struct page. Use the checked borrow there and refuse such a
page with -EIO; systems without reserved regions keep the unchecked
borrow of an allocated page-table page.

Document the single cache invalidate of the reserved regions at boot.
It runs after the host has already written table entries, which is safe
because the T6040 cleans every host table store to the PoC
(UatQuirks::clean_tables), so the invalidate drops only clean lines. Not
invalidating again assumes the firmware stops writing the tables the
host walks once it has booted; say that this is an assumption. The
wrapper's comment claimed the invalidate came before the first host
edit, which it doesn't.

Link: #408
Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Assisted-by: LLM
RyanTheTide added a commit to aurora-silicon/m1n1 that referenced this pull request Oct 25, 2026
Retain the public model provenance in the implementation. The numeric tables came through Jonathan Goyvaerts from Gravity Linux work by Cody Ho and Niklas Sheth. The loader calculates them from per-chip data. Preserve the proven generic restore diagnostic now that the GPU uses the helper too.

Originally-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Link: aurora-silicon/linux#408
Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
RyanTheTide added a commit to aurora-silicon/m1n1 that referenced this pull request Oct 27, 2026
Retain the public model provenance in the implementation. The numeric tables came through Jonathan Goyvaerts from Gravity Linux work by Cody Ho and Niklas Sheth. The loader calculates them from per-chip data. Preserve the proven generic restore diagnostic now that the GPU uses the helper too.

Originally-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com>
Link: aurora-silicon/linux#408
Assisted-by: LLM
Signed-off-by: Ryan Murray <ryan@aurorasilicon.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants