Skip to content

drm/asahi, drm/apple: J813 (M5 MacBook Air) GPU and display on the macOS 27.0 firmware - #1413

Merged
DjDeveloperr merged 31 commits into
aurora-silicon:integration/aurorafrom
sithglan:thomas/j813-gpu-27-on-integration
Nov 11, 2026
Merged

DjDeveloperr merged 31 commits into
aurora-silicon:integration/aurorafrom
sithglan:thomas/j813-gpu-27-on-integration

Conversation

@sithglan

@sithglan sithglan commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Base: integration/aurora (5186d2e), which has #1012 since c54454d. Branch
thomas/j813-gpu-27-on-integration (7595d75): the 30 J813 commits only.

This replaces #1135. Its two T8142 changes, the DCP on firmware 27 and the DCP's
firmware-27 clocks, are the first two commits here, reworked.

This brings up the J813 (M5 MacBook Air, T8142, G17G) under the macOS 27.0 stub, with the
26A428 GPU firmware and the 27.0 DCP firmware. Compute, render, Vulkan and GL run, and so do
sway, mpv and glxgears.

Rebased onto integration with #1012

The previous head, ab5d6bc, carried its own rebase of #1012. Integration has merged #1012
as DJ's later stack, which also has the M5 Ultra and Max commits and the M5 Pro completion
forms. Of the 31 J813 commits on that head:

  • 24 are unchanged (git range-diff: =).
  • 3 differ only in context: integration's other M5 boards now have completion_inputs.
  • "drm/apple: T8142 on firmware 27: give the console ARGB8888" is dropped. Integration's
    cd371f9 gives every firmware-27 DCP the ARGB console.
  • 4 were resolved against drm/asahi, drm/apple: M5 GPU and firmware-27 display on integration/aurora #1012 as merged:
    • "the J813 (T8142) on the 26A428 graph": the J813 board sets completion_inputs: false.
    • "the firmware's own offsets in the descriptor tail": common26_tail sits beside
      integration's submit_sequence.
    • "the J813's compute kicks as macOS 27.0 writes them": the J813 forms are tested before the
      M5 Pro ones.
      • explicit_dependency() is class 1 on the J813, as on completion_inputs boards.
      • counted_forms() includes the J813: it counts the ordinal and holds the kick for its
        parent.
      • kick_event_mask() and kick_tuple() keep their J813 forms.
      • J714, J716C and J775D get what integration gives them.
    • "on the J813, a tiling kick waits for the last fragment kick": rebased onto integration's
      render_barriers(.., compute_class).

Three things on the J813 now come from integration rather than from the tested ab5d6bc:

  • A render kick's compute dependencies take explicit_dependency(), which is class 1 on the
    J813: what macOS 27.0 writes for every J813 dependency. They were class 2.
  • host_producer_waits() holds on the J813 (no G17D forms, no completion inputs), so a
    compute kick waits on the host for producers on other engines.
  • An M5 timestamp buffer gets no firmware window (map_timestamp_buffer()). The old head
    had its own guard for M5 renders, which is gone.

The testing below is on this branch.

What the series does

Display (4 commits):

  • Drive the T8142's 27.0 DCP firmware as firmware 27 with the default method profile. Driving
    it through H17P panics the DCP firmware after the first hotplug, and with H17G every swap
    fails. Fix the merged firmware-27 table's A428 method.
  • The DCP's firmware-27 provider clocks (/arm-io/disp0 clock-ids 585 and 431), named
    firmware-0 and firmware-1. The DCP takes them by name, and clkref stays its first clock,
    so a J813 on 26.x firmware keeps the clock its H17P path had. apple,dcp.yaml gets a T8142
    branch for this layout. The J813 ADT has no rate for either clock. Firmware 27 reads only
    585, as its minimum pixel clock (getClockFrequency(0); with 0 it logs "Giving up on
    frequencies"). 585 gets 712 MHz, clock-frequencies entry 0x49, which its low byte names.
    431 was never asked for and stays 0.
  • Offer only alpha formats on the plane. The firmware blends XRGB's X byte as alpha, so
    GPU-rendered clients showed wrong colours. The console's ARGB8888 is integration's
    cd371f9.
  • Log why a present is dropped at the backlight step.

GPU bring-up (18 commits):

  • The J813 board on the common 26A428 graph:
    • InitData layout and order (GFX1 first);
    • its register map, perf tables and always-on block;
    • ring and completion placement, read from macOS 27.0's own objects under aura;
    • the compute descriptor tail;
    • the coprocessors' log buffers;
    • the GFX power hold.
  • Diagnostics for refused submissions and timed-out renders (rate-limited pr_warn), and
    debugfs firmware_log / hardware_data.
  • The render commands with the common 26A428 six-byte insert.

GPU execution (8 commits):

  • Compute kicks, and the TA and fragment register programs, in the forms macOS 27.0 writes
    on the J813.
  • Submission in the G17P/Neo forms: doorbell only, item rings of [configuration], descriptor,
    KickAnnounce, and a configuration with every kick.
  • The pools in the G17P form. This was the last blocker before work executed.
  • Render metrics also mapped in context 0, where the J813's firmware reads them. Without it,
    parallel clients took MMU faults.
  • Host parameter-buffer growth clamped to the buffer's capacity. Without it,
    fbo.no_attachments.maximums failed with ERANGE and lost the device.
  • A tiling kick also waits for the last fragment kick, the order macOS 27.0 keeps on the
    J813: in its captured kick queues, each TA kick that names a 3D kick names the previous
    render's. Without it, one context's TA ran beside another's 3D, and that 3D's ISP parameter
    fetch read its tile data through the TA's VM slot (or slot 0) and faulted. That caused a
    few GPU recoveries per run with parallel clients; there are none now.

Other boards

Everything J813-specific is gated on the T8142 (chip_id == 0x8142, Board::j813_forms())
or on the common 26A428 graph (Graph::is_common26()). The J714, J716C and J775D keep
integration's:

  • boot order and preparation;
  • register-window spacing;
  • placements;
  • compute descriptor tail;
  • kick forms;
  • InitData.

These changes do reach every M5 board:

  • Work-state words start at generation 1 (0x40) instead of 0. macOS never uses generation 0;
    the node indices and their order are unchanged.
  • Host parameter-buffer growth is clamped to the buffer's capacity (the ERANGE fix above).
  • Rate-limited pr_warn diagnostics on refused submissions and timed-out renders, and two
    read-only debugfs files.
  • The M5 TA command buffer is 0x9a0 bytes, zero padded, instead of 0x990.

I could not test on an M5 Pro, Max or Ultra. A boot and a quick GL run there would be
appreciated.

Testing (J813, macOS 27.0 stub, this branch)

Kernel 7.3.0-aurora-j813-oni4s+: this branch merged with #1423 (J813 sound, which touches no
GPU or display file), both on integration/aurora 5186d2e. Booted through aura-try.

