Skip to content

drm/asahi: g17: Run the M5 Max (J716c) GPU on bare metal - #1056

Closed
erikwb wants to merge 117 commits into
funkzy/t6050-gpu-on-integrationfrom
j716c/t6050-gpu-max
Closed

erikwb wants to merge 117 commits into
funkzy/t6050-gpu-on-integrationfrom
j716c/t6050-gpu-max

Conversation

@erikwb

@erikwb erikwb commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

What this is

#1012 runs the M5 Ultra on bare metal and refuses the M5 Max at probe. This series makes the M5 Max (J716c, G17C, one T6050 die) work on #1012's driver:

  1. J716c: write the MCC mask and topology words. HwData +0x10a8 names all eight memory cache controllers (0xff), and +0x100c/+0x1048 carry the M5 Max's topology (16, and its 40 cores). These come from t6050-gpu, which the M5 Max ran on, and were not carried into drm/asahi, drm/apple: M5 GPU and firmware-27 display on integration/aurora #1012.
  2. J716c: apply its controller defaults. The M5 Max's own power controller configuration (196098 mW, its four clusters' leakage), for parts with its leakage calibration, also from t6050-gpu. drm/asahi, drm/apple: M5 GPU and firmware-27 display on integration/aurora #1012 only had J714's, which no M5 Max matches.
  3. Run the M5 Max on bare metal. The M5 Ultra's bare-metal profile (published roots, crash logs in fw-data, its UAT policy) and the identity of the G17C firmware both roles load. Only the M5 Pro is refused at probe now.
  4. Match the T6050 GPU by its G17S compatible. apple,agx-g17s, which the M5 Max's apple,agx-g17c and the M5 Ultra's apple,agx-g17d fall back to, in place of the undocumented apple,agx-t6050.

The M5 Pro and the M5 Ultra are unchanged.

Dependencies

  • The T6050 GPU mailbox, memory and fabric floor, and system level cache, filed separately against integration/aurora.
  • The T6050 GPU device tree nodes and binding, filed separately on arm64: dts: apple: add the J716c (MacBook Pro 16-inch, M5 Max) #945.
  • The boot loader fills in the GPU's reservations and its leakage calibration words, and accepts apple,agx-g17s (aura-internal).
  • Mesa: agx3c admits the M5 Max with ASAHI_M5_BRINGUP=1.

Testing (J716c, bare metal)

  • The GPU boots both firmware roles; the desktop runs on the GPU through zink and Vulkan.
  • Seven Vulkan CTS synchronization tests (multi-queue, timeline semaphores, cross-instance) that faulted the GPU on every run without the first two commits: 21/21 passes over three runs, no GPU recoveries.
  • A 1-in-100 sample of the Vulkan CTS default list (17073 tests): 8345 pass, 23 fail, 52 crash, 8557 not supported, no device loss.
  • Every commit builds with integration's defconfig + asahi.config, with KUnit.

Known issues

