Apple M3 GPU reverse engineering progress

Anna Maniscalco (Valve) @pac85@treehouse.systems

Alyssa Milburn @noopwafel@teh.entar.net

TellowKrinkle @TellowKrinkle@treehouse.systems

Viola @viola@treehouse.systems - Couldn’t be here

Three pieces

Where our story begins?

The new HW

How much could Apple possibly have changed?

Easy port?

// PBE descriptor
agx_pack(..., PBE, cfg) {
  cfg.dimension = AGX_TEXTURE_DIMENSION_2D;
  cfg.layout = AGX_LAYOUT_LINEAR;
  cfg.channels = R8G8B8A8;
  cfg.type = AGX_TEXTURE_TYPE_UNORM;
  cfg.swizzle_r = 0;
  cfg.swizzle_g = 1;
  cfg.swizzle_b = 2;
  cfg.swizzle_a = 3;
  cfg.width = width;
  cfg.height = height;
  cfg.buffer = out_bo.va->addr & 0xffffffff;
  cfg.level = 1;
  cfg.stride = (width * 4) - 16;
}
// PBE descriptor
agx_pack(..., PBE, cfg) {
  cfg.dimension = AGX_TEXTURE_DIMENSION_2D;
  cfg.layout = AGX_LAYOUT_LINEAR;
  cfg.channels = R8G8B8A8;
  cfg.type = AGX_TEXTURE_TYPE_UNORM;
  cfg.swizzle_r = 0;
  cfg.swizzle_g = 1;
  cfg.swizzle_b = 2;
  cfg.swizzle_a = 3;
  cfg.width = width;
  cfg.height = height;
  cfg.buffer = out_bo.va->addr;
  cfg.level = 1;
  cfg.stride = (width * 4) - 16;
}

Shaders

Oh no, new ISA!

M1 ISA RE

Metal Binary Archives

00000000  cb fe ba be 00 00 00 02  01 00 00 13 00 00 01 42
00000010  00 00 00 30 00 00 20 a0  00 00 00 04 01 00 00 17
00000020  00 00 00 0b 00 00 20 d0  00 00 12 3c 00 00 00 04
00000030  cf fa ed fe 13 00 00 01  42 01 00 00 0d 00 00 00

Metal Binary Archives

00000000  cb fe ba be 00 00 00 02  01 00 00 13 00 00 01 42
00000010  00 00 00 30 00 00 20 a0  00 00 00 04 01 00 00 17
00000020  00 00 00 0b 00 00 20 d0  00 00 12 3c 00 00 00 04
00000030  cf fa ed fe 13 00 00 01  42 01 00 00 0d 00 00 00

Metal Binary Archives

00000000  cb fe ba be 00 00 00 02  01 00 00 13 00 00 01 42
00000010  00 00 00 30 00 00 20 a0  00 00 00 04 01 00 00 17
00000020  00 00 00 0b 00 00 20 d0  00 00 12 3c 00 00 00 04
00000030  cf fa ed fe 13 00 00 01  42 01 00 00 0d 00 00 00
$ file test.bin
test.bin:  [applegpu_g13g:Mach-O 64-bit GPU executable applegpu_g13g] [air64:MetalLib executable (MacOS), version 1.2.8]
test.bin (for architecture applegpu_g13g):  Mach-O 64-bit GPU executable applegpu_g13g
test.bin (for architecture cputype (16777239) cpusubtype (11)): MetalLib executable (MacOS), version 1.2.8

Metal Binary Archives

Metal Binary Archives

00000000  cf fa ed fe 13 00 00 01  42 01 00 00 01 00 00 00
00000010  03 00 00 00 90 02 00 00  00 00 00 00 00 00 00 00
00000020  19 00 00 00 28 02 00 00  00 00 00 00 00 00 00 00
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

Metal Binary Archives

00000000  cf fa ed fe 13 00 00 01  42 01 00 00 01 00 00 00
00000010  03 00 00 00 90 02 00 00  00 00 00 00 00 00 00 00
00000020  19 00 00 00 28 02 00 00  00 00 00 00 00 00 00 00
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00

Compiler Explorer