Booted by aura from thomas/j813-27-smp2: aura main of early November, plus the open aura
#82 (aura-try), #99 and #100, plus the speaker hand-off. I have not booted it from current aura
main. With aurora-silicon/aura#143 (merged), aura fills the J813 DT's fw-data from the ADT
instead of leaving its fixed address. On an earlier head, chainloaded with a DTB whose
fw-data was zeroed, aura filled it (0x10002444000 + 0x184000), and Linux and the GPU ran as
before.

The display comes up on the panel at 2560x1600. The DCP firmware logs "getClockFrequency
returned pixelClock = 712000000" and no "Giving up on frequencies". The kernel's warnings
match the previous head's boot. The one new line is integration's #1455 noting that
arm64.nowfxt already hid WFxT.

4 jobs, no GPU recoveries:

suite mesa-j813 + 53b674ec19c aurora-wip native + mesa#207 aurora-wip zink + mesa#208
GLES2 16955 / 0 / 206 skip 16956 / 0 / 206 16957 / 0 / 205
GLES3 44380 / 0 / 385 44409 / 0 / 385 not run
GLES31 37339 / 1 / 449 37352 / 0 / 449 not run

ab5d6bc gave the same: 16955 / 0, 44380 / 0, and 37337 / 0 with 3 flakes on mesa-j813;
16956 / 0, 44409 / 0 and 37352 / 0 on aurora-wip native; 16957 / 0 on zink. The GLES31
failure, image_load_store.cube.qualifiers.coherent_r32f, is from the parallel-only
qualifiers group in the notes below. Run alone it passes 5 / 5.

mesa-j813 is thomas/j813-m5-next with the Gallium storage declaration the 27.0 kernel requires,
53b674ec19c (a G17G texture-staging fix) and 40901c087f7 (GL_EXT_shader_texture_lod, from
aurora-wip). GLES2 matches the 26.x kernel's run on this machine (16955 / 0 / 206 / 4 warn).
Notes:

  • GLES31: every failure and flake is image_load_store.*.qualifiers.coherent/volatile_*.
    They pass serially and fail or flake only under parallel load, as they did on the 26.x
    kernel. The rate is the same with and without the Mesa staging fix (23 flakes in 8 runs of
    the group on each build).
  • GLES3 texture.units.8_units.mixed.4 failed in most runs, with a different image each time,
    until mesa-j813 53b674ec19c. That was a packer bug: on G17G, a 3D or cube sample's staging
    overwrote the coordinates of a 2D sample still in flight. It was never a kernel issue; the
    case passes 10/10 serially with the fix, on both mesa-j813 and aurora-wip native.
    8_units.mixed.8 still fails now and then on mesa-j813: the texture.units group, run 4-way
    in parallel 20 times, failed it twice on mesa-j813 and never on aurora-wip native, on the
    same kernel. Serially it passes 20/20 on both. That points at mesa-j813, not the kernel.
  • zink: rasterization.fill_rules.projected and texture.mipmap.cube.projected.* were a
    Honeykrisp bug: G17G needs shade model 5 for perspective varyings. Fixed by
    hk: G17G perspective bindings take shade model 5 mesa#208 ("hk: G17G perspective bindings take shade model 5"); no kernel
    involvement.
  • Native GL on aurora-wip: the M5 path refuses a render that declares no program storage,
    and aurora-wip's Gallium declared none. Every native render failed with EINVAL (GLES2
    3123 / 13836). asahi: G17G on #194's M5 submission path, within the kernel's command limit mesa#207 ("asahi: declare an M5 render's program storage to
    the kernel") adds the declaration; with it the three GLES suites run clean.

CI checks, run locally on this branch:

  • host-tests: tools/asahi/run-host-tests.py, 224 / 224 pass (with clang on PATH).
  • dt-checks: scripts/dtc/apple-dt-check.sh against integration/aurora 5186d2e:
    0 new warnings across the 137 Apple DTBs (412 = 412). apple,dcp.yaml passes
    dt_binding_check.
  • kunit: kunit.py run --arch=arm64 --kunitconfig=drivers/soc/apple/sep.kunitconfig 'apple_sep*' in QEMU, 194 / 194 pass. It also builds drm/asahi without cpufreq.
  • source: the aurora-sep tests pass.
  • Per-commit build of all 30 (Image, modules and dtbs, J813 config, clang 22): 30 / 30 build.

Also on this kernel, with mesa-j813:

  • Under headless sway (GLES2 renderer), all of these render as on the previous head:
    • Firefox: reddit, and a WebGL page with 4x MSAA and EXT_shader_texture_lod;
    • Chromium: reddit;
    • mpv (--vo=gpu), alacritty and zathura;
    • vkcube (Honeykrisp, "Apple M5 (G17G C0)").
  • kmscube on the panel, through zink on the display card: 600 frames in 9.98 s, 60 fps. When
    it exits, the DCP logs "present dropped: backlight prepare -61" and "swap failed! status
    4294967295" once, as on the previous head.