Multi-queue synchronization on the M5 Max is still weaker on this driver than on the earlier native-runtime one. In the CTS sample above, two GPU MMU faults (each failing one client's render; the GPU stays up) and 17 job timeouts (each costing one client its device), mostly in synchronization*.op.multi_queue, cross_instance and timeline-semaphore tests. 65 tests fail or crash here that pass or flake on the native-runtime driver. Follow-up work.

🤖 Generated with Claude Code

DjDeveloperr and others added 30 commits November 5, 2026 20:58
…are-metal GPU tables

Add the M5 Pro/Max configuration (T6050) and its OF match, selected by
the ID registers. A G17 configuration now names its firmware protocol,
and the 26A428/25G83 graph tables become optional, since the M5 graph
carries its own. Probe refuses an M5 board that has no bare-metal GPU
page tables, and the M5 boot entry point stays closed until one does.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..15e1b3e4fa44 (3 commits); SPTM/APIF removed; adapted to integration's g17::Config, ProbeConfig and Firmware::Identified; original 6613f5ab7b7e, 1a021061f226, 15e1b3e4fa44 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…d context pins

Add the UAT pieces the M5 queues use: sparse-tile binds and their
zeroed and per-VM sink tiles, render pool mappings that move with a
pool identity, the metrics alias, execution-context pins and the M5
dynamic kernel ranges. On M5, a healthy last-job release skips the VM
drain.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..692d42fb35d7 (5 commits); SPTM/APIF removed; adapted to integration's mmu.rs (7.3 GPUVM, UatQuirks, ExecutionRoot, SG leases); comments and names reworded; M5 paths restored where the first rewrite lost them: one prepare_bind_object, with the sparse-tile bind; the G15/G16 adopted UAT (Uat::new_adopted_inner) sets m5: false, for integration's own config (defconfig + asahi.config), which builds G16 beside G17 and failed with E0063 without it; original 6613f5ab7b7e, e25ba8a6fb2b, 1a021061f226, f49e91b3f364, 692d42fb35d7 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Add the M5 firmware's InitData layout and its graph builder, and let
InitData address the M5 bundle, private state, recovery views, effort
word and control ring at their M5 locations. Firmware events stay off
on M5 until its queues take submission, and the M5 effort word is
published as zero.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..77dc6eb34210 (5 commits); SPTM/APIF removed; adapted to integration's InitData (status_b and views fields) and fw::Variant; comments and names reworded; M5 paths restored where the first rewrite lost them: the M5 metrics binding; original 6613f5ab7b7e, e25ba8a6fb2b, 1a021061f226, f49e91b3f364, 77dc6eb34210 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…-list layout

Add the M5 pool descriptor and free-list encodings, the render storage
demand they are sized from, and M5 render and compute pools beside the
existing ones. Render pool identities become leases that can move
between logical contexts, so idle storage is reused instead of
rebuilt.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..77dc6eb34210 (4 commits); SPTM/APIF removed; adapted to integration's freelist (capacity refusals, lower-window placement, per-context render pools); comments and names reworded; original 6613f5ab7b7e, e25ba8a6fb2b, cda233bc91f7, 77dc6eb34210 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…mand port

Add the M5 queue records, configurations and notifications, and the
host command port that installs a queue pair and submits work. On M5,
tiling, fragment and compute share the compute ring of each priority,
whose outer credits are claimed before either host port is written,
all under the device mutex.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..f49e91b3f364 (4 commits); SPTM/APIF removed; adapted to integration's InitData work publication and resource wait domains; comments and names reworded; M5 paths restored where the first rewrite lost them: the UAT's range for the compute kick rings and timestamps, the M5 control records, reports and statistics mask, the M5 firmware preparation, the fault window read, and the scheduler page from the retention pool; original 6613f5ab7b7e, e25ba8a6fb2b, 1a021061f226, f49e91b3f364 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Add the render and compute program resources, the shader-write,
stage-prefix and whole-attachment render flags, matrix and no-query
compute, and their feature bits. The command parser takes the resource
commands and the stage-prefix tail; validation applies the M5 command
set only where the firmware variant has it.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..a507978ff411 (7 commits); SPTM/APIF removed; adapted to integration's UAPI numbering, ray-query stages and fw::Variant; the M5 UAPI flags and features in the UAPI header with agx3c Mesa's numbers, the stage-prefix fields appended to drm_asahi_cmd_render; interim forms until the render submission commit; comments and names reworded; M5 paths restored where the first rewrite lost them: VM_BIND accepts DRM_ASAHI_BIND_SPARSE_TILE; original 6613f5ab7b7e, e25ba8a6fb2b, bd4a3d7a0c97, 2f58522abec7, 2c5c77b6aaba, e393a52c7151, a507978ff411 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Encode the M5 compute program and descriptor, install M5 compute queues
cold and refresh their priority on reuse, and publish compute as M5
work on the shared ring. Timestamps come from the queue's own record,
waits on other queues stay in firmware, and the classic compute paths
are never taken on M5.

A closed context's hung M5 compute is killed through its queue's exit,
and the kill settles once the full-queue retirement witness covers the
killed commands.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..e393a52c7151 (7 commits); SPTM/APIF removed; adapted to integration's compute queue (cached graph, descriptor ring), completions and runtime; interim forms until the render submission commit; comments and names reworded; M5 paths restored where the first rewrite lost them: the firmware's own compute queue record, the shared compute aperture and global state left to G17P, every compute doorbell in the submission history, the M5 compute pool format, the kill of a closed context's hung compute settled by the full-queue witness, the full CPU refill floor on M5 with the feed's guard against unexecuted intervals and only exact compute execution records feeding it, and the item ring's count and cursor checks before its room is computed; M5 compute capacity threaded through integration's admission (rebind, readiness, growth, rearm); original 6613f5ab7b7e, e25ba8a6fb2b, f49e91b3f364, 7c95830ea3a1, 77dc6eb34210, ee68abb10520, e393a52c7151 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Add the M5 tiling and fragment command encodings, their register
programs and kick assembly, the M5 block geometry and buffer layout,
and the per-generation render state views. Nothing submits them yet.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..77dc6eb34210 (6 commits); SPTM/APIF removed; adapted to integration's fw::render arguments and register programs; interim forms until the render submission commit; comments and names reworded; original 6613f5ab7b7e, 3d2524b7e48a, e25ba8a6fb2b, 5a71513221de, bd4a3d7a0c97, 77dc6eb34210 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Build, park and reuse M5 render pairs and submit renders through them
on the device-mutex path, renewing the parameter-buffer token when its
binding changes. Attachment conflicts across pairs are ordered in
firmware, and unrelated whole-attachment renders are not ordered.

[ rebased onto integration/aurora: rewritten from t6050-gpu 6613f5ab7b7e..c3505169be1b (11 commits); SPTM/APIF removed; adapted to integration's render queue, buffer manager and device-mutex render path; the M5 features reported in GET_PARAMS except no query access; comments and names reworded; the no-query-access feature defined but not reported, as in the tested reference; M5 paths restored where the first rewrite lost them: a context's render channel moved to a new pair once its publications retire, and the replay of compute a render consumes, held until the owner's renders park; original 6613f5ab7b7e, e25ba8a6fb2b, 1d74fda93777, cda233bc91f7, 3cfa33f3237f, 77dc6eb34210, b0fdab9e3b55, 2f58522abec7, 2c5c77b6aaba, a507978ff411, c3505169be1b ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…ged renders

On M5, Mesa that already sends staged renders under
DRM_ASAHI_FEATURE_RENDER_STAGE_PREFIXES sends them in a 368-byte form.
This kernel accepts a staged render only as the full 480-byte struct
drm_asahi_cmd_render, so the first such render failed with EINVAL and
the application lost its device. That Mesa now leaves staged renders
off and keeps working; Mesa that sends the full command finds them
under a new bit.

The kernel reports the full-command form as
DRM_ASAHI_FEATURE_RENDER_STAGE_PREFIXES_V2 (bit 31) and no longer
reports bit 25, which stays defined for the shorter form. What the
kernel accepts under DRM_ASAHI_RENDER_STAGE_PREFIXES is unchanged.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Add a host GPU power-budget governor for M5 (asahi.m5_thermal_governor,
off by default). It holds the SoC hotspot near a target by lowering the
budget the firmware reads, shares the heat with a CPU frequency
ceiling, and ramps the fans with the hotspot under load. Publishers
note GPU activity with or without the device mutex.

[ rebased onto integration/aurora: rewritten from t6050-gpu 77dc6eb34210..60a4dd529842 (6 commits); SPTM/APIF removed; adapted to integration's Shared work items and the port's CLPC demand; comments and names reworded; original 77dc6eb34210, 0a75aae77faf, 284638c2a784, cc2c9850d389, f5de511cb487, 60a4dd529842 ]
Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
drm_sched_fini cancels each job left on the pending list through
cancel_job and frees it right after, while the job's completion callback
is still registered on the scheduler fence; cancel_job must signal that
fence. When the queue could not settle the packet in time (a busy or
failed runtime, or a settle racing another queue's timeout handling),
cancel returned with the fence unsignalled. A later settle then ran the
freed job's callback: drm_sched_job_done on a job with no s_fence, or a
NULL callback once the memory was reused, both seen as oopses in
drm_sched_job_timedout under 12-process synchronization CTS.

Fail such a completion while its queue still owns it (the VM stays
pinned until the queue retires it) and wait briefly for a signaller
that already claimed it.

Signed-off-by: Diljit Singh <hey@djdev.me>
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
The M5 Max shares the T6050 SoC and the gpu,t6050 binding with the M5
Pro, but its GPU has four clusters of ten cores instead of two, all eight
AMCCs enabled instead of four, and its own voltage tables.

Move the board-specific HW data behind M5Board { J714, J716c } and pass
the board from the Config to the HW data and initdata builders:

- Performance states carry a core and an SRAM voltage per table. HW perf
  lane n carries table n and lanes past the board's table count carry
  table 0, which reproduces the J714 data exactly. J716c has four tables
  (0 == 3, 1 == 2); they, the auxiliary-domain table and the
  relative-frequency curve come from its ADT.
- Cluster count 2 / 4, core count 20 / 40 and core type 0x18 / 0x19
  (the core type follows the ID variant).
- The minimum parameter-buffer size: 2 MiB / 3 MiB.
- The register map. J716c maps all eight AMCC windows in slot 3 (MCC
  reg[14..21]), and four instances, one per cluster, in slots
  20 and 24, where J714 has two at the same stride. Slot 44 is a fixed
  pair on both.

Gpu::new() checks the board's cluster and core counts against the GPU's.
Only J714 is probed; the following commits complete the J716c data and
the last one probes it.

The J714 data is unchanged by construction; not retested on J714.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: adapted to integration's g17::Config (the board check in Gpu::new, the board field of the T6040 and T6041 configurations) and InitData::build_m5; the SPTM boot path's board argument removed; comments reworded for publication (code unchanged by it), and message lines that named register offsets; original fbbb15c50bee ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
The three AON PTD records (RT0g, RT1g, RT0r) carry device-specific table
indices. Encode them per board; the M5 Max rows are 0x58, 0xa3 and 0xb8.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora; original c280d3b1777e ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
Both boards bind gpu,t6050 and probe used the J714 configuration
unconditionally, so a Max was rejected at the identity check (ID_VERSION
variant 4 instead of 3).

Add the J716c Config (variant 4, GpuVariant::C, M5Board::J716c) with its
crash-log buffers at the ADT gfx-data-shared-ro-base and
gfx1-data-shared-ro-base, and select the configuration from the ID
registers after powering AFR, with the same Resources the GPU then boots
with. probe_g17() now takes those Resources from its caller; on the other
G17 path they are created before the DMA mask is set instead of after.

On J714 the only change is that AFR is powered and the ID read once more
during probe; both are idempotent.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: adapted to integration's probe_m5, probe_g17 and Config; the SPTM profile and the SPTM probe with its AFR power-up removed, so J716c, like J714, is refused at probe until it has bare-metal GPU tables; original 11a9c30bacae ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
The M5 Max uses 8 MiB render blocks and an 8 GiB private-memory
address inventory, twice the M5 Pro's 4 MiB and 4 GiB. With the M5 Pro
geometry, compute work on the Max ends in firmware recoveries within
seconds.

Select the render pool from the board at probe, before any pool is built,
and size render and compute pool geometry and budgets with it. The M5 Pro
values are unchanged.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: adapted to integration's compute_m5 and PoolGeometry; comments reworded for publication (code unchanged by it), and one word of the message; original 8d4eed54d540 ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
The last segment-control word of an M5 render is the part's minimum
parameter-buffer size in 32 KiB pages. It was passed the
segment count (64), which is right only on the M5 Pro. Derive it from the
physical cluster count instead, for every render including single-group
ones: 64 pages on the M5 Pro (unchanged) and 96 on the M5 Max. A
compile-time check ties the page count to each board's HW data.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: adapted to integration's queue/render/m5_render_submission.rs; the m5_render_resources.rs hunk dropped, as integration has no such file; comments reworded for publication (code unchanged by it), and message lines that named register offsets; original d290b4a2ceab ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…k on

After a compute timeout, the driver asks the firmware to kill the closed
context's remaining commands and, once the kill is acknowledged and every
command settled, returns the queue to service with fresh backing. On M5 the
next owner's first command on that queue then never runs: it retires with a
zero-duration record and nothing written, and that client sees a lost
device. Commands after it run normally.

A killed QID stays killed in the firmware until a queue configuration with
its QoS update set binds it to a scheduler page other than the one the
firmware has for it. The new owner's first publication carries such a
configuration, but its command reaches the still-killed queue first.

On M5, reset a killed queue's install state, so the next owner's first
publication sends the configuration alone and waits until the firmware
consumed it before any command is published. The install record now goes
at the ring's current producer: a reinstalled queue restarts at the
firmware's consumer, not at zero, where the append was refused. Other GPUs
retire a killed queue instead: it keeps its QID and installed graph but
takes no owner again, and its backing and exited binding are still freed.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: M5 half only: the retirement of killed queues on other GPUs and the reinstall and retirement logs removed; adapted to integration's recycle_killed; original 457ef7786341 ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On M5 the recovery path reads no firmware fault reason, so a blamed
compute command whose completion record shows zero duration is taken to
have never executed: its blame is withdrawn and the command is replayed.
But firmware writes exactly that record when it cancels a command that
was running, which is what happens when that command faulted. The replay
then faults again on the next kick.

Read the GPU MMU fault status at every M5 recovery. When it holds a valid fault,
report the recovery as a page fault, as the other G17 profile does from
the firmware's reason word, so attribution keeps the firmware's blame and
the command is not withdrawn. Also log the decoded fault: access, reason,
address, page-table level, requestor and VM slot.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
[ J775D: backport onto integration/aurora: adapted to integration's Backend::fault_sources, where InitData::is_m5 already exists; the classify hunk dropped, as integration does not withdraw a blamed compute command; the recovery log prefix changed to the series' "asahi:"; original 6b85752ad673 ]
Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
No change for any GPU that runs today: the T6040, T6041 and J713 builds
keep their digests. It lets a later commit identify the M5 Ultra's
(J775D) GPU firmware, whose loaded image holds two roles plus ranges the
boot loader rewrites on every boot.

fwid admits a GPU firmware build by the SHA-256 of its loaded TEXT with
the 8 entropy bytes of its two entropy records zeroed. That assumes
one firmware role per TEXT region and nothing else the boot loader
changes. Neither holds for every SoC: the boot loader can load two GPU
roles' TEXT back to back into one region, each with its own pair of
entropy records, and fill other ranges of it on every boot (two RTKit
sections it patches and two load fields of each image header).

A build now lists its entropy records per role (at most two roles) and
the (offset, length) ranges the boot loader fills. Both are zeroed in a
private copy before hashing, so a loaded image and the firmware file it
came from hash alike. A build with no boot-filled ranges is hashed as
before, in place, with only its entropy zeroed: every existing build
keeps its digest. A record counts only if it is whole (its tag, length
and 8 entropy bytes all inside TEXT), in both paths and when an unknown
image's records are located, so a tag in the last 8 bytes of an unknown
image is refused like any other unknown image instead of indexing past
the end.

measure() copies the loaded TEXT twice, before anything is written to
the GPU, and keeps the copy only if both agree; Measured::find() looks
a build's images up in that one measurement, and Measured::identity()
states an unknown image in the form a pinned build would take (sizes,
each role's records, the number of boot-filled ranges zeroed, the
digest). identify() is built on them.

Testing: the fwid unit tests cover two roles, boot-filled ranges,
malformed records and ranges (each leaving the image unchanged) and a
record without its entropy bytes, in a pinned build's lookup and in an
unknown image's report. They pass on the host: fwid.rs built by rustc
--test against a stand-in for the kernel crate (KUnit itself was not
run). Without the whole-record check the new test panics on the slice.

The existing builds (T6040, T6041 and J713) name their one pair of
records and no boot-filled ranges, and keep their digests.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…oard

No change in what is written for J714 or J716c. The per-board values
move into a table so the M5 Ultra (J775D), which runs the same firmware
build with other values, can be added as a third board.

M5Board picks each per-board value of the M5 graph by a match on the
board: performance and auxiliary-domain states, core count and
revision, AON records, register windows, render pool and minimum
parameter buffer. The policy controller configuration and the leakage
calibration it was made for stay outside it. A third T6050 board runs
the same firmware build with other values of each, more voltage tables
than a performance state holds and a second auxiliary lane.

Collect the values in fw::m5::initdata::Board, a table per board (J714
and J716C, with M5Board's values), and name a configuration's board in
Config::m5 in place of Config::m5_board. The builder, the HW data and
AON encoders, the render pool selection, the board check and the
maximum frequency read the table, and the table carries the controller
configuration and its calibration. A performance state holds up to
eight voltage tables and an auxiliary state two lanes; a board names
how many tables it uses, and lanes past them carry table 0, as before.
J716C keeps J714's controller configuration and calibration, so, as
before, the configuration applies only to a part with J714's
calibration.

No change in what is written for J714 or J716c.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra Mac Studio (J775D) the GPU driver found no configuration
for the chip ("No M5 configuration for GPU variant") and the GPU stayed
off. It now boots both GPU firmware roles on bare metal and brings up
the M5 firmware graph; jobs are accepted from the later submission
commit on.

J775D (T6050, G17D, two dies) runs the M5 firmware's protocol with the
UAT roots both roles publish (FirmwareProfile::M5PublishedRoots).

- The J775D board: both dies' register windows with their explicit
  instances (32), one voltage table per cluster in the main
  performance table, the two-lane auxiliary table, the six Ultra AON
  records, core type 0x1a revision 2, 8 clusters of 10 cores and the
  minimum parameter buffer for eight clusters. No policy controller
  configuration. Values from the J775D ADT.
- hw/t6050.rs CONFIG_ULTRA, one of the configurations the ID registers
  select at probe (variant 6, two dies), with the UAT policy this board has run with:
  tables in no-map reservations, every table change written back,
  page-by-page TLB invalidation, the handoff's no-slot value without the lock,
  and the handoff byte after the flush records 1. Crash logs in
  fw-data. Its core layout is eight clusters of ten cores, with the
  eight-cluster operand-state layout (OperandState::EIGHT_BY_TEN).
- M5 GPUs are matched on the board's cluster and core-slot counts,
  the values the M5 layouts are sized from, not on enabled cores: the
  J775D has 64 of its 80 cores enabled. The T6050 core layouts drop
  their own copy of the counts.
- The M5 firmware build (V26A428_M5): the 26A428 variant with the M5
  command set and no host power hold around render-pair registration
  (Variant::registration_power_hold). The bare-metal M5 firmware powers
  the cores for work by its own policy: under a hold, J775D's
  core-power witness reads unpowered until the hold is released, so
  taking one only times out.
- Gpu::boot_m5: link the kernel lower root's tables the firmware
  reaches (entries 1, 7 and the canonical arena's 8) before either role
  starts, boot both roles, adopt the published roots, build the M5 graph
  in the shared kernel window and bind its metrics object, attach the host
  command port to the graph's views, then the M5 preparation, InitData to the primary and the secondary, POWER_ON_DONE,
  and wait for the opening records. A failed bootstrap keeps the
  session. The graph builder, the M5 preparation and the metrics
  binding are the M5 tree's.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra Mac Studio (J775D) the driver would write its firmware
tables to whatever GPU firmware the boot loader left loaded, unchecked.
It now drives only the known build, and any other image fails the GPU
probe before a single GPU write, with the rest of boot unaffected.

On bare metal nothing vouches for the firmware the boot loader left in
fw-text: the driver would write the 26A428 firmware's InitData graph to
whatever was loaded. J775D now names the build it admits
(Firmware::Identified), so Gpu::new identifies the loaded TEXT among
its images before anything is written to the GPU, as for the other
identified G17 SoCs.

fw-text holds the primary's TEXT then the secondary's, each with its
own pair of entropy records, and the boot loader refills eight ranges
per boot (two sections and two header load fields per role). The build
zeroes all of them, so the live image and the firmware file hash alike
(c35354407602...ce9cc8251). A mismatch logs the measured identity and
the probe fails before any GPU write; the rest of boot continues. A
digest pinned per boot would refuse the genuine firmware on the next
boot.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
A Rust DRM driver without core_object_args() read and wrote past the end
of objects the DRM core created for it, such as a dma-buf imported from
another device; on the M5 Ultra Mac Studio (J775D) this bug in an
earlier GPU driver tree panicked the kernel and kept the desktop from
starting. Such a driver (tyr, in this tree) now refuses the import with
EINVAL; asahi has core_object_args() and is not affected.

Handle lookups and every Object<T> accessor assume that each GEM object
of the device is a whole Object<T>. The DRM core creates imported and
dumb objects through gem_create_object, which the shmem abstraction
installs only for drivers with core_object_args(). For any other driver
the core allocates a bare drm_gem_shmem_object instead, and the Rust
side then reads and writes past its end.

Commit 2dc22a1 ("rust: drm: gem: shmem: let drivers allocate the
objects the core creates") lets drivers with core_object_args() allocate
them; this completes it for the others. Install
gem_prime_import_sg_table and dumb_create only with gem_create_object. A driver without core_object_args() then refuses an
import from another device with -EINVAL instead of corrupting memory. In this tree that is tyr; asahi has
core_object_args() and keeps both.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra Mac Studio (J775D) the GPU never left its base
performance state (338 MHz) under any load, so every GPU workload ran at
the lowest clock. The firmware's power controllers now get the Ultra's
own power model and ramp the GPU up.

The M5 builder installs the policy controllers' configuration only for
a board whose leakage calibration words match (J714). J775D has
none, so every controller word stayed zero, and the firmware never left
its base performance state (P1, 338 MHz) under any load.

Give J775D its own arm, as J714 has one:
encode_m5_policy_controller_defaults_j775d starts from the J714
defaults and writes the Ultra's power model over them:
- the modeled maximum power, 345262 mW, in the policy's maximum and
  ceiling words, and from there in Region C's host power budget;
- the relative power curves of the main and auxiliary domains;
- the leakage inputs of eight clusters and two dies;
- the controllers' gains and utilization thresholds;
- the temperature-sensor channel masks of both dies.

The model uses synthetic leakage inputs, valid for every J775D, so the
J775D arm needs no calibration words. J714 is unchanged.

The J775D arm follows the shape of Erik Bourget's J716c controller
defaults (f7f650b887cd, not carried here): the J714 defaults first, then
the board's own values over them.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…ltra

On the M5 Ultra Mac Studio (J775D) the idle GPU sat at 338 MHz and light
passes, ramping from there, settled at the second to fourth performance
states. The idle and ramp floor is now the Ultra's own P3.

The policy's performance-controller floor came from the J714 defaults:
P1, in the policy's three floor words and as the base of HwData's
relative-frequency tables. J775D's floor is P3. With P1 the idle GPU
sat at 338 MHz, and a busy pass ramped from there in steps of three
states, so light passes lost the first steps and settled at P2 to P4.

J775D's controller arm now writes its floor (state x 100) into the three
floor words, and its relative-frequency tables are based at P3: 0 at
and below the floor, 100 at P13. relative_frequency() takes the floor;
J714 keeps P1.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra Mac Studio (J775D) renders were refused once a context
slot had been reused 255 times: a render-churn test failed at its 258th
run. Renders now keep working however often a slot is reused; other GPUs
are unchanged.

An execution context slot's reuse generation is 8 bits and wrapped from
255 to 0, which M5 reads as "no generation": the 256th reuse of one
context slot refused its next render's context token (render churn
failed at run 258). On M5 it now wraps from 255 to 1.

Other GPUs keep the wrapping generation: the change is gated on the M5
UAT.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra Mac Studio (J775D) every Vulkan application failed at
vkCreateDevice: Mesa's sparse-tile bind for its descriptor table was
refused with ENOTSUPP on the driver's own page tables. The bind now
succeeds and devices are created.

DRM_ASAHI_FEATURE_SPARSE_TILES is reported for every M5 firmware
profile, but only SPTM-provided tables could map a sparse tile; on a
directly owned table every BIND_SPARSE_TILE returned ENOTSUPP, so Mesa
could not bind its descriptor table reservation and vkCreateDevice
failed.

A directly owned table has no per-frame mapping bound. Map the range
by repeating the tile's first page, which is zeroed: the read-only fill
reads zeros and the writable fill writes to this VM's own sink page,
the same semantics the SPTM tables provide.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra (J775D) the idle GPU went to standby after 1500 us, the
smaller M5 part's delay, instead of the Ultra's own 3500 us.

Region C's idle standby timer, how long the idle GPU waits before it
goes to standby, came from the shared M5 Region C image: 1500 us, the
value of the smaller M5 part. J775D's is 3500 us.

Board::idle_standby_us names the boards whose timer differs, and the
builder writes it into Region C (set_m5_region_idle_standby). The rest
of the idle configuration is unchanged. J714 keeps 1500 us.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On the M5 Ultra (J775D) the firmware's fast boost stopped raising the
GPU clock once the GPU idled more than 10 percent of a boost window,
where the Ultra's tolerance is 20 percent.

The firmware's fast-active boost keeps raising the performance state
while the GPU idles for no more than a tolerated share of each boost
window. The policy's tolerance, a byte in the boost block, kept the
controller default of 10 percent.

J775D's core type takes 20 percent: its controller arm writes it.
J714 keeps 10.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
funkzy and others added 19 commits November 5, 2026 21:49
…U for every client

On M5, a client could bind its own memory over the kernel's mappings in
the compact window at 64 GiB of its VM, where the kernel places the
parameter buffers' page lists, render targets, scratch and the metrics
records the firmware reads. The bind replaced the kernel's page-table
entries, so the GPU then used client memory in place of that
state, and the later unmap of either mapping cleared the other's.
An M5 client VM now reserves the compact window against VM_BIND from
creation, as it already reserves the queue arenas.

The bind check refuses only ranges registered as driver mappings, and
the compact window was never registered in a client VM: its mappings
were placed there at render or compute setup, after the VM existed. The
kernel range a client names at VM_CREATE need not cover the window, so
the client's own range check did not catch it either.

Mesa's M5 layout keeps that window inside its own driver reservation,
so no Mesa bind is refused.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On M5, GPU work that never finished could hold its queue, queue ID,
render pairs, pool backing and memory for as long as other clients kept
the GPU busy, or for as long as its owner kept the queue open. Every
such path is now bounded:

- A job queued behind an older node of its own context was renewed by
  the watchdog at every poll, with no count, while any client kept the
  firmware's device-wide progress word moving. When the older node was
  failed work held until the firmware drains it, the renewal had no
  end. It now stops once the VM has failed work, mirroring the rule
  that a failed blocker of another VM excuses nothing, and it is
  bounded by the existing thirty-second shared budget counted on the
  job's deferrals. The older node's own job runs under the same
  allowances and was published earlier, so a healthy older node drains
  or fails within that window.
- A compute command the watchdog timed out was never killed while its
  owner kept the queue open: a context kill was published only for a
  released queue or a closed context. A timed-out queue's context is
  now killed once the quarantine delay has passed; its VM has already
  failed, so the owner loses nothing it could still use. Spared work,
  which a recovery returns to service, is not killed, and a running ray
  traversal is still never killed, as before.
- The kill of a closed context with a timed-out command was postponed
  while any other client had work in flight and the GPU retired
  something every five seconds, on the assumption that its commands
  retire like any others. A timed-out command now counts as failed
  compute for the kill, as a firmware-reported fault already does, and
  any closed context's kill waits at most the recovery bound after it
  closed.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…ther process takes it

Physical compute queues and render pairs move between processes, but
the client-visible state behind them did not move cleanly with them. A
previous owner could keep reading and writing the state the next
owner's GPU work used, and the next owner could read what the previous
owner's commands left. Each handover now gives the next owner state
the previous owner can no longer write, and clears what it left:

- Compute: when a queue moved to another VM, the previous VM kept
  read-write mappings of the queue's kick ring, preemption buffer,
  usage page and operand state until the new owner's first command
  retired, and the kick ring kept the previous owner's entries. The
  previous owner's mappings are now removed before the new owner's are
  created, and the kick ring is cleared with the other state pages. A
  move needs an idle queue whose pool release the firmware consumed,
  so the firmware no longer uses those views; the previous context's
  scheduler state, which the queue may still run until the new owner's
  first command, stays retained as before. If the new owner releases
  the queue before publishing anything, the queue is left detached, as
  for an exited owner, instead of restoring the removed views.
- Render: a parked pair goes to whichever queue takes its render slot
  next, and storage of a ring that never wrote an entry returns to a
  stock any VM's next ring could take. The rings kept their contents,
  and stock storage handed to another VM stayed mapped read-write in
  the VM that created it. A parked pair's rings are now cleared when it
  goes to another VM's context, and stock storage is reused only by the
  VM that created it.

A parked pair moved to another VM still leaves the previous VM's
mapping of its rings in place, since that mapping lasts as long as the
VM. The read-only client kick rings later in this series keep that VM
from writing the new owner's entries.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
… another process reuses

After a GPU context was released, the firmware's QoS table kept naming
its scheduler page for that QoS ID until the ID's next owner published
work. On M5 scheduler pages are pooled, so the page could meanwhile be
zeroed and given to another process's context, and a firmware read of
that row would see the other process's scheduler state. A released
ID's scheduler and share rows are now cleared before a new context is
created, and at each event turn.

A released ID is recorded when its lease drops and forgotten again if
a new context takes it, since that owner writes its own row at its
first publication. Rows of IDs with work still in flight are left
alone.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
When a user bind and a kernel mapping claimed the same GPU addresses in
one VM, the page-table code logged the collision and overwrote the
existing entries, so the GPU silently used the newer mapping's pages in
place of the older one's, such as client memory in place of state the
firmware reads, and the later unmap of either cleared the survivor's
pages. On the A18 Pro a client could reach this by binding where the
kernel later places that client's queue aliases. A map operation now
fails with EBUSY and leaves the existing mapping untouched, and the A18
Pro arenas are reserved against binds from VM creation.

- The structural fix: no mapping path maps over a live entry on
  purpose (user binds run after the VM manager removed every
  overlapping mapping, kernel mappings take fresh ranges, a residency
  restore clears its own range first), so a live entry is always
  another mapping's. Each map operation checks its whole range before
  its first store, because its failure path clears the whole range it
  asked for; the store loop refuses a live entry too, as a backstop.
- The A18 Pro instance: the kick and descriptor rings, render
  generation state and client-private compute and render backing are
  placed in two arenas when a queue is installed, after the VM exists,
  and were registered as driver mappings only then. A bind made earlier
  was not refused, nor one checked at parse time and committed after an
  alias was installed. With both arenas reserved at creation, the
  reserved set no longer changes. Mesa already keeps this range out of
  its allocator on the A18 Pro, so no Mesa bind is refused.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On M5, a compute dispatch with shader-context scratch passes the
hardware an auxiliary allocation word, the per-core page count of its
scratch storage, unchanged from userspace. The pool behind it was sized
for a fixed eight pages per core whatever the word said, so a dispatch
asking for more (Mesa's ray-query dispatches ask for 14) let the
hardware use storage past what its pool holds. Any value was accepted,
also without scratch. The pool now covers the requested count, and a
nonzero word without scratch is refused with EINVAL.

Render already sizes its pool from the same word: its spill budget adds
the published auxiliary page count, which includes the eight-page tail,
for every core. Compute now does the same, with the eight-page tail as
the floor, so no dispatch gets a smaller pool than before. Mesa's
ray-query compute pools grow by six pages per core.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…PU pool sizes

On M5, the render and compute pools behind a client's queues are sized
from that client's own program records, up to the board's whole pool
inventory (16 GiB on the M5 Ultra), and allocated and zeroed at once
as kernel memory. Kernel memory counts toward the device total but is
never refused and is attributed to no process, so one unprivileged
client could pin many gigabytes per queue: other clients' allocations
then failed with ENOMEM, and the OOM killer could not see the culprit.
An M5 pool backing is now admitted against its client's and the
device's memory limits before it is allocated, as a user buffer of the
same size would be, and a refusal fails the submission with ENOMEM.

A client VM now carries the memory accounting of the file that created
it, so every place that builds an M5 pool for a context's VM charges
that client: compute pools at install, re-arm and growth, and render
pools when their backing is built. The charge is released with the
backing. Kernel VMs and the other GPUs' pools are unchanged.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
On GPUs with independent queues, QUEUE_CREATE had no per-client limit,
so one unprivileged client could open queues until the device's
execution contexts were gone. Every other process, the compositor
included, then failed to create a queue with EBUSY: a GPU denial of
service for the whole seat. A client now holds at most half of the
device's independent queues, and its next QUEUE_CREATE fails with
EBUSY.

The limit comes from the same queue limit GET_PARAMS reports, so it
follows each GPU's budget (29 of 58 where all execution contexts are
available), and leaves the other half to the rest of the seat. The
file's queue table hands out the lowest free ID, so bounding the IDs
bounds the queues the file holds. GPUs without independent queues are
unchanged.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…er GPU VM

Each sparse tile was a 64-page physically contiguous allocation, 1 MiB
with 16 KiB pages, of which the page tables read only the first UAT
page. Every VM that did a sparse-tile bind allocated two, so any client
could pin 2 MiB of high-order memory per VM it created, exhausting or
fragmenting contiguous memory for the rest of the system. A tile is
now one UAT page.

The allocation's order is derived from the UAT page size, so the tile
stays naturally aligned to a UAT page whatever the kernel page size.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…r overwrite kernel memory

On GPUs whose page tables the firmware builds in reserved memory (the
T6040, T6041 and T6050), a page-table walk followed any table
descriptor the firmware or bootloader had written, and accepted a
child table anywhere in System RAM. A corrupted or unexpected
descriptor below the shared kernel root would then have the walk store
PTEs into an arbitrary kernel page. A table page must now be in the
reserved regions or be one the driver allocated; anything else fails
the walk with EIO.

Freeing already refuses tables the driver does not own, so only the
write path was exposed. The firmware's own tables on these boards are
reserved no-map regions, and every table the driver adds is recorded
when it is allocated, so no legitimate walk is refused.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…pletion and scheduler pages

On M5, the render completion stamps, each context's scheduler and
work-state pages, and each compute queue's support page were mapped for
both the firmware and the GPU. The kernel trusts what they hold. They
sit in the kernel's upper half, which every execution context includes.
Whether user shaders could reach that half was never established. This
removes the GPU permission either way: on M5 these pages are now
firmware-only. On the Studio, every GPU path ran clean with them
firmware-only, so the GPU itself doesn't need them.

Scope, by page set:
- Render completion stamps (initdata/m5.rs): allocated only on M5.
- Scheduler pages (context.rs): M5 takes them from the retained pool, Neo
  allocates its own. Firmware-only on M5 only; Neo is unchanged.
- Work-state pages (context.rs): shared code, allocated on Neo too.
  Firmware-only on M5 only; Neo is unchanged.
- Compute support page (queue/compute.rs): Neo allocates a larger one
  for its own use. Firmware-only on M5 only; Neo is unchanged.

set_fw_cached_prefix now derives the cached prefix from the mapping's own
protection, changing only the cache attribute, instead of rewriting it to
the firmware-and-GPU protection. Its readback already refused any change
other than the cache attribute. So a firmware-only pooled object used to
fail queue creation with EFAULT, and every source that succeeded is one
for which the derived protection is the same as before. That covers every
pooled family on Neo and M5 today, all firmware-and-GPU shared, so the
change is identity there.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…e kernel validated them

Every client VM had a read-write view of its queues' kick rings, the
entries the kernel builds from validated state for each command: queue
and descriptor addresses, dependencies, QoS slot, event masks and the
register programs' addresses. A client's GPU work could therefore
rewrite an entry after the kernel had built and checked it, before the
firmware consumed it. A render pair's rings also stay mapped in every
VM that ran on them. Client views of kick rings are now read-only.

The host writes kick entries through the kernel's own mapping and the
firmware reads them through its own; neither uses the client's view,
and nothing in the driver reads an entry back. The client's view is
there for the GPU's reads, as the descriptor rings' client views,
already read-only, are. The kernel's lower and firmware mappings keep
their permissions.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…emory

On T6050 with display firmware 27, when the display co-processor's
firmware named a buffer in a request that was none of the host's
pending command buffers, the host resolved the address through the
whole IOMMU domain and copied up to 1 MiB to or from whatever it
translated to, ignoring the mapping's permissions. A reply could thus
be written into memory the co-processor may only read, such as another
process's framebuffer or an imported buffer, and a request could read
any memory the co-processor can reach. An address that translated to a
device's registers was mapped cacheable and copied through, which can
take the system down. Only the firmware's own retained mappings are
now followed, a reply needs a mapping that grants writes, and an
address in Linux's RAM or in any claimed memory resource is refused.

The DART driver gains a resolver for this that refuses any address
Linux mapped itself and returns the protection the firmware's leaves
grant, for the four-level subtrees and root slots it retains. DRM_APPLE
now follows the DART driver's build choice, since it calls into it.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
… under memory pressure

Two kinds of G17 work signal job fences: the event work, which
completes jobs, and each scheduler's timeout work, which fails timed-out
ones. Memory reclaim may wait on those fences, but neither ran on a
reclaim-safe workqueue. The event work ran on an M5 driver queue
allocated without WQ_MEM_RECLAIM, or on system_highpri_wq; the timeout
work ran on system_percpu_wq, drm_sched's default. When a pool has no
idle worker and creating one needs memory, that work cannot run, and
neither the fences nor reclaim make progress.

Send all of it to one reclaim-safe queue, aurora_g17_events. On M5 it
is unbound and high priority as before, so the event work still follows
the submitting CPU; elsewhere it is per-CPU and high priority, the
counterpart of system_highpri_wq, so the event work runs where it did.
Every scheduler takes the queue as its timeout queue and keeps a
reference until it has been finalized. Their run and free work already
used reclaim-safe queues.

For that, the Rust OwnedQueue gains reclaim-safe unbound and per-CPU
constructors (restrict() refuses a per-CPU queue), and the Rust DRM
scheduler binding's new_with_queues() takes an optional submission
queue and an optional shared timeout queue, in place of
new_with_submit_wq().

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
…he report and fault-ownership changes

With KUnit enabled, asahi no longer built: two of its tests still
called interfaces that later commits had changed.

- f04338837f3f ("drm/asahi: g17: M5: install M5 queues and submit
  through the host command port") gave Reports::record() the family's
  statistics tag mask and 64 tags per role. The report tests now pass
  the G17 mask and index the second role at TAGS, and a new case checks
  that tag 32 counts under M5's mask only and that tags past 63 are
  refused under any mask.
- e7ddf2b8e869 ("drm/asahi: g17: Fix a process keeping access to a GPU
  queue after another process takes it") added Previous::moved. The
  same-VM replacement test sets it false.

Signed-off-by: Jeff Queisser <funkzy@users.noreply.github.com>
- HW+0x10a8: the active MCC mask for all eight AMCCs (0xff), matching the
  slot 3 windows, instead of the inherited 0xf.
- HW+0x100c (16) and HW+0x1048 (40, all cores).

J714 keeps the inherited values, and J775D its own.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The M5 Max's controller defaults were J714's, applied only to a part
with J714's calibration words, which no M5 Max has. Give it its own: the
J714 defaults, including the relative power curves, with the leakage of
its four clusters, its base and AFR values, a maximum power of 196098 mW,
and the found masks of its AON PTD records, applied for the J716c
calibration words.

As on J714, a unit whose calibration words differ keeps the default
budget.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
The M5 Max (J716c) already has its board data, but its configuration
still names no firmware and probe refuses it as having no bare-metal GPU
tables.

Give it the M5 Ultra's bare-metal profile: both roles publish their
kernel roots, the secondary's at 0x40000 in `pagetables`, each asks for
its crash log inside `fw-data`, and the UAT policy is the Ultra's. Admit
the G17C firmware build of both roles: primary TEXT 0x50000, then the
secondary's 0x54000, with the boot-filled ranges zeroed as on the Ultra.

Only the M5 Pro is refused at probe now. The firmware identity test
covers the Max's image too.

Tested on a J716c: the GPU boots both roles and runs the desktop through
zink and Vulkan.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>
Bind the T6050 GPU through "apple,agx-g17s", the compatible its M5 Max
("apple,agx-g17c") and M5 Ultra ("apple,agx-g17d") nodes fall back to,
in place of "apple,agx-t6050", which no binding documents. The GPU's ID
registers still choose the board's configuration.

Developed with Claude Code.

Signed-off-by: Erik Bourget <erik@ebourget.net>

@DjDeveloperr DjDeveloperr 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.

Approved: the M5 Pro runs with these commits, and they're already on this PR's base.

All four commits are on funkzy/t6050-gpu-on-integration (0dd0833) as 21a5bde, 6d8d401, 15fa55b and e1bf8cf. Two are patch-identical. The other two differ only in a doc comment and in #1012's later ProbeConfig::M5 → G17M5 rename. That's why GitHub shows a conflict: nothing is left here to merge.

On the M5 Pro (J714sAP, G17S), that base plus our J714 enablement passes:

  • compute: 65,536 words, 0 wrong;
  • the 256x256 triangle, crc32 d5e4bea9, identical to the M2 Pro reference render;
  • dEQP s0 smoke: 109 Pass, 2 NotSupported, 0 Fail.

The MCC-mask/topology and controller-default commits only set J716C's board fields, so J714 is unaffected. The G17S match is what J714 binds through.

@DjDeveloperr

Copy link
Copy Markdown
Contributor

Leaving this open: as in my review, all four commits are already on #1012's branch (funkzy/t6050-gpu-on-integration), and integration/aurora has no drivers/gpu/drm/asahi/g17 yet. #1012 isn't being merged in this round. Once it lands, this PR has nothing left to add and can be closed.

🤖 Generated with Claude Code

@erikwb

erikwb commented Nov 10, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #1012, which now carries this PR's J716c commits (G17S compatible match, bare-metal run, controller defaults, MCC mask and topology words). #1177 is retargeted onto #1012.

@erikwb erikwb closed this Nov 10, 2026
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