kernel void test(uint pos [[thread_position_in_grid]], device uint4* out, const device uint4* in0, device uint4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
$ pbpaste | python3 compiler_explorer.py -
compute shader:
   0: 72111004             get_sr           r4, sr80 (thread_position_in_grid.x)
   4: 0501840e00c8f200     device_load      0, i32, xyzw, r0_r1_r2_r3, u2_u3, r4, unsigned, lsl 2
   c: 0529880e00c8f200     device_load      0, i32, xyzw, r5_r6_r7_r8, u4_u5, r4, unsigned, lsl 2
  14: 3800                 wait             0
  16: 0e01ca022c000000     iadd             r0, r5.discard, r0.discard
  1e: 0e05cc222c000000     iadd             r1, r6.discard, r1.discard
  26: 0e09ce422c000000     iadd             r2, r7.discard, r2.discard
  2e: 0e0dd0622c000000     iadd             r3, r8.discard, r3.discard
  36: 4501800e00c8f200     device_store     0, i32, xyzw, r0_r1_r2_r3, u0_u1, r4, unsigned, lsl 2, 0
  3e: 8800                 stop

Running Custom Assembly

Running Custom Assembly

$ python3 hwtestbed.py "isub.sat r0, r0, r1" -r{1,-8},{2,0x7fffffff}
Thread 0: r0: 00000000 (0         ), input: 1,2
Thread 1: r0: fffffff6 (-10       ), input: -8,2
Thread 2: r0: 00000000 (0         ), input: 1,0x7fffffff
Thread 3: r0: 7ffffff9 (2147483641), input: -8,0x7fffffff
$ python3 hwtestbed.py "isub.sat r0, r0, r1.sx" -r{1,-8},{2,0x7fffffff}
Thread 0: r0: ffffffff (-1        ), input: 1,2
Thread 1: r0: 7fffffff (2147483647), input: -8,2
Thread 2: r0: 80000002 (-2147483646), input: 1,0x7fffffff
Thread 3: r0: 7ffffff9 (2147483641), input: -8,0x7fffffff

Running Custom Assembly

$ python3 hwtestbed.py -b 4e01402a24000000 -r{1,-8},{2,0x7fffffff}
Thread 0: r0: 00000000 (0         ), input: 1,2
Thread 1: r0: fffffff6 (-10       ), input: -8,2
Thread 2: r0: 00000000 (0         ), input: 1,0x7fffffff
Thread 3: r0: 7ffffff9 (2147483641), input: -8,0x7fffffff
$ python3 hwtestbed.py -b 4e01402a64000000 -r{1,-8},{2,0x7fffffff}
Thread 0: r0: ffffffff (-1        ), input: 1,2
Thread 1: r0: 7fffffff (2147483647), input: -8,2
Thread 2: r0: 80000002 (-2147483646), input: 1,0x7fffffff
Thread 3: r0: 7ffffff9 (2147483641), input: -8,0x7fffffff

M3 ISA RE

compute shader prolog:
   0: 0300                 <disassembly failed>
   2: 0700                 <disassembly failed>
TODO: 1000

M1 vs M3

M1 M3
Variable length ISA, in multiples of 16 bits
Simple compression scheme (upper bits are 0)
Field placement is consistent

M3 ISA RE

kernel void test(uint pos [[thread_position_in_grid]], device uint4* out, const device uint4* in0, device uint4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
compute shader:
00000000: 8C A0 10 06 06 04 06 00  06 00 06 00 67 10 54 00  ............g.T.
00000010: 01 08 20 00 57 01 00 40  40 00 67 00 44 08 02 08  .. .W..@@.g.D...
00000020: 20 00 57 01 00 40 40 00  06 80 06 00 06 00 06 00   .W..@@.........
00000030: 9F 01 56 00 02 10 00 A8  17 05 9F 01 54 02 02 14  ..V.........T...
00000040: 08 A8 17 05 9F 01 54 04  02 18 10 A8 17 05 9F 01  ......T.........
00000050: 54 06 02 1C 18 A8 17 05  E7 00 54 00 00 08 21 00  T.........T...!.
00000060: 17 00 00 10 10 00 0E 00  00 00                    ..........

M3 ISA RE

kernel void test(uint pos [[thread_position_in_grid]], device uint4* out, const device uint4* in0, device uint4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
compute shader:
00000000: 8C A0 10 06 06 04 06 00  06 00 06 00 67 10 54 00  ............g.T.
00000010: 01 08 20 00 57 01 00 40  40 00 67 00 44 08 02 08  .. .W..@@.g.D...
00000020: 20 00 57 01 00 40 40 00  06 80 06 00 06 00 06 00   .W..@@.........
00000030: 9F 01 56 00 02 10 00 A8  17 05 9F 01 54 02 02 14  ..V.........T...
00000040: 08 A8 17 05 9F 01 54 04  02 18 10 A8 17 05 9F 01  ......T.........
00000050: 54 06 02 1C 18 A8 17 05  E7 00 54 00 00 08 21 00  T.........T...!.
00000060: 17 00 00 10 10 00 0E 00  00 00                    ..........

M3 ISA RE

kernel void test(uint pos [[thread_position_in_grid]], device uint4* out, const device uint4* in0, device uint4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
compute shader:
   0: 8C A0 10 06
   4: 06 04 06 00 06 00 06 00
   c: 67 10 54 00 01 08 20 00 57 01 00 40 40 00
  1a: 67 00 44 08 02 08 20 00 57 01 00 40 40 00
  28: 06 80 06 00 06 00 06 00
  30: 9F 01 56 00 02 10 00 A8 17 05
  3a: 9F 01 54 02 02 14 08 A8 17 05
  44: 9F 01 54 04 02 18 10 A8 17 05
  4e: 9F 01 54 06 02 1C 18 A8 17 05
  58: E7 00 54 00 00 08 21 00 17 00 00 10 10 00
  66: 0E 00 00 00

M3 ISA RE

kernel void test(uint pos [[thread_position_in_grid]], device uint4* out, const device uint4* in0, device uint4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
compute shader:
   0: 8C A0 10 06                                get_sr           0, r8, sr32 (thread_position_in_grid.x)
   4: 06 04 06 00 06 00 06 00                    wait_long        0
   c: 67 10 54 00 01 08 20 00 57 01 00 40 40 00  device_load      5, i32, xyzw, r0_r1_r2_r3, u2_u3, r8, lsl 4, 0, wait 0, 1, 1, 0
  1a: 67 00 44 08 02 08 20 00 57 01 00 40 40 00  device_load      5, i32, xyzw, r4_r5_r6_r7, u4_u5, r8, lsl 4, 0, 1, 0, 0
  28: 06 80 06 00 06 00 06 00                    wait_long        5
  30: 9F 01 56 00 02 10 00 A8 17 05              iadd             r0, r4.discard, r0.discard, wait 5
  3a: 9F 01 54 02 02 14 08 A8 17 05              iadd             r1, r5.discard, r1.discard
  44: 9F 01 54 04 02 18 10 A8 17 05              iadd             r2, r6.discard, r2.discard
  4e: 9F 01 54 06 02 1C 18 A8 17 05              iadd             r3, r7.discard, r3.discard
  58: E7 00 54 00 00 08 21 00 17 00 00 10 10 00  device_store     0, i32, xyzw, r0_r1_r2_r3, u0_u1, r8.discard, lsl 4, 0, 1, 1
  66: 0E 00 00 00                                stop

M3 ISA RE

kernel void test(uint pos [[thread_position_in_grid]], device float4* out, const device float4* in0, device float4* in1) {
    out[pos] = in0[pos] + in1[pos];
}
compute shader:
   0: 8C A0 10 06                                get_sr           0, r8, sr32 (thread_position_in_grid.x)
   4: 06 04 06 00 06 00 06 00                    wait_long        0
   c: 67 10 54 00 01 08 20 00 57 01 00 40 40 00  device_load      5, i32, xyzw, r0_r1_r2_r3, u2_u3, r8, lsl 4, 0, wait 0, 1, 1, 0
  1a: 67 00 44 08 02 08 20 00 57 01 00 40 40 00  device_load      5, i32, xyzw, r4_r5_r6_r7, u4_u5, r8, lsl 4, 0, 1, 0, 0
  28: 06 80 06 00 06 00 06 00                    wait_long        5
  30: 09 09 1C 01 00 C0                          fadd             r0, r4.discard, r0.discard, wait 5
  36: 19 0B 18 03                                fadd             r1, r5.discard, r1.discard
  3a: 29 0D 18 05                                fadd             r2, r6.discard, r2.discard
  3e: 39 0F 18 07                                fadd             r3, r7.discard, r3.discard
  42: E7 00 54 00 00 08 21 00 17 00 00 10 10 00  device_store     0, i32, xyzw, r0_r1_r2_r3, u0_u1, r8.discard, lsl 4, 0, 1, 1
  50: 0E 00 00 00                                stop

M1 vs M3

M1 M3
Variable length ISA, in multiples of 16 bits
Simple compression scheme (upper bits are 0) Complicated compression scheme
Field placement is consistent Fields look like they’ve been packed like C bitfields

Load Immediate

M1

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Imm32 Opcode

Load Immediate

M1

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Opcode

Load Immediate

M1

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Opcode

M3

63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Opcode

Enums

Enums

Mask
0
1 x
2 yw
3 xyw
4 zw
5 xzw
6 yzw
7 xyzw
8 y
9 xy
a z
b xz
c yz
d xyz
e w
f xw

Enums

Buffer Texture
0 x
1 x y
2 yw z
3 xyw w
4 zw xw
5 xzw xy
6 yzw xyz
7 xyzw xyzw
8 y xz
9 xy yz
a z yw
b xz zw
c yz xzw
d xyz xyw
e w yzw
f xw

Enums

Buffer Texture
0 x
1 x y
2 yw z
3 xyw w
4 zw xw
5 xzw xy
6 yzw xyz
7 xyzw xyzw
8 y xz
9 xy yz
a z yw
b xz zw
c yz xzw
d xyz xyw
e w yzw
f xw

Texture one is at bits 27, 28, 37, and 86

Compare Select

Control Flow

Compare Select CC

Puzzling out the rest of userspace

Staring at bytes

Road to demo

Apple’s way of doing drivers

Think different

CDM?

What about graphics

VDM?

How does it map to vulkan?

The big news

How it used to work

Preshader?

The new HW

This shader is treated like a descriptor

Entry shader

   0: 2C A0 02 00 12 08 78 01                    mov_imm          r2, 3085472
   8: 3C 80 02 00 04 00 00 00                    mov_imm          r3, 256
  10: 6C E0 42 00 06 00 20 00                    mov_imm          r22, 262624
  18: 7C 80 42 00 04 00 00 00                    mov_imm          r23, 256
  20: 67 00 54 24 02 00 00 00 57 00 00 40 26 00  device_load      1, i32, xyzw, r18_r19_r20_r21, r2_r3, 0, lsl 2, 0, 1, 1, 0
  2e: 67 00 54 34 16 00 00 00 57 00 02 40 26 00  device_load      1, i32, xyzw, r26_r27_r28_r29, r22_r23, 0, lsl 2, 16, 1, 1, 0
  3c: 67 00 54 2C 16 00 00 00 57 04 00 40 26 00  device_load      1, i32, xyzw, r22_r23_r24_r25, r22_r23, 0, lsl 2, 0, 1, 1, 1
  4a: 77 00 2A 41 00 00 00 00                    load_shader.todo 130
  52: 77 01 AA A6 00 00 00 02                    load_shader.todo 17179869517
  5a: 06 00                                      nop              
  5c: F7 00 2A 00 00 10 20 10 20 00              ralloc           1026, 1026
  66: 1C 80 02 00 00 00 00 00                    mov_imm          r1, 0
  6e: 14 81 11 06 00 00 00 00                    get_sr           0, r1l, sr1
  76: 0C 80 02 00 04 00 00 00                    mov_imm          r0, 256
  7e: 9F 11 54 00 02 00 08 A8 10 05              iadd             r0, r0, r1, lsl 4, wait 0
  88: 1C 80 02 00 04 00 00 00                    mov_imm          r1, 256
  90: 0F 12 54 00 4C 00 06 08 06 00              <disassembly failed>
  9a: 06 00                                      nop              
  9c: 06 00                                      nop              
  9e: 0B 24 09 04                                mov              u0, r18.discard
  a2: 1B 26 09 04                                mov              u1, r19.discard
  a6: 2B 28 09 04                                mov              u2, r20.discard
  aa: 3B 2A 09 04                                mov              u3, r21.discard
  ae: 8B 34 09 04                                mov              u8, r26.discard
  b2: 9B 36 09 04                                mov              u9, r27.discard
  b6: AB 38 09 04                                mov              u10, r28.discard
  ba: BB 3A 09 04                                mov              u11, r29.discard
  be: 4B 2C 09 04                                mov              u4, r22.discard
  c2: 5B 2E 09 04                                mov              u5, r23.discard
  c6: 6B 30 09 04                                mov              u6, r24.discard
  ca: 7B 32 09 04                                mov              u7, r25.discard
  ce: 0E 00 00 00                                stop 

Dynamic registers

More than 128?

Wtf?

🤯

Wtf?

.. if only we had some way to observe this?

What does this mean for the compiler?

A catch

A catch

Bad for Mesa

So what do we do?

Current status

We’ve implemented

Current status

We can compile this shader

   nir_builder b = nir_builder_init_simple_shader(MESA_SHADER_COMPUTE,
                                                  &agx_nir_options, "agx_troll");
   
   nir_def *l =  nir_load_global(&b, 1, 32, nir_imm_int64(&b, iova));
   nir_def *cond = nir_ilt_imm(&b, nir_ior_imm(&b, l, 1), 10);
   nir_if * if_ = nir_push_if(&b, cond);
   nir_push_loop(&b);
   nir_store_global(&b, nir_ior(&b, l, nir_imm_int(&b, 67)), nir_imm_int64(&b, iova));
   nir_break_if(&b, cond);
   nir_pop_loop(&b, NULL);
   nir_pop_if(&b, if_); 

Current status

output

   0: 2C 80 02 00 00 00 0C 00                    mov_imm          r2, 98304
   8: 3C 80 02 00 04 00 00 00                    mov_imm          r3, 256
  10: 67 00 44 02 02 00 00 00 31 00 00 00 20 00  device_load      0, i32, x, r1, r2_r3, 0, lsl 4, 0, 0, 0, 0
  1e: 06 04 06 00 06 00 06 00                    wait_long        0
  26: 4B 03 07 80 12 0A 00 00 00 00              or               r4, r1, 1
  30: 0A 09 0D 8A 22 81 07 02 20 80              cmpsel           slt, r0h, r4.discard, 10, 1, 0
  3a: 1A 80 23 00 06 00 97 00 00 00              cmp              ine, cc0, 0, r0h
  44: 0F 05 54 01                                if               cc0, 1
  48: 0A 80 23 80 06 00 87 02 00 00              cmp              ieq, cc0, 0, 0
  52: 0F 05 54 01                                if               cc0, 1
  56: 4B 03 07 C2 12 0A 00 00 00 00              or               r4, r1, 67
  60: E7 00 44 08 02 00 00 00 31 00 00 00 08 00  device_store     0, i32, x, r4, r2_r3, 0, lsl 4, 0, 0, 0
  6e: 06 04 06 00 06 00 06 00                    wait_long        0
  76: 1A 80 23 00 06 00 97 00 00 00              cmp              ine, cc0, 0, r0h
  80: 0F 05 54 01                                if               cc0, 1
  84: 8F 05 54 02 00 06                          <disassembly failed>
  8a: 0F 06 04 01 00 00                          pop_exec         1
  90: 0F 00 54 E2 FF FF FF FF FF 00              jmp_exec_any     0x72
  9a: 0F 06 04 01 00 00                          pop_exec         1
  a0: 0F 06 04 01 00 00                          pop_exec         1
  a6: 0E 00 00 00                                stop

...
Output buffer after dequeue, 67

Where do we go from there

None of this is hard, mostly just significant work

Kernel driver

drivers/gpu/drm/asahi/driver.rs
@@ -145,6 +145,11 @@ impl drm::driver::Driver for AsahiDriver {
+        (
+            of::DeviceId::new(c_str!("apple,agx-t8122")),
+            &hw::t8122::HWCONFIG
+        ),
+

@@ -205,6 +210,9 @@ fn probe(
+            (hw::GpuGen::G15, _, &[14, 7, 0]) => {
+                gpu::GpuManagerG15V14_7::new(&drm.clone(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }

Kernel driver

arch/arm64/boot/dts/apple/t8122.dtsi
@@ -374,6 +408,65 @@ soc {
+   gpu: gpu@292400000 {
+     compatible = "apple,agx-t8122", "apple,agx-g15g";
+     reg = <0x2 0x92400000 0 0x70000>,
[1.245075] asahi 292400000.gpu: Probing...
[1.271715] asahi 292400000.gpu: GPU Information:
[1.272328] asahi 292400000.gpu:   Type: G15G
[1.276848] asahi 292400000.gpu:   Cores: 10 (10)
[5.139875] [drm] Initialized asahi 0.0.0 for 292400000.gpu on minor 1

Easy mode

Three small tweaks:

How to port?

add 14.x support to m1n1’s AGX tracer, then add M3 support, then add 14.x+M3 support to the kernel

Tracing

CBW. WC3D job_params3 =
CBW. WC3D   Start3DJobParameters3:
CBW. WC3D.S3DP3 scissor_array = Start3DArrayAddr:
CBW. WC3D.S3DP3   ptr = 0x10001c50000
CBW. WC3D.S3DP3 visibility_result_buffer = 0x10001af8000
CBW. WC3D.S3DP3 unk_partial_bg = Start3DBackgroundProgram:
CBW. WC3D.S3DP3   pipeline_bind = 0x7800000000040
CBW. WC3D.S3DP3   address = 0x10001ae8100
CBW. WC3D.S3DP3 partial_bg = Start3DBackgroundProgram:
CBW. WC3D.S3DP3   pipeline_bind = 0xffff800000000000
CBW. WC3D.S3DP3   address = 0x10001ae8000
CBW. WC3D.S3DP3 depth_flags = 0x0
CBW. WC3D.S3DP3 unk_290 = 0x4040404

Tracing

[gfx-asc] frag work item
[gfx-asc] vert work item
[gfx-asc] kick~!
[gfx-asc] stats: Power ON: spent 0.5544s powered off (13305045 ticks)
[gfx-asc] stats: PowerState:         ps=1 100 0 0 3 0 0 0 0
[gfx-asc] stats: Power: 22.291 mW
[gfx-asc] stats: TempSensor: #3 40.19°C s=4 tmin=32 tmax=40
[gfx-asc] stats: Temp: 40.61°C s=4 tmin=32 tmax=40
[gfx-asc] stats: Power OFF: spent 0.003242s powered on (77810 ticks)
[gfx-asc] pong~!
regionB.page_mgr_info (0xfffffc20000e0000..0xfffffc20000e01ff)
  0003ae00 10000002 00004a00 0000bca0 00005c00 00000000 00000000 00000000
[gfx-asc] event fired: 1, 2

14.x Firmware

(Someone maintained the m1n1 debug tooling, right?)

Rearranging structures

     pub(crate) struct RunWorkQueueMsg {
+    #[ver(V < V14_7)]
     pub(crate) pipe_type: PipeType,
     pub(crate) work_queue: Option<GpuWeakPointer<super::workqueue::...>>,
+    #[ver(V >= V14_7)]
+    pub(crate) pipe_type: PipeType,
+    #[ver(V < V14_7)]
     pub(crate) wptr: u32,
+    #[ver(V >= V14_7)]
+    pub(crate) wptr: u16,
+    #[ver(V < V14_7)]
     pub(crate) event_slot: u32,
+    #[ver(V >= V14_7)]
+    pub(crate) event_slot: u8,
     ...

driver: G15 specifics

Surprisingly little on top of G14X:

Tracing

Ver("G >= G15", "g15_unk_8" / Int64ul), # 0
Ver("G >= G15", "g15_unk_10" / Int64ul), # 0
Ver("G >= G15", "g15_ptr_18" / Int64ul), # blockmgr buf
Ver("G >= G15", "g15_ptr_20" / Int64ul), # 0
Ver("G >= G15", "g15_gptr_28" / Int64ul), # pmgr buf1
Ver("G >= G15", "g15_gptr_30" / Int64ul), # pmgr buf2
Ver("G >= G15", "g15_gptr_38" / Int64ul), # pmgr buf3
Ver("G >= G15", "g15_gptr_40" / Int64ul), # pmgr buf4

Tracing

CBW. WCCP. SCC.[0x164.8] g15_unk_8 = 0x0
CBW. WCCP. SCC.[0x16c.8] g15_unk_10 = 0x0
CBW. WCCP. SCC.[0x174.8] g15_ptr_18 = 0xfffffc2000190000
CBW. WCCP. SCC.[0x17c.8] g15_ptr_20 = 0x0
CBW. WCCP. SCC.[0x184.8] g15_gptr_28 = 0x1000c5d0000
CBW. WCCP. SCC.[0x18c.8] g15_gptr_30 = 0x1000c5df400
CBW. WCCP. SCC.[0x194.8] g15_gptr_38 = 0x1000c5ee800
CBW. WCCP. SCC.[0x19c.8] g15_gptr_40 = 0x1000c5ef000

driver: Page manager

.. huh, why would the HW be allocating memory?

driver: Page manager

  ralloc           2052
 
  mov_imm          r6, 458855      # 0x67 | (7 << 16)
  mov              r7, r6.cache, group 4
  ...
  mov_imm          r6, 134086759   # 0x67 | (2046 << 16)
  mov              r2046, r6.cache, group 4
  mov_imm          r6, 134152295   # 0x67 | (2047 << 16)
  mov              r2047, r6.cache, group 4

what is GPU

      agx_push(out, CDM_GLOBAL_SIZE, cfg) {
         cfg.x = 8;
         cfg.y = 8;
         cfg.z = 4;
      }

driver: Page manager

>>> chexdump(agx.uat.ioread(1, 0x10080000000 + 0x17000, 0x1000))
00000000  67 00 39 00 67 00 39 00  67 00 39 00 67 00 39 00
00000010  *
00000080  67 00 3a 00 67 00 3a 00  67 00 3a 00 67 00 3a 00
00000090  *
00000100  67 00 3b 00 67 00 3b 00  67 00 3b 00 67 00 3b 00
00000110  *
000001c0  67 00 3c 00 67 00 3c 00  67 00 3c 00 67 00 3c 00
000001d0  *

UAPI

For now .. kind of works as-is?

(Assuming you don’t want more than 4GB.)

So.. Linux driver?

[8.086635] asahi 292400000.gpu: GPU: run_job
[8.087234] asahi 292400000.gpu: GPU: run_job: pipe_type=Compute
[8.087993] asahi 292400000.gpu: GPU: run_job: run()
[8.088669] asahi 292400000.gpu: Pipe: RunWorkQueueMsgG15V14_7 { timestamp: 0, work_queue:
  Some(0xfffffc0800267e80 (asahi::fw::workqueue::QueueInfoG15V14_7)), pipe_type: Compute,
  wptr: 1, event_slot: 0, is_new: true, __pad: <pad> }
[8.097272] asahi 292400000.gpu: GPU: run_job: ring doorbell
[8.098834] asahi 292400000.gpu: Stats: PowerOn { off_time: 0x1d604a9f }
[8.099680] asahi 292400000.gpu: Stats: PowerState { timestamp: 0x1d604a9f, last_busy_ts: 0x24edb549,
  active: 0, poweroff: 0, unk1: 0, pstate: 1, unk2: 100, unk3: 0 }
[8.101344] asahi: Unknown stats message: (15, [159, 74, 96, 29, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
[8.103587] asahi 292400000.gpu: Stats: PowerOff { on_time: 0x5a96 }

Status

🤔

March 2026

commit b5076d68c85a4ca1df054c1f9a7234b31a64fbf4
Date: Sat, 7 Mar 2026

    demo: it's metalover

commit a1006e523146717b4e75078a25c67389b41bf2ce
Date: Sat, 28 Mar 2026

    M3 agx test: compute shader runs

May-August 2026

😴 🫩🏃‍♀️💨 ⏳

Status: September

Status: coming soon™

Lies

[    6.101783] asahi 292400000.gpu: Stats: TempSensor { __pad: <pad>, sensor_id: 11, raw_value: 21346, scale: 8, tmin: 34, tmax: 41 }
[    6.103294] asahi 292400000.gpu: Event: Timeout { counter: 0, unk_8: 0, event_slot: 0 }
[    6.104420] asahi 292400000.gpu:  (\________/) 
[    6.105180] asahi 292400000.gpu:   |        |  
[    6.105964] asahi 292400000.gpu: '.| \  , / |.'
[    6.106741] asahi 292400000.gpu: --| / (( \ |--
[    6.107513] asahi 292400000.gpu: .'|  _-_-  |'.
[    6.108287] asahi 292400000.gpu:   |________|  
[    6.109060] asahi 292400000.gpu: ** GPU timeout nya~!!!!! **
[    6.109940] asahi 292400000.gpu:   Event slot: 0
[    6.110712] asahi 292400000.gpu:   Timeout count: 0
[    6.111514] asahi 292400000.gpu:   Unk: 0
[    6.112231] asahi 292400000.gpu:   Pending events:
[    6.113030] asahi 292400000.gpu:   Halt count: 0
[    6.113808] asahi 292400000.gpu:   Halted: 0

What about the M4?


MacOS 26.x running under m1n1’s hypervisor on an M4 mac mini

A word on AI

A word on AI

A word on AI

Thank you

We’d like to thank Vio for her contributions

(As well as all the Asahi folks)

Tank you

And the audience for listening

Let’s see something now

demo time!