Known issues

  • pmpdash.c holds ps_gfx through the T8142 PMGR register at a fixed address. It should move
    to the power-domain driver.
  • The GPU fw-data reservation in the J813 DT is at the address iBoot has used on every 27.0
    boot so far. kboot_gpu: T8142: reserve the GPU firmware's data as fw-data aura#143 fills it from the ADT (tested chainloaded, above).
  • The J813 ADT has no rate for DCP clocks 585 and 431. 585 has 712 MHz from clock-frequencies
    entry 0x49 (see the display part); 431 is 0 because firmware 27 has never asked for it.

Reviewers: @Acelogic @DjDeveloperr @RyanTheTide @LiamRay10

@sithglan

Copy link
Copy Markdown
Contributor Author

Updated (839536f -> 73b353d). The tree changes only in the following:

  • CI:
    • kunit: g17/thermal.rs calls cpufreq through CONFIG_CPU_FREQ wrappers, so the SEP KUnit
      config builds.
    • host-tests: stand-ins for the queue share in queue-priority-tests.py, and a pr_warn!
      in the asahi-m4 command-stream harness; 224 / 224 pass locally.
    • dt-checks: below. source fails on integration/aurora itself as well (the aurora-sep
      diag tests, since 227ed46); this PR does not touch them.
  • Ryan's review of drm/apple, dts: T8142: drive the macOS 27.0 DCP firmware as firmware 27 #1135 (points 2-4), which this PR replaces:
    • The DCP's firmware-27 clocks are named firmware-0 / firmware-1, and dcp.c takes them
      by name. clkref stays the first clock, so a J813 on 26.x keeps the clock its H17P path
      had.
    • apple,dcp.yaml gets a T8142 branch for that layout. apple-dt-check.sh, run with this
      branch merged into integration/aurora 064054d, finds no new warnings in the 137 Apple
      DTBs.
    • The J716c's rates are gone. I measured what firmware 27 reads: only 585, as its minimum
      pixel clock (getClockFrequency(0); with 0 it gives up on frequencies). 585 now has
      712 MHz, the J813 ADT's clock-frequencies entry 0x49. 431 was never asked for and stays 0.

The same CTS runs on the updated kernel: no GPU recoveries, results in the description.

@sithglan

Copy link
Copy Markdown
Contributor Author

Rebased onto integration/aurora 095e47d (73b353d -> ab5d6bc), since #1413 no
longer merged. The 31 J813 commits are unchanged. Six of #1012's commits needed changes,
each recorded in its bracketed note:

  • drm/asahi: g17: let the firmware write render fragment end timestamps #1229 (firmware-written timestamps, dynamic objects below the MMIO aperture):
    • kernel_dynamic_range() returns integration's dynamic::upper() for the GPUs that are
      not M5, so the window ends at the aperture there.
    • map_timestamp_buffer() keeps the timestamp window and falls back to
      kernel_dynamic_range().
    • M5 renders take no window addresses. The M5 forms do not carry them (ta_pair_a and
      fragment_pair_a stay 0), and asking for them marks the stamps handed out: the first
      eight timestamped renders would each have waited 200 us, and then the window would be
      switched off. After the CTS, aurora-g17/capacity reads timestamp_window firmware=0 host_fallbacks=0.
  • The T6041 25G83 profiles:
    • the J616C profile's perf and region_c as Some(...);
    • the 40-core J614C image in the new FirmwareBuild form;
    • active_cores, which integration now computes earlier.

On the J813: the CTS, as in the description, with no failures and no GPU recoveries;
Firefox, Chromium, mpv, alacritty, zathura and vkcube under sway; kmscube on the panel at
60 fps. All 167 commits build one by one. Locally: kunit 194 / 194 in QEMU, host-tests
224 / 224, no new DT warnings, and the aurora-sep source tests pass (#1438).
Integration has moved again since (7a7cfa1); this branch merges into it cleanly.

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

integration/aurora now contains the M5 bring-up from #1012 (c54454d): 120 of this branch's 167 commits are patch-identical to commits there, and the branch conflicts with it. Please rebase onto integration/aurora c54454d (or later) so that only the J813-specific commits remain; CI and review then run on that smaller diff.

…on T8142

iomfb_v27.c named A428 dcpep_pipe_set_428, which this tree calls
iomfbep_set_brightness_correction (94621d9). T8142 with
firmware-compat 27.0.0 takes DCP_FIRMWARE_V_27_0: the H17P interface
makes the 27.0 DCP firmware panic after the first hotplug.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
On the 27.0 stub the J813's DCP firmware (AppleDCP-1317.1.1~116) panics
right after the first hotplug when driven through the H17P interface
("ap_link: callback failed with: 0x5" in its crash log), so apple-drm
never binds and the unwind WARNs in devm_clk_match.

Drive it as the T6050's firmware 27 instead, for a T8142 whose
firmware-compat is 27.0.0:
- DCP_FIRMWARE_V_27_0 with the default method profile: with H17G every
  swap fails with status 4;
- apple,clock-ids = <585 431> (/arm-io/disp0 clock-ids) with two fixed
  clocks, named firmware-0 and firmware-1: firmware 27 asks for its
  provider clocks by index. The DCP takes those by name, so clkref stays
  the first clock and the 26.x paths keep the clock they had. The binding
  gets a T8142 branch for this layout.

The J813 ADT's clock-frequencies (ids 0x100..0x1ad) has no rate for 585
or 431. Firmware 27 reads only 585, as its minimum pixel clock
(getClockFrequency(0)). With 0 it logs "getClockFrequency(0) (0) <
videoClock 138521008! Giving up on frequencies". 585 gets 712 MHz,
clock-frequencies entry 0x49, which its low byte names; with it macOS
27.0's DCP firmware offered a display under aura. The firmware never asked
for 431 in these boots, which keeps 0.

Result: DCP booted, 2560x1600@60, fb0, sway, no swap failures, no
WARNING. The H17G profile would also have selected the J813's alpha
semantics and powerlog-service matching ("failed to extract init props").

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
5e98b68 offers the H17G primary plane only alpha formats, since its
firmware blends XRGB's X byte as alpha. Since the T8142 runs macOS 27.0's
DCP firmware as firmware 27 it is outside the H17G profile, so XRGB8888
came back, and the same blending: GPU-rendered alacritty under sway showed
wrong colours. Treat the T8142 on firmware 27 the same way.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
iomfb_apply_backlight_h17p() returning false makes dcp_swap_submit() drop
the present, which then reports only "swap failed! status 4294967295". On
the J813 (firmware 27) that happened while typing in alacritty under sway.
Name the cause: no transaction, no brightness maximum, or the backlight
prepare's error.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Without aura's 25G76 firmware profile (the macOS 27.0 stub), the T8142
probe takes the G17 path with a bring-up configuration that refuses at
the identity check, after get_g17_id has logged the ID registers. On
J813: family 0xb, variant 2, one die, one cluster of ten core slots,
core mask 0x3bd.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A J813 board for the M5 graph and a T8142 configuration on the G17
path, for the macOS 27.0 (26A428) GPU firmware:

- Board: chip ID 0x8142, one cluster of ten core slots, core type 0x20
  revision 5 and twelve states (its 25G76 HW data), the ADT's
  perf-states (padded to fourteen with the top state), no CS/AFR
  table, the RT0g AON row, J714's slots at the J813's addresses
  (slot 17 widened to 0x28000: the firmware reads G + 0x25148).
- Its firmware checks root +0 against 0x0c8b_4339_83a0_04c0 (TEXT
  0x21ed8), the common 26A428 interface, and has no M5 HW data
  insert: Board::interface_version and Board::hw_data_insert, with
  the later HW data offsets shifted back.
- Crash logs in the firmware's own DATA_SHARED_RO (FirmwareData) and a
  trial fw-data reservation in the J813 DT.
- OperandState::ONE_BY_TEN.

Both roles now acknowledge their InitData.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813's macOS publishes and acknowledges GFX1's InitData before
GFX's (its 25G76 trace); the M5 Pro's graph starts with the primary.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…ing-up)

The J813's 26A428 GPU firmware checks the common 26A428 interface word,
the one J873 runs, and the M5 graph gave it the M5 Pro's layout with only
that word replaced. The two builds differ well beyond the root: the
perf/policy block is 0x7800 bytes, not 0x126b0, with the policy state and
AON PTD pointers at +0x3d30 and +0x64d8 (M5 Pro +0x8254, +0x11a1c); the
status config is 0x6d0 bytes with its cross-link at +0x6c4; region C keeps
the 25G76 fields moved up by four. Everything after the policy block in the
shared cluster moves with it.

Add a per-board Graph (root profile, both cluster layouts, bundle views,
work rings, state grids, the region-C power-assert tally) and give the J813
the common one: the J873 profile for the root, main config and channels,
and common 26A428 encoders for the private cluster, the policy blocks and
region C. Those carry the J813's own 25G76 words through the J873 backend's
relocation bands, plus the fields 26A428 added as macOS sets them on J873;
the J813-scaled values come out where J873 has its own (1200 for 1300,
0x5eb0 for 0x8f16). The runtime's layout users follow the board's graph;
the M5 Pro-only telemetry is skipped on the common build.

Also for the J813: its one memory cache controller (HW data +0x10a8 was
J714's 0xf), its own 25G76 word at common +0x1044 (6, J714 12), slot 17 at
the common map's 0x36000, DPEP (29), ADCLK (30) and slot 32, the J813's
25G76 slot 27.

On the J813 (27.0): both roles ACK InitData; GFX1 no longer crashes (it hit
NULL+0x44 before). GFX still takes an SError right after its ACK: its crash
log's bus-error section names 0x5_8002_1140/0x5_8002_1148 (KSM, the HW
data's completion-queue table in x13), so the KSM block does not answer.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…count (bring-up)

macOS 27.0's initFirmwareData also writes the KSM kick-queue setup into
InitData: a u64 timestamp seed at bundle +0x2728 and the ring's entry
count at +0x2730, read from the accelerator's kick-queue config block
(createKSMKickQueues loads them at +0x10bc0 and +0x10bb8). The firmware
copies the seed into its context +0xb80 and seeds all 128 queue states
from it; the Linux J813 encoder left both fields zero.

Fill them on the common 26A428 graph (the J813) from this driver's own
kick protocol: the first kick of every
queue is KickTimestamp::FIRST and every ring has KICK_SLOTS entries, so
the firmware's expected counts line up with what the host will send.
These are the logical 0x2730/0x2738 offsets, which hw_offset maps to the
physical 0x2728/0x2730 on a no-insert board like the J813.

Tried on the J813 (trial module try22): both roles ACK InitData and GFX
still takes the same external abort at 0x5_8002_1148, so these fields are
not what keeps the KSM block down under 27.0; keep the encoding as the
protocol-correct one.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
aura's trace of macOS 27.0 on the J813 (FINDINGS.md) shows two things this
driver did differently, and on the J813 both are now as macOS does them:

- The Fender gating is cleared once both coprocessors run, not before them.
  macOS writes the SGX start control at platform init, powers ps_gfx, starts
  GFX1's then GFX's ASC, exchanges RTKit HELLO, and only then reads 0x6 from
  the gating control and writes 0. prepare_m5_firmware() is split so the start
  control can be set on its own.
- After the firmware acknowledges its initialization root, macOS answers on the
  doorbell endpoint with the acknowledgement type and the host bit
  (0x89000000000000 on endpoint 0x21), to both roles. This driver never sent it.

Neither changes the J813's crash: both roles still acknowledge and the primary
still takes the same SError on the KSM block (FAR 0x580021148) immediately after
its own acknowledgement, before it can process the doorbell answer. They are
kept because they match the machine's own firmware. The other M5 boards keep
the order they had: prepare_m5_firmware() after the coprocessor boot, and no
answer.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…ndle

aura can now park the macOS 27.0 guest right after its GPU bring-up and read the
memory its firmware runs on, so the J813's hardware-data bundle no longer has to
be inferred from another board's trace. Its register map, which the firmware
reads to reach every block it drives, differed in four ways:

- Thirteen slots this encoder filled are empty in macOS's map (4, 11, 18, 23,
  29, 30, 38, 39, 42, 48, 52, 53 and 58), three it left empty are filled (8 at
  0x3_a135_0000, 47 at sgx+0xe3_5000 and 56 at sgx+0x1_4400, which nothing
  mapped before), and slot 17 spans 0x21400, not 0x36000: it ends just above the
  KSM block the firmware programs.
- The device VAs are packed in slot order, and the guard page between windows
  follows a window's own bytes before either is rounded up to a page. Rounding
  first pushed every window after slot 32 -- whose 0x1000 bytes sit at a 0x1000
  offset -- half a page too high. The other boards keep the spacing they had.
- Every slot carries a flag whether or not a window uses it. The flag vector is
  the board's, so only a board whose own firmware's map has been read has one.
- Ten words of the hardware data that the inherited 25G76 template and the
  generic encoding leave different, including the pair at 0x2720 (whose values
  were the other way round) and the chip-id words at 0xfa8 and 0xfb4.

With these the map this driver encodes and the windows it maps are byte for byte
macOS's own, at macOS's own device VAs.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813's GPU firmware died on an external abort every time it programmed the
KSM completion-queue registers, with the completion table in its hardware data
under the pointer that faulted. Read against macOS 27.0's own bundle, that table
differed in one byte per ring: the GPU-visible alias of each completion ring is
at 0x72_0184_8000 + n * 0x2_8000, not 0x70_... -- 8 GiB out, in the same
top-level entry, inherited from the 25G76 trace this encoder was built from. The
region views the same band holds move with them.

Their firmware-side addresses were 0x18000 out as well: macOS keeps the four
backings at the kernel window's 0xc07e_8000.

Only the common 26A428 graph (the J813) moves; the M5 Pro's build keeps the
25G76 addresses.

With the rings where the firmware expects them it no longer aborts: both
coprocessors acknowledge their initialization data, the opening records retire
and the driver registers a DRM device on the M5 Air for the first time.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
macOS 27.0 answers each coprocessor's request for the crash log buffer in its
own data segment exactly as RTKit does here, by echoing the request -- and then
sends a second message this driver never sent: on the crashlog endpoint, under a
type of its own, a 32 KiB buffer of the host's at a fixed address in the
firmware's kernel window. Without it the firmware asks for its buffer a second
time, which is where this driver's mailbox history differed from macOS's.

Both buffers are allocated and mapped with the rest of the graph, and published
once both coprocessors run and before any endpoint of theirs is started, which
is where macOS sends them -- GFX1's first, as with the initialization data. The
firmware's history then matches macOS's message for message.

The coprocessors are also started in macOS's order: GFX1's first, GFX's about
8 ms later. All of this is for the J813 only; the other boards keep their
order and their preparation step.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The first submission on the J813 killed the firmware every time: a data abort
(ESR 0x96000004, a level-0 translation fault) on 0x20007, from

    100c4: add x9, x8, #0xfb2   ; ldr x9, [x9]
    100d0: cbz x9, done         ; zero would have been fine
    100d4: ldr w8, [x9, #0x8]   ; -> the fault

The firmware takes an eight-byte pointer at 0xfb2 of the compute descriptor,
where this driver wrote `shared_control_va` at 0xfac: the load picked up that
VA's top two bytes (0xffff) and the byte after it, so x9 was 0x1ffff and
0x1ffff + 8 is the faulting address. Six bytes out.

0xfac is not padding either -- the firmware reads a byte count there, rounds it
up to eight, adds it to the shared-control object's own total and clears it
(0x100f4..0x1010c) -- so the tail is: the pending count at 0xfac, two bytes, the
pointer at 0xfb2. The usage object follows at 0xfcb, which is what the image
reads; it never reads 0xfc5 or 0xfcd, the offsets this driver used.

This is the common 26A428 interface's six-byte insert again, as in the render
commands; the M5 Pro's build keeps the record at 0xfac. The descriptor now
holds the tail as bytes and places the record by build.

With the tail as the firmware reads it, the firmware survives the submission:
the work runs instead of faulting.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…does

The policy names its always-on (AON) block through a pointer at bundle +0xb858.
In macOS 27.0's own J813 bundle that pointer is the kernel window's 0xc07b_8000,
right above the shared cluster; this driver put the block at 0x1d_0000 (the 25G76 trace's address, which
the M5 Pro's build keeps), where
macOS keeps an empty page -- which is why that page looked like an empty AON
block in the first diff. Both name the same RT0g row.

On its own this does not change what the firmware does with a submission (the
GPU cores still stay off), but the block is now where the firmware has it on
macOS.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Two differences from macOS 27.0's own J813 hardware data, in the per-state
tables the firmware's power management reads:

- The J813 has twelve states, but the padded fourteen-entry table repeats its
  top state as state 13, and every per-state table (frequency, scale, relative
  performance A and B, the core and SRAM voltage rows) carried that phantom
  state. macOS leaves state 13 zero. The tables now stop at the board's own
  state count; every other M5 board has thirteen states, so all fourteen rows
  as before.
- Relative performance A (bundle +0x19e4) is an IEEE single per state in
  macOS's data -- 19.4 % at the lowest state up to 100 % -- where this encoder
  wrote the frequency ratio as a whole number (21, 30, ... 100), which read as
  floats are denormals. The values are not the frequency ratio, nor f*V or
  f*V^2: they are characterization data, so the J813 takes macOS's table and
  the other boards keep the ratio until theirs is read.

With these the J813's perf area is macOS's byte for byte. It does not by itself
get the GPU cores powered for a submission.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
Two debugfs files for comparing a running firmware with the one a machine's own
operating system runs:

- `firmware_log` dumps the log buffers the host publishes to each coprocessor,
  where an M5 firmware records what it is doing when it never reports a crash.
  (On the J813 they stay empty, as they do under macOS 27.0.)
- `hardware_data` dumps the hardware-data bundle this driver built, so it can be
  diffed word for word against a bundle read out of a macOS guest. Reading the
  bundle through the UAT instead needs physdump, which cannot map the no-map
  page-table region while this driver holds it.

Both take a torn snapshot of memory the firmware may be writing, so they are for
diagnosis only. `KernelObject::bytes()` exists for the same reason.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The GPU's power domain is gated by the time this driver probes: the boot loader
leaves it on, genpd finds no active consumer and powers it off, and ps_gfx then
reads target 0xf with actual 0 -- while the coprocessors still run, out of their
own always-on island, and report the GPU busy. macOS 27.0 has 0x1f0023ff there.

Two steps on the J813, as the G15 and G16 paths take for their own sessions:

- `asahi_j813_hold_power()` takes the runtime-PM reference
  (`asahi_g15_hold_power`'s sequence), so nothing gates the domain under us.
  On its own it is not enough here: marking the device active does not request
  a transition, and this domain is already off by then.
- `asahi_j813_power_on_gfx()` writes target 0xf with auto power management off
  and waits for the domain to reach it, then leaves it held. macOS re-enables
  auto power management because its own stack keeps a demand on the domain;
  this driver has nothing to keep one with yet.

The domain now reads 0x0f0023ff, actual 0xf, for the whole session. The GPU's
cores are a separate matter -- sgx+0xe01000 still reads 0, and a submission
still does not retire -- but they cannot come up through a gated domain.

This also drops the power-cycle trial from the boot path, which took the domain
off and asked for it back with auto power management on, so it gated again as
soon as the firmware went idle. That helper and the dashboard report helper,
which no longer has a caller either, are removed.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A render whose watchdog expires reaches its client as a lost device and the log
as a recovery with no reason, which does not separate work the firmware never
picked up from work it started and never finished. The evidence for that is
already read on this path -- the pair's ring cursors, its pass record, its
completion stamps -- and then thrown away.

Print it instead, for the first few groups of a boot. On the J813 it says the
firmware consumed every ring entry of a clear-only render and retired it with
an untouched pass record: it ran nothing.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A submission the driver rejects reaches userspace as EINVAL alone. Neither the
command the parser stopped at nor the render that missed a requirement is
named, so a userspace driver whose commands this kernel will not take has
nothing to go on.

Print the command's header where the parse stops, the counts where the walk
ends short, and the render's own fields -- the ones the requirements are
written in -- where validation fails. The first few refusals of a boot report;
further ones would repeat them.

The asahi-m4 host test compiles this parser (test-command-stream.py); its
harness gets a pr_warn! that checks the format arguments.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
A command's own requirements are only one of the steps an ioctl's commands
pass: the VM pin, the backend's preparation, the barrier and prior ordering,
the tiling prefixes and the timestamp destinations each refuse a submission
with the same bare EINVAL. Name the step.

On the J813 this located the refusal of every render one userspace driver
sends in the backend's preparation, which requires a render to declare its
per-program storage.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
… them

The common 26A428 interface inserts six bytes before the render command's UMA
pointer, moving the whole trailing record. macOS 27.0's own AGXG17G
(AGX-PI_300-G-A0, HAL300) is the ground truth: its
AGXTAChannelSKSM::submitBuffer writes the tiling command at +0x934 (UMA state),
+0x93c (the flag byte), +0x93d, +0x945, +0x94d, +0x975/+0x985 (the extra
addresses) and +0x995 (their flags), and
AGX3DChannelSKSM::submitBuffer the fragment one at +0x21ce, +0x21d6, +0x21d7,
+0x21df, +0x21e7, +0x220f, +0x2213, +0x2214/+0x2224, +0x2234 and +0x2235 --
every one of them exactly six bytes above the offset this encoder, built for
the M5 Pro, writes. The old 25G76 M5 encoder used the same offsets as the kext
does now, which is why the pre-27.0 driver's renders were accepted.

Move the record after encoding, for the common26 graph only, and grow the
tiling command to 0x9a0 bytes to hold it. Compared field by field against both
of the kext's writers, the J813's TA and 3D commands now agree with macOS's at
every offset it writes.

This is not by itself what keeps a render from executing: with it, the kernel
and the firmware still retire a kick without running the work.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
macOS 27.0 on the J813 (common 26A428 firmware) kicks compute differently
from the M5 Pro. Its WindowServer kicks, read under aura, show:

- every dependency of every kick class (TA, 3D, compute) in class 1, two
  bits each at kick+0x180 (0x01, 0x05, 0x15, 0x55 for one to four);
- the completion identity 0x0400000000000002 and the same 16 bytes of event
  metadata in every compute descriptor;
- a completion ordinal that counts the queue's compute kicks (+0xf70 runs
  0x341c, 0x341d, ...), as with the G17D forms;
- the event mask bit 64 + context in word 1 and no entry gate;
- the tuple preempt 0x1480 / dispatch base 0x14c08a60 / auxiliary control 0;
- resource flags 0x0104054b: one secondary and one imageblock unit beside
  the primary one.

Add Board::j813_forms() and take these forms on the J813 only; the J714,
J716C and J775D keep theirs.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
… writes them

macOS 27.0's TA register program on the J813 keeps its own bracket: it opens
with 0x1748 = 1 and closes with 0x1740 = 1. It carries the common registers
unchanged, without the tiler parameter sub-buffer writes the M5 Pro program
inserts. 0x10141 (copied to 0x1ca60) keeps the one-layer 0x200. Its fragment
program writes no 0x160a8 before either tile write (90 registers instead of
92) and 0xa741 = 0 at entry.

Build both programs in that form on the J813 only.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
macOS 27.0 never uses generation 0 for a context's work-state words: on the
J813 they run 0x17c8.. 0x17ea. Start the sequence at 0x40, the first word of
generation 1. Only the words' upper bits change; the nodes they address and
the order they are drawn in are the same.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813's firmware (common 26A428) takes work in the G17P/Neo forms, not
the M5 Pro's. macOS 27.0 there:

- submits through the mailbox doorbell alone. It never writes the host port
  (sgx+0x4090/0x4098) or the KSM queue registers (sgx+0x21068..0x21080), and
  rings render doorbells as 0x83 | priority << 2, without the M5 Pro's bit 1;
- fills its TA, 3D and compute item rings with [configuration], descriptor,
  KickAnnounce (tag 0xe) records, never the descriptor-less tag-16
  notification;
- sends a queue configuration with every kick and never publishes one
  alone. Leaving it out of a kick whose configuration had not changed let
  a fragment run under another client's (or no) context under parallel
  load;
- names the paired 3D queue at +0x82 of the TA configuration, where the M5
  Pro has the parameter manager;
- names no event in its TA and 3D kicks.

Do the same on the J813, gated by J813_FORMS, which the device sets from
its board at probe.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The J813 (common 26A428) keeps its private-memory pools in the G17P form.
This was read from macOS 27.0's own objects on that machine:

- The page-pool rows are the M5 descriptor's first four words on a 0x20
  stride: [directory >> 7, control + 0x20, pages | entries << 32, 0] (macOS's
  table at firmware 0x20015f0000, slots 0, 1, 2). The M5 Pro form is eight
  words on 0x40. Written in that form, every pool the driver installed read
  as an empty row, and the firmware recovered within milliseconds of each
  kick, blaming the queue.
- The pool control is the 0x68-byte G17P one: +0x00 render 1 / compute 3,
  +0x10 1, run cursor +0x48 (eight qwords per block), state +0x4c,
  completed +0x54, +0x5c compute 1, class +0x60 (render 2, compute 1). The
  M5 Pro control is the same with 0x20 bytes inserted at +0x38.
- The run list has 0x40-byte slots of 512-page runs (the non-G17S geometry).
- Only the first four saved descriptor fields live in the row; the rest are
  the pool's own.

With this the J813 executes GPU work: compute fills and copies, renders
clear and draw, and Vulkan and GL run.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
With parallel clients the J813's firmware took fragment MMU faults reading
0x10_0000_0040 in VM slot 0. It touches a render's metrics in context 0,
whose user root is the kernel lower VM, at the client's compact-window
address.

Map each client's metrics entry at the compact window's start plus the
entry's offset in the shared backing. Map the whole backing at the window's
start in the kernel lower VM too, and link that window's table before the
firmware samples the lower root's top-level entries. If the fixed address is
taken, a client falls back to any free address in the window, as before.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
The host's pre-render growth target is an upper bound from the tile count,
and the buffer's capacity was fixed when it was created for the pair's
first render. A 16384x16384 render without attachments after smaller ones
asked for more blocks than the buffer can hold. The render then failed with
ERANGE and the client lost the device
(dEQP-GLES31.functional.fbo.no_attachments.maximums.*).

Clamp the target to the buffer's capacity, as firmware_target() does for
the firmware's own requests.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
…gment kick

With several clients, the J813 took a few GPU recoveries per conformance run
(GLES31 at four jobs: 5 to 9). Every one was the same MMU fault: the ISP
parameter fetch (requestor 0x9a) reading a render's own tile data in its
compact window, at level 1, while the hardware had another context's VM slot
bound, or slot 0. The firmware then blamed the render's 3D queue. One client
at a time never faulted.

macOS 27.0 never lets those overlap. In its own kick queues, read from four
captures of the J813 under aura (256 TA kicks each), 239 TA kicks name a 3D
kick as a dependency, and every one of them names the 3D kick of the
immediately preceding render. This driver gave a tiling kick no dependency
on earlier fragment work at all, so with parallel clients one context's TA
ran beside another's 3D, and that 3D's parameter fetch went through the TA's
VM.

On the J813, a tiling kick now also waits, in class 2, for the last fragment
kick published on any pair, which is the order macOS keeps.

These did not change the faults:
- sending the TA configuration without its parameter-buffer binding, as
  macOS 27.0 does in steady state;
- growing every parameter buffer to capacity, so no render needs a partial
  render;
- listing the live contexts' scheduler records at +0x800 of the PB region,
  as macOS has them.

At four jobs, with this ordering:
- GLES2: 16955 pass, 0 fail, 0 recoveries;
- GLES3: 44379 pass, 1 fail, 0 recoveries;
- GLES31: 37337 pass, 2 fail, 1 flake, 0 recoveries (it ran in 3m00s; 4m03s
  before, with the recoveries).

The remaining failures fail without it too. The GLES31 ones are the
image_load_store qualifier cases, which also fail under parallel load on
the 26.x kernel.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
@sithglan
sithglan force-pushed the thomas/j813-gpu-27-on-integration branch from ab5d6bc to 7595d75 Compare November 10, 2026 13:21
@sithglan

Copy link
Copy Markdown
Contributor Author

Rebased onto integration/aurora 5186d2e, which is past c54454d. I force-pushed ab5d6bc -> 7595d75, so the branch now has only the 30 J813 commits. The description is updated.

How the 31 J813 commits of ab5d6bc came across (git range-diff):

  • 24 are unchanged, and 3 differ only in context: integration's other M5 boards gained completion_inputs.
  • I dropped "drm/apple: T8142 on firmware 27: give the console ARGB8888". Integration's cd371f9 does the same for every firmware-27 DCP.
  • Four were resolved against drm/asahi, drm/apple: M5 GPU and firmware-27 display on integration/aurora #1012 as merged, which is DJ's later stack with the M5 Pro completion forms:
    • The J813 board sets completion_inputs: false.
    • The descriptor tail: common26_tail sits beside submit_sequence.
    • The J813 compute forms are checked before the M5 Pro ones. explicit_dependency() gives class 1 on the J813 and counted_forms() includes it. kick_event_mask() and kick_tuple() keep their J813 forms. J714, J716C and J775D get exactly what integration gives them.
    • The last-fragment wait is rebased onto render_barriers(.., compute_class).

Three things on the J813 now come from integration's code. A render kick's compute dependencies take class 1 (explicit_dependency()), which is what macOS 27.0 writes there. A compute kick waits on the host for producers on other engines (host_producer_waits()). Timestamp buffers get no firmware window. I tested with all three.

Kernel 7.3.0-aurora-j813-oni4s+ is this branch merged with #1423, booted through aura-try. 4 jobs, no GPU recoveries:

suite mesa-j813 aurora-wip native + mesa#207 aurora-wip zink + mesa#208
GLES2 16955 / 0 16956 / 0 16957 / 0
GLES3 44380 / 0 44409 / 0 not run
GLES31 37339 / 1 37352 / 0 not run

The previous head gave 16955 / 0, 44380 / 0 and 37337 / 0 with 3 flakes. The one GLES31 failure, image_load_store.cube.qualifiers.coherent_r32f, is in the qualifiers group that fails only under parallel load. Run alone, it passes 5 / 5. The aurora-wip native and zink results are identical to the previous head's.

Host tests: 224 / 224. apple-dt-check: 0 new warnings (412 = 412), and apple,dcp.yaml passes. SEP KUnit: 194 / 194. Per-commit build: 30 / 30. Firefox, Chromium, mpv, alacritty, zathura and vkcube under sway, and kmscube on the panel (60 fps), work as before.

cc @Acelogic @DjDeveloperr @LiamRay10

@sithglan

Copy link
Copy Markdown
Contributor Author

Panel screenshot from the J813 on this branch (7.3.0-aurora-j813-oni4s+, booted through aura-try):

J813 panel under sway

sway runs on the panel (eDP-1 on the DCP, 2560x1600 at scale 2) and composites natively on the GPU (WLR_RENDER_DRM_DEVICE=/dev/dri/renderD128, mesa-j813). In the screenshot:

  • alacritty, with the model, the kernel, the EGL/GL renderer "Apple M5 (G17G C0)" and the Vulkan device (Honeykrisp);
  • vkcube;
  • Firefox on reddit.

The capture is grim -o eDP-1.

sithglan added a commit to sithglan/mesa-aurora that referenced this pull request Nov 10, 2026
On integration/aurora the J813 (G17G) runs on the G17 kernel runtime
(aurora-silicon/linux#1413), which reports DRM_ASAHI_FEATURE_RENDER_RESOURCES
and _V2 for every M5 board. agx_m5_render_resources() then holds for G17G
as well, so its renders send aurora-silicon#194's program storage lists and its compute
goes as one command per dispatch in the render's submission.

m5_split_prepass still took G17G, for the M5 runtime of older kernels,
which takes one dispatch per submission. With both set:
- the render command's CDM barrier was 0, so the render did not wait for
  the compute commands of its own submission;
- the submission pointed the kernel at its output syncs for its input
  syncs. The split path clears in_sync_count after its per-dispatch
  submissions, but this path made none, so a batch with input syncs
  would have named its own unsignalled output syncobj as an input.

Take the split path only where the kernel does not take mixed command
arrays, and say in the comments that G17G takes the G17 runtime's lists
where it runs on it.

Signed-off-by: Thomas Glanzmann <thomas@glanzmann.de>
@sithglan

Copy link
Copy Markdown
Contributor Author

@MrNews89 Following up on your J815 questions in #847: the J813 GPU no longer depends on the 25G76 stub. This PR runs it under the macOS 27.0 stub (26A428 GPU firmware, 27.0 DCP firmware), with compute, render, Vulkan and GL, and sway, Firefox and mpv on the panel. #847's backend refused any firmware profile other than 25G76, so if your stub was on 27.x, that was likely why it didn't come up.

The Mesa side is public now too: the G17G path is on aurora-silicon/mesa (agx3c, aurora-silicon/mesa#168), and aurora-silicon/mesa#207 moves it onto aurora-wip's M5 submission path. One caveat from #847 still applies: the init data was measured on the J813's 8 cores, so the J815's 10 cores may need their own measurement.

@m0d16l14n1

Copy link
Copy Markdown
Contributor

@sithglan thank you for your work - I was able to boot on J815, on macOS 27.2 with some of Claude help and tinkering

Preserve the J813 firmware-27 series and current integration GPU/display
changes. Resolve only the Asahi object list by retaining both mainline
legacy_sched.o and the J813 g17/pmpdash.o helper, along with retained
cpu-capacity objects.

Signed-off-by: Diljit Singh <hey@djdev.me>

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

Codex (GPT-6.1 Sol), reviewing on @DjDeveloperr’s behalf.

No remaining blocking source findings in the reviewed/integrated net change. Integrated current main without changing firmware constants or dropping newer M5 behavior. The sole manual conflict is the Asahi Makefile: both legacy_sched.o and g17/pmpdash.o are kept, along with newer retain/cpu_capacity objects. Original38-path source scope and author ancestry remain. Existing J813 FW27 GPU/display reports and per-commit builds are attributed evidence; fresh exact-head Host/DT results are required. The narrow fixed PMGR helper is acknowledged follow-up architectural debt, not permission to widen admission or skip other M5 regression qualification.

Approval does not waive pending CI, dependency order, hardware-safety gates or source-versus-release qualification. No source merge is an installed-loader write or a new hardware test.

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.

4 participants