From 341cd387681f51d241605375a9297ac71be010bc Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:11:47 -0400 Subject: [PATCH 01/16] arm64: dts: apple: share the T604x SoC and PMGR description Reuse the existing SoC and PMGR descriptions as the shared T604x base. The J616s compiled tree keeps every node and property unchanged. Assisted-by: LLM --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 2 +- .../arm64/boot/dts/apple/{t6040-pmgr.dtsi => t604x-pmgr.dtsi} | 0 arch/arm64/boot/dts/apple/{t6040.dtsi => t604x.dtsi} | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename arch/arm64/boot/dts/apple/{t6040-pmgr.dtsi => t604x-pmgr.dtsi} (100%) rename arch/arm64/boot/dts/apple/{t6040.dtsi => t604x.dtsi} (99%) diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 6c28c95f8155d..82562dec27db6 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -9,7 +9,7 @@ /dts-v1/; -#include "t6040.dtsi" +#include "t604x.dtsi" #include #include #include diff --git a/arch/arm64/boot/dts/apple/t6040-pmgr.dtsi b/arch/arm64/boot/dts/apple/t604x-pmgr.dtsi similarity index 100% rename from arch/arm64/boot/dts/apple/t6040-pmgr.dtsi rename to arch/arm64/boot/dts/apple/t604x-pmgr.dtsi diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t604x.dtsi similarity index 99% rename from arch/arm64/boot/dts/apple/t6040.dtsi rename to arch/arm64/boot/dts/apple/t604x.dtsi index 961af1b7b5bd4..d50888b229d75 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t604x.dtsi @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ OR MIT /* - * Apple T6040 "M4 Pro" SoC + * Apple T604x SoC base * * Unit addresses are the Apple Device Tree's /arm-io addresses plus the * 0x2_00000000 arm-io base. The nodes sit at the root, not under a @@ -2268,4 +2268,4 @@ }; -#include "t6040-pmgr.dtsi" +#include "t604x-pmgr.dtsi" From b8053763cc5c7e52fbc5581044d8de465380ed43 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:11:54 -0400 Subject: [PATCH 02/16] arm64: dts: apple: add the T6041 J616c board Describe the Max CPU, OPP and register-window differences on the shared base. Preserve the qualified J616c node and property values. Assisted-by: LLM Co-authored-by: Claude Opus 5.5 --- .../devicetree/bindings/gpu/apple,agx.yaml | 11 + arch/arm64/boot/dts/apple/Makefile | 1 + arch/arm64/boot/dts/apple/t6041-j616c.dts | 686 ++++++++++++++++++ arch/arm64/boot/dts/apple/t6041.dtsi | 555 ++++++++++++++ 4 files changed, 1253 insertions(+) create mode 100644 arch/arm64/boot/dts/apple/t6041-j616c.dts create mode 100644 arch/arm64/boot/dts/apple/t6041.dtsi diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index f5b1afd7d7dd3..ecada84f98f86 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -18,6 +18,7 @@ properties: - apple,agx-g14g - apple,agx-g14s - apple,agx-t6040 + - apple,agx-t6041 - apple,agx-t8132 - apple,agx-t8140 - items: @@ -307,6 +308,15 @@ allOf: apple,afr-leak-coef: minItems: 1 maxItems: 1 + - if: + properties: + compatible: + contains: + enum: + - apple,agx-t6040 + - apple,agx-t6041 + then: + properties: reg: items: - description: GFX coprocessor control registers @@ -354,6 +364,7 @@ allOf: contains: enum: - apple,agx-t6040 + - apple,agx-t6041 - apple,agx-t8132 - apple,agx-t8140 then: diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile index 0f6e6714f7de7..560c4219027b7 100644 --- a/arch/arm64/boot/dts/apple/Makefile +++ b/arch/arm64/boot/dts/apple/Makefile @@ -96,6 +96,7 @@ dtb-$(CONFIG_ARCH_APPLE) += t6034-j514m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6034-j516m.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s.dtb dtb-$(CONFIG_ARCH_APPLE) += t6040-j616s-tether.dtb +dtb-$(CONFIG_ARCH_APPLE) += t6041-j616c.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb diff --git a/arch/arm64/boot/dts/apple/t6041-j616c.dts b/arch/arm64/boot/dts/apple/t6041-j616c.dts new file mode 100644 index 0000000000000..61d14c1f9694c --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6041-j616c.dts @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +// Copyright The Asahi Linux Contributors + +/dts-v1/; + +#include "t6041.dtsi" +#include +#include + +/ { + compatible = "apple,j616c", "apple,t6041", "apple,arm-platform"; + model = "Apple MacBook Pro (16-inch, M4 Max, Nov 2024)"; + chassis-type = "laptop"; + + aliases { + serial0 = &serial0; + keyboard = &keyboard; + wifi0 = "/pcie@1cb0000000/pci@0,0/wifi@0,0"; + bluetooth0 = "/pcie@1cb0000000/pci@0,0/bluetooth@0,1"; + mtp = &mtp; + dcp = &dcp; + disp0 = &display; + disp0-piodma = &disp0_piodma; + gpu = &gpu; + }; + + chosen { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + stdout-path = "serial0:115200n8"; + + framebuffer0: framebuffer@0 { + compatible = "apple,simple-framebuffer", "simple-framebuffer"; + reg = <0 0 0 0>; + status = "disabled"; + }; + }; + + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + }; + + memory@0 { + device_type = "memory"; + reg = <0 0 0 0>; + }; + + serial0: serial@429200000 { + compatible = "apple,s5l-uart"; + reg = <0x4 0x29200000 0x0 0x1000>; + reg-io-width = <4>; + interrupt-parent = <&aic>; + interrupts = ; + clocks = <&clkref>, <&clkref>; + clock-names = "uart", "clk_uart_baud0"; + }; + + clk_disp0: clock-disp0 { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <712000000>; + clock-output-names = "disp0"; + }; + + thermal-zones { + cpu-die-thermal { + polling-delay-passive = <1000>; + polling-delay = <1000>; + thermal-sensors = <&smc_hwmon 0>; + + trips { + cpu_die_passive: cpu-die-passive { + temperature = <113000>; + hysteresis = <5000>; + type = "passive"; + }; + + cpu-die-critical { + temperature = <118000>; + hysteresis = <0>; + type = "critical"; + }; + }; + + cooling-maps { + map-e { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_e0 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e1 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e2 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_e3 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p0 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p00 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p01 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p02 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p03 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p04 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p05 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + + map-p1 { + trip = <&cpu_die_passive>; + cooling-device = + <&cpu_p10 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p11 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p12 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p13 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p14 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>, + <&cpu_p15 THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; + }; + }; + }; + }; +}; + +&cpufreq_e { + /delete-property/ apple,fast-die-effort; +}; + +&cpufreq_p0 { + /delete-property/ apple,fast-die-effort; +}; + +&cpufreq_p1 { + /delete-property/ apple,fast-die-effort; +}; + +&wdt { + timeout-sec = <140>; +}; + +&smc { + status = "okay"; + + smc_gpio_low: gpio-low { + compatible = "apple,smc-low-gpio"; + gpio-controller; + #gpio-cells = <2>; + }; +}; + +&smc_mbox { + status = "okay"; +}; + +&nub_spmi0 { + status = "okay"; +}; + +&smc_hwmon { + temperature-TCMz { + apple,key-id = "TCMz"; + label = "CPU Die Max"; + }; + temperature-TCMb { apple,key-id = "TCMb"; label = "CPU TCMb"; }; + temperature-Tpx0 { apple,key-id = "Tpx0"; label = "CPU Tpx0"; }; + temperature-Tpx1 { apple,key-id = "Tpx1"; label = "CPU Tpx1"; }; + temperature-Tpx2 { apple,key-id = "Tpx2"; label = "CPU Tpx2"; }; + temperature-Tpx3 { apple,key-id = "Tpx3"; label = "CPU Tpx3"; }; + temperature-Tpx4 { apple,key-id = "Tpx4"; label = "CPU Tpx4"; }; + temperature-Tpx5 { apple,key-id = "Tpx5"; label = "CPU Tpx5"; }; + temperature-Tpx8 { apple,key-id = "Tpx8"; label = "CPU Tpx8"; }; + temperature-Tpx9 { apple,key-id = "Tpx9"; label = "CPU Tpx9"; }; + temperature-TpxA { apple,key-id = "TpxA"; label = "CPU TpxA"; }; + temperature-TpxB { apple,key-id = "TpxB"; label = "CPU TpxB"; }; + temperature-TpxC { apple,key-id = "TpxC"; label = "CPU TpxC"; }; + temperature-TpxD { apple,key-id = "TpxD"; label = "CPU TpxD"; }; + temperature-Tex0 { apple,key-id = "Tex0"; label = "CPU Tex0"; }; + temperature-Tex1 { apple,key-id = "Tex1"; label = "CPU Tex1"; }; + temperature-Tex2 { apple,key-id = "Tex2"; label = "CPU Tex2"; }; + temperature-Tex3 { apple,key-id = "Tex3"; label = "CPU Tex3"; }; + temperature-TH0x { apple,key-id = "TH0x"; label = "NAND Flash Temperature"; }; + temperature-TB0T { apple,key-id = "TB0T"; label = "Battery Hotspot"; }; + temperature-TCHP { apple,key-id = "TCHP"; label = "Charge Regulator Temp"; }; + temperature-TW0P { apple,key-id = "TW0P"; label = "WiFi/BT Module Temp"; }; + power-PSTR { apple,key-id = "PSTR"; label = "Total System Power"; }; + power-PHPC { apple,key-id = "PHPC"; label = "Heatpipe Power"; }; + power-PDTR { apple,key-id = "PDTR"; label = "AC Input Power"; }; + voltage-VD0R { apple,key-id = "VD0R"; label = "AC Input Voltage"; }; + voltage-SBAV { apple,key-id = "SBAV"; label = "Battery Voltage"; }; + current-ID0R { apple,key-id = "ID0R"; label = "AC Input Current"; }; +}; + +&pinctrl { + status = "okay"; + + wifi_clkreq_pins: wifi-clkreq-pins { + pinmux = ; + }; +}; + +&pinctrl_nub { + status = "okay"; + /delete-property/ interrupt-controller; + /delete-property/ #interrupt-cells; + /delete-property/ interrupts; +}; + +&mtp { + status = "okay"; +}; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + compatible = "apple,dockchannel-hid"; + apple,afe-reset-gpios = <&smc_gpio_low 25 GPIO_ACTIVE_LOW>; + apple,stm-reset-gpios = <&smc_gpio_low 26 GPIO_ACTIVE_LOW>; + + multi-touch { + firmware-name = "apple/tpmtfw-j616c.bin"; + apple,power-method = <2>; + }; + + keyboard: keyboard { + hid-country-code = <0>; + apple,keyboard-layout-id = <0>; + }; + + stm { + }; + + actuator { + }; + + tp-accel { + }; + + mtp { + }; +}; + +&pcie_dart { + status = "okay"; +}; + +/ { + pcie0: pcie@1cb0000000 { + compatible = "apple,t6041-pcie", "apple,t6020-pcie"; + reg = <0x1c 0xb0000000 0x0 0x1000000>, + <0x4 0x14000000 0x0 0x4000>, + <0x4 0x10028000 0x0 0x4000>, + <0x4 0x11028000 0x0 0x4000>, + <0x4 0x12028000 0x0 0x4000>, + <0x4 0x13028000 0x0 0x4000>, + <0x4 0x17020000 0x0 0x4000>, + <0x4 0x17024000 0x0 0x4000>, + <0x4 0x17028000 0x0 0x4000>, + <0x4 0x1702c000 0x0 0x4000>; + reg-names = "config", "rc", + "port0", "port1", "port2", "port3", + "phy0", "phy1", "phy2", "phy3"; + ranges = <0x43000000 0xb 0xc0000000 0xb 0xc0000000 0 0x20000000 0x2000000 0 0x80000000 0xb 0x80000000 0 0x40000000>; + pinctrl-names = "default"; + pinctrl-0 = <&wifi_clkreq_pins>; + #size-cells = <2>; + #address-cells = <3>; + bus-range = <0 1>; + iommu-map-mask = <0xffff>; + iommu-map = <0x100 &pcie_dart 1 2>; + msi-ranges = <&aic AIC_IRQ 2071 IRQ_TYPE_EDGE_RISING 32>; + msi-parent = <&pcie0>; + msi-controller; + interrupts = <0 0x6bb 4>; + interrupt-parent = <&aic>; + device_type = "pci"; + + port00: pci@0,0 { + compatible = "pciclass,0604"; + reg = <0 0 0 0 0>; + ranges; + interrupt-map = <0 0 0 1 &port00 0 0 0 0 0 0 0 2 &port00 0 0 0 1 0 0 0 3 &port00 0 0 0 2 0 0 0 4 &port00 0 0 0 3>; + interrupt-map-mask = <0 0 0 7>; + #interrupt-cells = <1>; + interrupt-controller; + #size-cells = <2>; + #address-cells = <3>; + pwren-gpios = <&smc_gpio 0x13 0>; + reset-gpios = <&pinctrl 4 1>; + device_type = "pci"; + + bluetooth@0,1 { + compatible = "pci14e4,5f72"; + reg = <0x100 0 0 0 0>; + local-bd-address = [00 00 00 00 00 00]; + brcm,board-type = "apple,hostomel"; + status = "okay"; + }; + + wifi@0,0 { + compatible = "brcm,bcm4329-fmac"; + reg = <0 0 0 0 0>; + brcm,ccode-map-trivial; + brcm,board-type = "apple,hostomel"; + }; + }; + }; +}; + +&ans_mbox { + status = "okay"; +}; + +&ans_sart { + status = "okay"; +}; + +&nvme { + status = "okay"; +}; + +&display { + phandle = <&display>; + status = "okay"; +}; + +&dcp { + clocks = <&clk_disp0>; + phandle = <&dcp>; + + panel { + compatible = "apple,panel-j616", "apple,panel-mini-led", "apple,panel"; + width-mm = <346>; + height-mm = <223>; + adj-height-mm = <216>; + apple,max-brightness = <500>; + }; +}; + +&disp0_piodma { + phandle = <&disp0_piodma>; +}; + +&nub_spmi_a0 { + status = "okay"; + + hpm1: usb-pd@a { + compatible = "apple,sn201202x"; + reg = <0xa SPMI_USID>; + interrupts = <37 IRQ_TYPE_EDGE_RISING>, + <39 IRQ_TYPE_EDGE_RISING>, + <43 IRQ_TYPE_EDGE_RISING>, + <45 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + typec1: connector { + compatible = "usb-c-connector"; + label = "USB-C Left Front"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec1_con_hs: endpoint { + remote-endpoint = <&usb_drd1_hs>; + }; + }; + }; + }; + }; +}; + +&nub_spmi_a1 { + status = "okay"; + + hpm2: usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + interrupts = <11 IRQ_TYPE_EDGE_RISING>, + <13 IRQ_TYPE_EDGE_RISING>, + <17 IRQ_TYPE_EDGE_RISING>, + <19 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + typec2: connector { + compatible = "usb-c-connector"; + label = "USB-C Right"; + power-role = "dual"; + data-role = "dual"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + typec2_con_hs: endpoint { + remote-endpoint = <&usb_drd2_hs>; + }; + }; + }; + }; + }; +}; + +&atcphy1 { + apple,tunable-usb2-host = <0x8 0x7003 0x3>; + status = "okay"; +}; + +&usb_drd1_dart_0 { + status = "okay"; +}; + +&usb_drd1_dart_1 { + status = "okay"; +}; + +&usb_drd1 { + maximum-speed = "high-speed"; + phys = <&atcphy1 PHY_TYPE_USB2>; + phy-names = "usb2-phy"; + apple,tunable-gbl-host = <0x4 0x0001ef00 0x00002f00>, <0x1c 0x00000100 0x0>, + <0x2c 0x000a07ff 0x000800c8>, <0x100 0x02000100 0x02000100>, + <0x504 0x00001000 0x0>, <0x528 0xff0000ff 0x06000006>, + <0x52c 0xff0000ff 0x08000008>, <0x538 0x00000800 0x00000800>, + <0x580 0xffffffff 0x6f000006>; + apple,tunable-cfg-host = <0x20 0xff 0x1b>; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd1_hs: endpoint { + remote-endpoint = <&typec1_con_hs>; + }; + }; + }; +}; + +&atcphy2 { + apple,tunable-usb2-host = <0x8 0x7003 0x3>; + status = "okay"; +}; + +&usb_drd2_dart_0 { + status = "okay"; +}; + +&usb_drd2_dart_1 { + status = "okay"; +}; + +&usb_drd2 { + maximum-speed = "high-speed"; + phys = <&atcphy2 PHY_TYPE_USB2>; + phy-names = "usb2-phy"; + apple,tunable-gbl-host = <0x4 0x0001ef00 0x00002f00>, <0x1c 0x00000100 0x0>, + <0x2c 0x000a07ff 0x000800c8>, <0x100 0x02000100 0x02000100>, + <0x504 0x00001000 0x0>, <0x528 0xff0000ff 0x06000006>, + <0x52c 0xff0000ff 0x08000008>, <0x538 0x00000800 0x00000800>, + <0x580 0xffffffff 0x6f000006>; + apple,tunable-cfg-host = <0x20 0xff 0x1b>; + status = "okay"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + usb_drd2_hs: endpoint { + remote-endpoint = <&typec2_con_hs>; + }; + }; + }; +}; + +/ { + reserved-memory { + #address-cells = <2>; + #size-cells = <2>; + ranges; + + gpu_uat_ttbs: uat-ttbs { + no-map; + status = "disabled"; + }; + + gpu_uat_pagetables: uat-pagetables { + no-map; + status = "disabled"; + }; + + gpu_uat_l2_pagetables: uat-l2-pagetables { + no-map; + status = "disabled"; + }; + + gpu_uat_handoff: uat-handoff { + no-map; + status = "disabled"; + }; + + gpu_fw_text: gpu-fw-text { + no-map; + status = "disabled"; + }; + + gpu_fw_data: gpu-fw-data { + no-map; + status = "disabled"; + }; + }; + + gpu_opp: opp-table-gpu { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <0>; + opp-microvolt = <125000 125000 125000 125000>; + }; + opp01 { + opp-hz = /bits/ 64 <338000000>; + opp-microvolt = <600000 600000 600000 600000>; + }; + opp02 { + opp-hz = /bits/ 64 <618000000>; + opp-microvolt = <665000 665000 660000 660000>; + }; + opp03 { + opp-hz = /bits/ 64 <796000000>; + opp-microvolt = <705000 705000 700000 705000>; + }; + opp04 { + opp-hz = /bits/ 64 <924000000>; + opp-microvolt = <725000 725000 715000 730000>; + }; + opp05 { + opp-hz = /bits/ 64 <952000000>; + opp-microvolt = <770000 770000 770000 780000>; + }; + opp06 { + opp-hz = /bits/ 64 <1056000000>; + opp-microvolt = <770000 770000 770000 780000>; + }; + opp07 { + opp-hz = /bits/ 64 <1062000000>; + opp-microvolt = <810000 810000 805000 810000>; + }; + opp08 { + opp-hz = /bits/ 64 <1182000000>; + opp-microvolt = <810000 810000 805000 810000>; + }; + opp09 { + opp-hz = /bits/ 64 <1182000000>; + opp-microvolt = <860000 860000 860000 860000>; + }; + opp10 { + opp-hz = /bits/ 64 <1312000000>; + opp-microvolt = <860000 860000 860000 860000>; + }; + opp11 { + opp-hz = /bits/ 64 <1242000000>; + opp-microvolt = <900000 895000 900000 900000>; + }; + opp12 { + opp-hz = /bits/ 64 <1380000000>; + opp-microvolt = <900000 895000 900000 900000>; + }; + opp13 { + opp-hz = /bits/ 64 <1326000000>; + opp-microvolt = <940000 925000 940000 940000>; + }; + opp14 { + opp-hz = /bits/ 64 <1470000000>; + opp-microvolt = <940000 925000 940000 940000>; + }; + opp15 { + opp-hz = /bits/ 64 <1578000000>; + opp-microvolt = <1020000 1020000 1020000 1020000>; + }; + }; + + gpu_cs_opp: opp-table-gpucs { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <24000000>; + opp-microvolt = <585000>; + }; + opp01 { + opp-hz = /bits/ 64 <338000000>; + opp-microvolt = <585000>; + }; + opp02 { + opp-hz = /bits/ 64 <618000000>; + opp-microvolt = <635000>; + }; + opp03 { + opp-hz = /bits/ 64 <796000000>; + opp-microvolt = <685000>; + }; + opp04 { + opp-hz = /bits/ 64 <992000000>; + opp-microvolt = <720000>; + }; + opp05 { + opp-hz = /bits/ 64 <1188000000>; + opp-microvolt = <770000>; + }; + opp06 { + opp-hz = /bits/ 64 <1380000000>; + opp-microvolt = <855000>; + }; + opp07 { + opp-hz = /bits/ 64 <1470000000>; + opp-microvolt = <885000>; + }; + }; + + gpu_afr_opp: opp-table-gpuafr { + compatible = "operating-points-v2"; + + opp00 { + opp-hz = /bits/ 64 <24000000>; + opp-microvolt = <585000>; + }; + opp01 { + opp-hz = /bits/ 64 <338000000>; + opp-microvolt = <585000>; + }; + opp02 { + opp-hz = /bits/ 64 <618000000>; + opp-microvolt = <635000>; + }; + opp03 { + opp-hz = /bits/ 64 <796000000>; + opp-microvolt = <685000>; + }; + opp04 { + opp-hz = /bits/ 64 <992000000>; + opp-microvolt = <720000>; + }; + opp05 { + opp-hz = /bits/ 64 <1188000000>; + opp-microvolt = <770000>; + }; + opp06 { + opp-hz = /bits/ 64 <1380000000>; + opp-microvolt = <855000>; + }; + opp07 { + opp-hz = /bits/ 64 <1470000000>; + opp-microvolt = <885000>; + }; + }; +}; + +&gpu { + memory-region = <&gpu_uat_ttbs>, <&gpu_uat_pagetables>, + <&gpu_uat_handoff>, <&gpu_fw_data>, + <&gpu_uat_l2_pagetables>, <&gpu_fw_text>; + memory-region-names = "ttbs", "pagetables", "handoff", "fw-data", + "l2-pagetables", "fw-text"; + operating-points-v2 = <&gpu_opp>; + apple,cs-opp = <&gpu_cs_opp>; + apple,afr-opp = <&gpu_afr_opp>; + apple,firmware-version = <26 5 2>; +}; diff --git a/arch/arm64/boot/dts/apple/t6041.dtsi b/arch/arm64/boot/dts/apple/t6041.dtsi new file mode 100644 index 0000000000000..f185e7db4d5d3 --- /dev/null +++ b/arch/arm64/boot/dts/apple/t6041.dtsi @@ -0,0 +1,555 @@ +// SPDX-License-Identifier: GPL-2.0+ OR MIT +// Copyright The Asahi Linux Contributors + +#include "t604x.dtsi" + + +/* Keep the validated T6041 topology independent of unqualified T6040 blocks. */ +/delete-node/ &ane; +/delete-node/ &clk_200m; +/delete-node/ &sio; +/delete-node/ &ave0_dart1; +/delete-node/ &ave0_dart0; +/delete-node/ &apr0_dart; +/delete-node/ &usb4_0_dart; +/delete-node/ &apciec0_dart; +/delete-node/ &usb4_1_dart; +/delete-node/ &apciec1_dart; +/delete-node/ &usb4_2_dart; +/delete-node/ &apciec2_dart; +/delete-node/ &dispext0_dart; +/delete-node/ &dcpext0_dart; +/delete-node/ &sep_dart; +/delete-node/ &apcie1_dart; +/delete-node/ &sio_dart; +/delete-node/ &ane0_dart0; +/delete-node/ &ane0_dart1; +/delete-node/ &ane0_dart2; +/delete-node/ &jpeg0_dart; +/delete-node/ &jpeg1_dart; +/delete-node/ &scaler0_dart; +/delete-node/ &dispext1_dart; +/delete-node/ &dcpext1_dart; +/delete-node/ &usb4_0_mbox; +/delete-node/ &usb4_1_mbox; +/delete-node/ &usb4_2_mbox; +/delete-node/ &sep_mbox; +/delete-node/ &sio_mbox; +/delete-node/ &pinctrl_smc; +/delete-node/ &pinctrl_aop; +/delete-node/ &ps_gpx; +/delete-node/ &ps_apcie_gp; +/delete-node/ &ps_fab4_soc; +/delete-node/ &ps_sio_cpu; +/delete-node/ &ps_fpwm1; +/delete-node/ &ps_fpwm2; +/delete-node/ &ps_i2c0; +/delete-node/ &ps_i2c5; +/delete-node/ &ps_i2c7; +/delete-node/ &ps_spi_p; +/delete-node/ &ps_uart_p; +/delete-node/ &ps_dpa_p; +/delete-node/ &ps_aes; +/delete-node/ &ps_spi0; +/delete-node/ &ps_spi1; +/delete-node/ &ps_spi2; +/delete-node/ &ps_spi3; +/delete-node/ &ps_spi4; +/delete-node/ &ps_spi5; +/delete-node/ &ps_qspi; +/delete-node/ &ps_uart_n; +/delete-node/ &ps_uart0; +/delete-node/ &ps_uart1; +/delete-node/ &ps_uart2; +/delete-node/ &ps_uart3; +/delete-node/ &ps_uart4; +/delete-node/ &ps_uart5; +/delete-node/ &ps_uart6; +/delete-node/ &ps_dpa0; +/delete-node/ &ps_dpa1; +/delete-node/ &ps_dpa2; +/delete-node/ &ps_dpa3; +/delete-node/ &ps_dpa4; +/delete-node/ &ps_disp_sys; +/delete-node/ &ps_amcc4; +/delete-node/ &ps_amcc5; +/delete-node/ &ps_amcc6; +/delete-node/ &ps_amcc7; +/delete-node/ &ps_amcc12; +/delete-node/ &ps_amcc13; +/delete-node/ &ps_amcc14; +/delete-node/ &ps_amcc15; +/delete-node/ &ps_dcs_04; +/delete-node/ &ps_dcs_05; +/delete-node/ &ps_dcs_06; +/delete-node/ &ps_dcs_07; +/delete-node/ &ps_dcs_12; +/delete-node/ &ps_dcs_13; +/delete-node/ &ps_dcs_14; +/delete-node/ &ps_dcs_15; +/delete-node/ &ps_disp_fe; +/delete-node/ &ps_apcie_sys_gp; +/delete-node/ &ps_apcie_phy_sw; +/delete-node/ &ps_jpg; +/delete-node/ &ps_ane_sys; +/delete-node/ &ps_sep; +/delete-node/ &pmgr_ane; +/delete-node/ &pmgr_disp; +/delete-node/ &ps_ecpu_0; +/delete-node/ &ps_ecpu_2; +/delete-node/ &ps_dwi; +/delete-node/ &ps_aic; +/delete-node/ &ps_pmc; +/delete-node/ &ps_pms_c1ppt; +/delete-node/ &ps_soc_dpe; +/delete-node/ &ps_pmgr_soc_ocla; +/delete-node/ &ps_pms_fpwm0; +/delete-node/ &ps_pms_fpwm1; +/delete-node/ &ps_pms_fpwm2; +/delete-node/ &ps_pms_fpwm3; +/delete-node/ &ps_pms_fpwm4; +/delete-node/ &ps_fab0_soc; +/delete-node/ &ps_ecpu_3; +/delete-node/ &ps_amcc0; +/delete-node/ &ps_dcs_00; +/delete-node/ &ps_rom; +/delete-node/ &ps_amcc1; +/delete-node/ &ps_dcs_01; +/delete-node/ &ps_amcc2; +/delete-node/ &ps_dcs_02; +/delete-node/ &ps_amcc3; +/delete-node/ &ps_dcs_03; +/delete-node/ &ps_amcc8; +/delete-node/ &ps_dcs_08; +/delete-node/ &ps_amcc9; +/delete-node/ &ps_dcs_09; +/delete-node/ &ps_amcc10; +/delete-node/ &ps_dcs_10; +/delete-node/ &ps_amcc11; +/delete-node/ &ps_pcpu0_0; +/delete-node/ &ps_dcs_11; +/delete-node/ &ps_venc0_sys; +/delete-node/ &ps_pcpu0_1; +/delete-node/ &ps_fab_gw_0; +/delete-node/ &ps_fab_gw_1; +/delete-node/ &ps_fab_afr; +/delete-node/ &ps_fab1_soc; +/delete-node/ &ps_pcpu0_2; +/delete-node/ &ps_scodec; +/delete-node/ &ps_scodec_streaming; +/delete-node/ &ps_fab2_soc; +/delete-node/ &ps_dispext0_sys; +/delete-node/ &ps_dispext0_fe; +/delete-node/ &ps_dispext0_cpu; +/delete-node/ &ps_pcpu0_3; +/delete-node/ &ps_atc0_cio; +/delete-node/ &ps_atc0_pcie; +/delete-node/ &ps_atc0_cio_pcie; +/delete-node/ &ps_atc0_cio_usb; +/delete-node/ &ps_atc1_cio; +/delete-node/ &ps_atc1_pcie; +/delete-node/ &ps_atc1_cio_pcie; +/delete-node/ &ps_atc1_cio_usb; +/delete-node/ &ps_atc2_cio; +/delete-node/ &ps_atc2_pcie; +/delete-node/ &ps_atc2_cio_pcie; +/delete-node/ &ps_pcpu0_4; +/delete-node/ &ps_atc2_cio_usb; +/delete-node/ &ps_atc3_common; +/delete-node/ &ps_atc3_cio; +/delete-node/ &ps_atc3_pcie; +/delete-node/ &ps_atc3_cio_pcie; +/delete-node/ &ps_atc3_cio_usb; +/delete-node/ &ps_fab5_soc; +/delete-node/ &ps_sbr; +/delete-node/ &ps_gpio; +/delete-node/ &ps_msg; +/delete-node/ &ps_ap_tmm; +/delete-node/ &ps_msr0; +/delete-node/ &ps_msr0_ase_core; +/delete-node/ &ps_pcpu0_5; +/delete-node/ &ps_fab7_soc; +/delete-node/ &ps_dispext1_sys; +/delete-node/ &ps_dispext1_fe; +/delete-node/ &ps_dispext1_cpu; +/delete-node/ &ps_pcpu1_0; +/delete-node/ &ps_pcpu1_1; +/delete-node/ &ps_pcpu1_2; +/delete-node/ &ps_pcpu1_3; +/delete-node/ &ps_pcpu1_4; +/delete-node/ &ps_pcpu1_5; +/delete-node/ &ps_ecpu_1; +/delete-node/ &ps_ecpm; +/delete-node/ &ps_pcpm0; +/delete-node/ &ps_pcpm1; +/delete-node/ &pmgr_venc; +/delete-node/ &ps_debug_gated; +/delete-node/ &ps_nub_fabric; +/delete-node/ &ps_nub_sram; +/delete-node/ &ps_debug_switch; +/delete-node/ &ps_atc3_usb; +/delete-node/ &ps_atc3_phymxwrap; +/delete-node/ &ps_nub_spmi_ahb; +/delete-node/ &ps_nub_spmi0; +/delete-node/ &ps_nub_spmi_a2; +/delete-node/ &ps_nub_spi0; +/delete-node/ &ps_nub_aon; +/delete-node/ &ps_nub_ocla; +/delete-node/ &ps_atc3_common_dp; +/delete-node/ &ps_nub_spmi1; +/delete-node/ &ps_atc3_usb_aon; +/delete-node/ &ps_nub_spmi2; +/delete-node/ &ps_nub_spmi3; +/delete-node/ &ps_nub_spmi4; +/delete-node/ &sep; +/delete-node/ &dcuart; +/delete-node/ &spi2; +/delete-node/ &spi4; +/delete-node/ &qspi; +/delete-node/ &nub_spmi1; +/delete-node/ &nub_spmi2; +/delete-node/ &nub_spmi3; +/delete-node/ &nub_spmi4; +/delete-node/ &aop_spmi0; +/delete-node/ &ave; + +&ps_avd_sys { + /delete-property/ power-domains; +}; + +&ps_atc0_common { + /delete-property/ power-domains; +}; + +&ps_atc1_common { + /delete-property/ power-domains; +}; + +&ps_atc2_common { + /delete-property/ power-domains; +}; + +&ps_pmp { + /delete-property/ power-domains; +}; + +&ps_pms_sram { + /delete-property/ power-domains; +}; + +&serial0 { + /delete-property/ power-domains; + /delete-property/ status; +}; + +/delete-node/ &gpu; +/delete-node/ &gfx_mbox; +/delete-node/ &opp_p0; +/delete-node/ &opp_p1; + +/ { + cpus { + cpu-map { + cluster1 { core5 { cpu = <&cpu_p05>; }; }; + cluster2 { core5 { cpu = <&cpu_p15>; }; }; + }; + cpu_p05: cpu@10105 { + compatible = "apple,brava-p"; + device_type = "cpu"; + reg = <0x0 0x10105>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; + next-level-cache = <&l2_cache_p0>; + operating-points-v2 = <&opp_p0>; + capacity-dmips-mhz = <1024>; + performance-domains = <&cpufreq_p0>; + #cooling-cells = <2>; + }; + cpu_p15: cpu@10205 { + compatible = "apple,brava-p"; + device_type = "cpu"; + reg = <0x0 0x10205>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; + next-level-cache = <&l2_cache_p1>; + operating-points-v2 = <&opp_p1>; + capacity-dmips-mhz = <1024>; + performance-domains = <&cpufreq_p1>; + #cooling-cells = <2>; + }; + }; + + opp_p0: opp-table-p0 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <1260000000>; + opp-level = <1>; + clock-latency-ns = <7200>; + }; + opp02 { + opp-hz = /bits/ 64 <1512000000>; + opp-level = <2>; + clock-latency-ns = <19000>; + }; + opp03 { + opp-hz = /bits/ 64 <1800000000>; + opp-level = <3>; + clock-latency-ns = <20000>; + }; + opp04 { + opp-hz = /bits/ 64 <2088000000>; + opp-level = <4>; + clock-latency-ns = <22000>; + }; + opp05 { + opp-hz = /bits/ 64 <2352000000>; + opp-level = <5>; + clock-latency-ns = <30000>; + }; + opp06 { + opp-hz = /bits/ 64 <2616000000>; + opp-level = <6>; + clock-latency-ns = <34000>; + }; + opp07 { + opp-hz = /bits/ 64 <2868000000>; + opp-level = <7>; + clock-latency-ns = <37000>; + }; + opp08 { + opp-hz = /bits/ 64 <3096000000>; + opp-level = <8>; + clock-latency-ns = <38000>; + }; + opp09 { + opp-hz = /bits/ 64 <3300000000>; + opp-level = <9>; + clock-latency-ns = <40000>; + }; + opp10 { + opp-hz = /bits/ 64 <3468000000>; + opp-level = <10>; + clock-latency-ns = <41000>; + }; + opp11 { + opp-hz = /bits/ 64 <3624000000>; + opp-level = <11>; + clock-latency-ns = <43000>; + }; + opp12 { + opp-hz = /bits/ 64 <3756000000>; + opp-level = <12>; + clock-latency-ns = <44000>; + }; + opp13 { + opp-hz = /bits/ 64 <3828000000>; + opp-level = <13>; + clock-latency-ns = <45000>; + }; + opp14 { + opp-hz = /bits/ 64 <3888000000>; + opp-level = <14>; + clock-latency-ns = <46000>; + }; + opp15 { + opp-hz = /bits/ 64 <3948000000>; + opp-level = <15>; + clock-latency-ns = <47000>; + }; + opp16 { + opp-hz = /bits/ 64 <3996000000>; + opp-level = <16>; + clock-latency-ns = <48000>; + }; + opp17 { + opp-hz = /bits/ 64 <4044000000>; + opp-level = <17>; + clock-latency-ns = <49000>; + }; + opp18 { + opp-hz = /bits/ 64 <4104000000>; + opp-level = <18>; + clock-latency-ns = <50000>; + }; + opp19 { + opp-hz = /bits/ 64 <4416000000>; + opp-level = <19>; + clock-latency-ns = <55000>; + turbo-mode; + }; + opp20 { + opp-hz = /bits/ 64 <4512000000>; + opp-level = <20>; + clock-latency-ns = <56000>; + turbo-mode; + }; + }; + + opp_p1: opp-table-p1 { + compatible = "operating-points-v2"; + opp-shared; + + opp01 { + opp-hz = /bits/ 64 <1260000000>; + opp-level = <1>; + clock-latency-ns = <7200>; + }; + opp02 { + opp-hz = /bits/ 64 <1512000000>; + opp-level = <2>; + clock-latency-ns = <19000>; + }; + opp03 { + opp-hz = /bits/ 64 <1800000000>; + opp-level = <3>; + clock-latency-ns = <20000>; + }; + opp04 { + opp-hz = /bits/ 64 <2088000000>; + opp-level = <4>; + clock-latency-ns = <22000>; + }; + opp05 { + opp-hz = /bits/ 64 <2352000000>; + opp-level = <5>; + clock-latency-ns = <30000>; + }; + opp06 { + opp-hz = /bits/ 64 <2616000000>; + opp-level = <6>; + clock-latency-ns = <34000>; + }; + opp07 { + opp-hz = /bits/ 64 <2868000000>; + opp-level = <7>; + clock-latency-ns = <37000>; + }; + opp08 { + opp-hz = /bits/ 64 <3096000000>; + opp-level = <8>; + clock-latency-ns = <38000>; + }; + opp09 { + opp-hz = /bits/ 64 <3300000000>; + opp-level = <9>; + clock-latency-ns = <40000>; + }; + opp10 { + opp-hz = /bits/ 64 <3468000000>; + opp-level = <10>; + clock-latency-ns = <41000>; + }; + opp11 { + opp-hz = /bits/ 64 <3624000000>; + opp-level = <11>; + clock-latency-ns = <43000>; + }; + opp12 { + opp-hz = /bits/ 64 <3756000000>; + opp-level = <12>; + clock-latency-ns = <44000>; + }; + opp13 { + opp-hz = /bits/ 64 <3828000000>; + opp-level = <13>; + clock-latency-ns = <45000>; + }; + opp14 { + opp-hz = /bits/ 64 <3888000000>; + opp-level = <14>; + clock-latency-ns = <46000>; + }; + opp15 { + opp-hz = /bits/ 64 <3948000000>; + opp-level = <15>; + clock-latency-ns = <47000>; + }; + opp16 { + opp-hz = /bits/ 64 <3996000000>; + opp-level = <16>; + clock-latency-ns = <48000>; + }; + opp17 { + opp-hz = /bits/ 64 <4044000000>; + opp-level = <17>; + clock-latency-ns = <49000>; + }; + opp18 { + opp-hz = /bits/ 64 <4104000000>; + opp-level = <18>; + clock-latency-ns = <50000>; + }; + opp19 { + opp-hz = /bits/ 64 <4416000000>; + opp-level = <19>; + clock-latency-ns = <55000>; + turbo-mode; + }; + opp20 { + opp-hz = /bits/ 64 <4512000000>; + opp-level = <20>; + clock-latency-ns = <56000>; + turbo-mode; + }; + }; + +}; + +&cpu_p00 { + enable-method = "spin-table"; + cpu-release-addr = <0 0>; +}; + +&aic { + reg = <0x5 0x02400000 0x0 0x1cc000>, + <0x5 0x02440000 0x0 0x4>; +}; + +&smc { + reg = <0x5 0x0c600000 0x0 0x4000>, + <0x5 0x0de00000 0x0 0x170000>; +}; + +&pmc { + /delete-property/ apple,pmp; + apple,static-voters = <1 3>; +}; + +/ { + gpu: gpu@288000000 { + compatible = "apple,agx-t6041"; + reg = <0x2 0x8a600000 0x0 0x88000>, + <0x2 0x88000000 0x0 0x3758000>; + reg-names = "asc", "sgx"; + mboxes = <&gfx_mbox>; + power-domains = <&ps_gfx>; + status = "disabled"; + }; + + gfx_mbox: mailbox@28a600000 { + compatible = "apple,t6041-ascwrap-v6", "apple,t6040-ascwrap-v6"; + reg = <0x2 0x8a600000 0x0 0x88000>, + <0x2 0x8a050000 0x0 0x8>; + reg-names = "wrapper", "iop-vbar"; + interrupt-parent = <&aic>; + interrupts = , + ; + interrupt-names = "send-empty", "recv-not-empty"; + apple,firmware-role = "GFX"; + power-domains = <&ps_gfx>; + #mbox-cells = <0>; + status = "disabled"; + }; +}; + +&ps_afr { + apple,always-on; + apple,no-auto-pm; +}; + +&ps_gfx { + apple,always-on; + apple,no-auto-pm; +}; From 553968e50b0276404b1ee89b9e5e61992fb7b439 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:12:01 -0400 Subject: [PATCH 03/16] drm/asahi: share T604x register-window constructors Move identical register-window constructors into a family helper. The T6040 and T6041 initialization-data checks retain their exact values. Assisted-by: LLM --- drivers/gpu/drm/asahi/hw/mod.rs | 1 + drivers/gpu/drm/asahi/hw/t6040.rs | 33 +------------------------------ drivers/gpu/drm/asahi/hw/t604x.rs | 33 +++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 drivers/gpu/drm/asahi/hw/t604x.rs diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs index 2650131a4a293..5149f87a4ea1d 100644 --- a/drivers/gpu/drm/asahi/hw/mod.rs +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -14,6 +14,7 @@ const MAX_POWERZONES: usize = 5; pub(crate) mod t600x; pub(crate) mod t602x; pub(crate) mod t6040; +mod t604x; pub(crate) mod t8103; pub(crate) mod t8112; pub(crate) mod t8140; diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 11ed37a7c1ef8..4c36c592dccba 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -11,6 +11,7 @@ use crate::g17::fw::{ }; use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; use crate::hw::{GpuGen, GpuVariant}; +use super::t604x::{instances, window}; use crate::mmu::UatQuirks; /// Static configuration of the T6040 GPU. @@ -110,38 +111,6 @@ pub(crate) const CONFIG: Config = Config { /// Chip identifier written to the hardware data. pub(crate) const CHIP_ID: u32 = 0x6040; -/// Base of the firmware MMIO aperture in the kernel window. -const MMIO_APERTURE: u64 = 0xffff_fc21_8000_0000; - -const fn window( - slot: usize, - phys: u64, - offset: u64, - size: u32, - unk_18: u64, - unk_20: u32, -) -> RegisterWindow { - RegisterWindow { - slot, - phys, - va: MMIO_APERTURE + offset, - size, - unk_18, - unk_20, - instances: 1, - phys_stride: 0, - } -} - -/// `window` repeated `instances` times, `phys_stride` apart in physical memory. -const fn instances(window: RegisterWindow, instances: u32, phys_stride: u64) -> RegisterWindow { - RegisterWindow { - instances, - phys_stride, - ..window - } -} - /// Register windows the firmware maps, by register-map slot. Slot 3 holds the four memory /// controllers (ADT /arm-io/mcc), slot 20 the four AFR blocks. pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ diff --git a/drivers/gpu/drm/asahi/hw/t604x.rs b/drivers/gpu/drm/asahi/hw/t604x.rs new file mode 100644 index 0000000000000..961cef76cf4aa --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t604x.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +use crate::g17::fw::initdata::RegisterWindow; + +const MMIO_APERTURE: u64 = 0xffff_fc21_8000_0000; + +pub(super) const fn window( + slot: usize, + phys: u64, + offset: u64, + size: u32, + unk_18: u64, + unk_20: u32, +) -> RegisterWindow { + RegisterWindow { + slot, + phys, + va: MMIO_APERTURE + offset, + size, + unk_18, + unk_20, + instances: 1, + phys_stride: 0, + } +} + +pub(super) const fn instances(window: RegisterWindow, instances: u32, phys_stride: u64) -> RegisterWindow { + RegisterWindow { + instances, + phys_stride, + ..window + } +} From 3ae8de479f8f37685f1c766b1b602592c1dc1df7 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:12:09 -0400 Subject: [PATCH 04/16] drm/asahi: add T6041 and the measured 25F84 firmware profile Select a static 25F84 profile and admit the normalized loaded image measured in three qualification boots. Remove exploratory overrides and unknown-image admission from the support path. Assisted-by: LLM Co-authored-by: Claude Opus 5.5 --- drivers/gpu/drm/asahi/asahi_core.rs | 4 + drivers/gpu/drm/asahi/driver.rs | 4 + drivers/gpu/drm/asahi/g17/fw/compute.rs | 129 ++++- drivers/gpu/drm/asahi/g17/fw/compute_pool.rs | 15 +- drivers/gpu/drm/asahi/g17/fw/initdata.rs | 9 + .../gpu/drm/asahi/g17/fw/initdata/v25g83.rs | 69 ++- drivers/gpu/drm/asahi/g17/fw/kick.rs | 61 ++- drivers/gpu/drm/asahi/g17/fw/render.rs | 4 + drivers/gpu/drm/asahi/g17/fw/variant.rs | 51 +- drivers/gpu/drm/asahi/g17/initdata.rs | 2 +- drivers/gpu/drm/asahi/g17/initdata/v25g83.rs | 94 ++-- drivers/gpu/drm/asahi/g17/mod.rs | 42 +- drivers/gpu/drm/asahi/g17/port.rs | 31 +- drivers/gpu/drm/asahi/g17/queue/compute.rs | 8 + drivers/gpu/drm/asahi/g17/runtime.rs | 11 +- drivers/gpu/drm/asahi/g17/runtime/render.rs | 12 +- drivers/gpu/drm/asahi/hw/mod.rs | 1 + drivers/gpu/drm/asahi/hw/t6040.rs | 2 + drivers/gpu/drm/asahi/hw/t6041.rs | 490 ++++++++++++++++++ drivers/gpu/drm/asahi/hw/t8140.rs | 2 + drivers/gpu/drm/asahi/regs.rs | 56 ++ 21 files changed, 1015 insertions(+), 82 deletions(-) create mode 100644 drivers/gpu/drm/asahi/hw/t6041.rs diff --git a/drivers/gpu/drm/asahi/asahi_core.rs b/drivers/gpu/drm/asahi/asahi_core.rs index e6b4d0c9a088f..fb7b6e2b042ab 100644 --- a/drivers/gpu/drm/asahi/asahi_core.rs +++ b/drivers/gpu/drm/asahi/asahi_core.rs @@ -216,5 +216,9 @@ module! { default: 1, description: "G16: validate CDM/VDM stream records", }, + t6041_fw_log: u32 { + default: 0, + description: "T6041 bring-up: enable the firmware's log at boot (main configuration +0x250 = 1, status A debug flags 3)", + }, }, } diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs index addabd04fe701..0153a992b8b49 100644 --- a/drivers/gpu/drm/asahi/driver.rs +++ b/drivers/gpu/drm/asahi/driver.rs @@ -154,6 +154,10 @@ macro_rules! asahi_of_device_table { of::DeviceId::new(c"apple,agx-t6040"), ProbeConfig::G17(&hw::t6040::CONFIG) ), + ( + of::DeviceId::new(c"apple,agx-t6041"), + ProbeConfig::G17(&hw::t6041::CONFIG) + ), ( of::DeviceId::new(c"apple,agx-t8103"), ProbeConfig::Legacy(&hw::t8103::HWCONFIG) diff --git a/drivers/gpu/drm/asahi/g17/fw/compute.rs b/drivers/gpu/drm/asahi/g17/fw/compute.rs index b1afdad816b6b..211a9e419e976 100644 --- a/drivers/gpu/drm/asahi/g17/fw/compute.rs +++ b/drivers/gpu/drm/asahi/g17/fw/compute.rs @@ -26,6 +26,33 @@ pub(crate) const COMPUTE_KICK_EVENT_MASK: [u64; 4] = [0, 0, 4, 0]; const COMPUTE_REGISTERS: usize = 41; const COMPUTE_REGISTERS_MAX: usize = COMPUTE_REGISTERS + 1; +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub(crate) enum ComputeProgram { + G17P = 0, + V25F84 = 1, +} + +static PROGRAM: core::sync::atomic::AtomicU8 = + core::sync::atomic::AtomicU8::new(ComputeProgram::G17P as u8); + +impl ComputeProgram { + pub(crate) fn select(self) { + PROGRAM.store(self as u8, core::sync::atomic::Ordering::Relaxed); + } + + pub(crate) fn current() -> Self { + match PROGRAM.load(core::sync::atomic::Ordering::Relaxed) { + 1 => Self::V25F84, + _ => Self::G17P, + } + } +} + +const V25F84_REGISTERS: usize = 36; +const V25F84_CHECKPOINTS: [u16; 4] = [34, 35, 35, V25F84_REGISTERS as u16]; +const V25F84_UNK_0A599_FIRST: u64 = 0x0000_0132_0040_0020; + /// Validated per-command shader-context scratch request. /// /// The layout word is Apple's packed SCS layout: depth (bits 0:15), levels @@ -134,7 +161,10 @@ impl ScratchRequest { } fn checkpoints(self) -> [u16; 4] { - CHECKPOINTS.map(|count| count + u16::from(self.enabled())) + match ComputeProgram::current() { + ComputeProgram::G17P => CHECKPOINTS.map(|count| count + u16::from(self.enabled())), + ComputeProgram::V25F84 => V25F84_CHECKPOINTS, + } } } const ALT_REGISTERS: usize = 4; @@ -187,6 +217,15 @@ impl OperandState { r0a5c9: 0x1e000, size: 0x44000, }; + pub(crate) const FOUR_BY_TEN: OperandState = OperandState { + r10229: 0x78800, + r140a8: 0x7a800, + r10099: 0x3c405, + r10091: 0x78400, + r0a5c1: 0x0005, + r0a5c9: 0x3c000, + size: 0x80000, + }; } /// Checkpoints into the main register array, as entry counts: after the @@ -392,6 +431,61 @@ fn compute_registers(args: &ComputeArgs) -> Result<[RegisterWrite; COMPUTE_REGIS ]) } +fn compute_registers_25f84(args: &ComputeArgs) -> Result<[RegisterWrite; V25F84_REGISTERS]> { + let preempt = args.preempt_va; + let operand = args.operand_state_va; + let state = args.operand; + let key = work_key(args.context_id, args.work_state); + let ids = OBJECT_IDS.word(); + let intersection = args.intersection.words(); + + Ok([ + RegisterWrite::new(0x1a510, preempt), + RegisterWrite::new(0x1a420, args.cdm_va), + RegisterWrite::new(0x1a4d0, offset_va(preempt, PREEMPT_STATE[0])?), + RegisterWrite::new(0x1a4d8, offset_va(preempt, PREEMPT_STATE[1])?), + RegisterWrite::new(0x1a4e0, offset_va(preempt, PREEMPT_STATE[2])?), + RegisterWrite::new(0x1a4e8, offset_va(preempt, PREEMPT_STATE[3])?), + RegisterWrite::new(0x1a440, UNK_1A440), + RegisterWrite::new(0x1a458, UNK_1A458), + RegisterWrite::new(0x101d9, UNK_101D9), + RegisterWrite::new(0x1a089, intersection[0]), + RegisterWrite::new(0x1a091, intersection[1]), + RegisterWrite::new(0x1a059, intersection[2]), + RegisterWrite::new(0x1a061, intersection[3]), + RegisterWrite::new(0x1a0b9, intersection[4]), + RegisterWrite::new(0x1a0c1, intersection[5]), + RegisterWrite::new(0x101d1, args.scratch.size_word()), + RegisterWrite::new( + 0x0d479, + if args.intersection.auxiliary == 0 { + args.scratch.tail_word() + } else { + u64::from(args.intersection.auxiliary) + }, + ), + RegisterWrite::new(0x1a0e9, args.scratch.stack_word()), + RegisterWrite::new(0x107a1, args.scratch.window_word()), + RegisterWrite::new(0x0a599, V25F84_UNK_0A599_FIRST), + RegisterWrite::new(0x0d411, UNK_0D411), + RegisterWrite::new(0x1a540, ids), + RegisterWrite::new(0x014a9, ids), + RegisterWrite::new(0x0a351, ids), + RegisterWrite::new(0x10201, key), + RegisterWrite::new(0x10428, key), + RegisterWrite::new(0x14028, u64::from(args.free_list_slot)), + RegisterWrite::new(0x14070, args.usage_va | 1), + RegisterWrite::new(0x10229, offset_va(operand, state.r10229)?), + RegisterWrite::new(0x140a8, offset_va(operand, state.r140a8)?), + RegisterWrite::new(0x10099, offset_va(operand, state.r10099)?), + RegisterWrite::new(0x10091, offset_va(operand, state.r10091)?), + RegisterWrite::new(0x0a5c1, offset_va(operand, state.r0a5c1)?), + RegisterWrite::new(0x0a5c9, offset_va(operand, state.r0a5c9)?), + RegisterWrite::new(0x1a440, UNK_1A440_FINAL), + RegisterWrite::new(0x0a599, UNK_0A599), + ]) +} + /// The second array exchanges the operand-state regions of registers /// 0x10099/0x0a5c1 and 0x10091/0x0a5c9. fn alt_registers(args: &ComputeArgs) -> Result<[RegisterWrite; ALT_REGISTERS]> { @@ -506,6 +600,8 @@ impl ComputeDescriptor { const UNK_FB0: u16 = 0x1a; const UNK_FBA: u32 = 0xe0a0_0001; const UNK_FC5: u8 = 0x9f; + const UNK_FBA_25F84: u32 = 0x6c00_0001; + const UNK_FC5_25F84: u8 = 0x5f; /// Fills a descriptor cleared by `Object::initialize`. The unused firmware /// fields and padding retain that zero initialization. @@ -516,7 +612,7 @@ impl ComputeDescriptor { { return Err(EINVAL); } - let registers = compute_registers(args)?; + let program = ComputeProgram::current(); let alt_registers = alt_registers(args)?; let registers_va = offset_va(args.descriptor_va, Self::REGISTERS_OFFSET)?; let alt_registers_va = offset_va(args.descriptor_va, Self::ALT_REGISTERS_OFFSET)?; @@ -527,10 +623,17 @@ impl ComputeDescriptor { self.context_id = args.context_id.into(); self.work_state_va = args.work_state_va; self.checkpoints = args.scratch.checkpoints(); - // The enabled program prepends one register; disabled descriptors keep - // the same forty writes, trailer count and resume checkpoints. - let first = usize::from(!args.scratch.enabled()); - self.registers.set(registers_va, ®isters[first..])?; + match program { + ComputeProgram::G17P => { + let registers = compute_registers(args)?; + let first = usize::from(!args.scratch.enabled()); + self.registers.set(registers_va, ®isters[first..])?; + } + ComputeProgram::V25F84 => { + self.registers + .set(registers_va, &compute_registers_25f84(args)?)?; + } + } self.alt_registers.set(alt_registers_va, &alt_registers)?; self.preempt_va = args.preempt_va; self.cdm_end_va = args.cdm_end_va; @@ -552,8 +655,18 @@ impl ComputeDescriptor { self.timestamp_end_va = timestamp_end_va; self.unk_fb0 = Self::UNK_FB0; self.free_list_control_va = args.free_list_control_va; - self.unk_fba = Self::UNK_FBA; - self.unk_fc5 = Self::UNK_FC5; + match program { + ComputeProgram::G17P => { + self.unk_fba = Self::UNK_FBA; + self.unk_fc5 = Self::UNK_FC5; + } + ComputeProgram::V25F84 => { + self.unk_fba = Self::UNK_FBA_25F84; + self.unk_fbe[0] = 0x06; + self.unk_fc5 = Self::UNK_FC5_25F84; + self.unk_fc6[0] = 0x04; + } + } self.usage_va = args.usage_fw_va; self.context_generation = args.context_generation; Ok(()) diff --git a/drivers/gpu/drm/asahi/g17/fw/compute_pool.rs b/drivers/gpu/drm/asahi/g17/fw/compute_pool.rs index 0f0614f6155a1..8ce8807cd71c0 100644 --- a/drivers/gpu/drm/asahi/g17/fw/compute_pool.rs +++ b/drivers/gpu/drm/asahi/g17/fw/compute_pool.rs @@ -18,9 +18,22 @@ pub(crate) fn extra_bytes(active_cores: u32, scs_pages: u16) -> Result { Ok(extra) } +static BASE_BLOCKS: core::sync::atomic::AtomicUsize = + core::sync::atomic::AtomicUsize::new(COMPUTE_BLOCKS); + +pub(crate) fn set_base_blocks(blocks: usize) -> Result { + if blocks < COMPUTE_BLOCKS || blocks > PAGE_LIST_SIZE / (FREE_LIST_BLOCK_PAGES as usize * 8) { + return Err(EINVAL); + } + BASE_BLOCKS.store(blocks, core::sync::atomic::Ordering::Relaxed); + Ok(()) +} + pub(crate) fn required_blocks(active_cores: u32, scs_pages: u16) -> Result { let extra = extra_bytes(active_cores, scs_pages)?; - let bytes = extra.checked_add((COMPUTE_BLOCKS * FREE_LIST_BLOCK_SIZE) as u64) + let base = BASE_BLOCKS.load(core::sync::atomic::Ordering::Relaxed); + let bytes = extra + .checked_add((base * FREE_LIST_BLOCK_SIZE) as u64) .ok_or(EOVERFLOW)?; let blocks = usize::try_from(bytes.div_ceil(FREE_LIST_BLOCK_SIZE as u64)) .map_err(|_| EOVERFLOW)?; diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata.rs b/drivers/gpu/drm/asahi/g17/fw/initdata.rs index 3092e599caa84..387cebb28a368 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata.rs @@ -781,6 +781,15 @@ impl PerfGroup { } } } + + pub(crate) fn replicate_first_lane(&mut self, lanes: usize) { + for index in 0..PERF_STATES { + for lane in lanes.max(1)..PERF_LANES { + self.core_mv[index][lane] = self.core_mv[index][0]; + self.sram_mv[index][lane] = self.sram_mv[index][0]; + } + } + } } /// Returns the performance states selected by `map` from `opps`. diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index f4c3ee5db22e2..6e2453f77ab52 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -1,20 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -//! Initialization data of the 25G83 firmware build (macOS 26.6, `mBoot-18000.161.10`). -//! -//! The M4 Pro (T6040) runs this build. It has the interface version of the A18 Pro's -//! 26A428 build and the same objects, with these differences: -//! -//! - [`HwData25G83`]: the A18 Pro's hardware data up to `0x2554`, eight bytes inserted before -//! `0x2570` and four more before `0x2614`, and the CS and AFR performance tables; -//! - [`PowerConfig25G83`]: the power configuration with two blocks removed (`0xa8` and `0xa0` -//! bytes), at bundle `+0x5380`, followed by the auxiliary data; -//! - region C and the power state ([`POWER_STATE_WORDS`]) with other values; -//! - one firmware instance carrying both roles, with a larger status B ([`private`]), five -//! separately allocated views and a firmware area ([`kernel_window`]). -//! -//! The constant contents were derived from the G16 encoders of Gravity Linux, which run this -//! firmware on the M4 Pro, by evaluating them for this SoC; see the commit that added them. use super::{ fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, @@ -107,6 +92,7 @@ pub(crate) struct HwDataArgs25G83<'a> { pub(crate) cs_opps: &'a [Opp], /// Operating points of the AFR clock domain. pub(crate) afr_opps: &'a [Opp], + pub(crate) chip: Option<&'a Chip>, } /// The hardware data of the 25G83 build, at the base of the bundle. @@ -300,8 +286,12 @@ impl HwData25G83 { } self.register_map[window.slot] = RegisterMapEntry::window(window); } - if !args.reserved_slots.is_empty() { - return Err(EINVAL); + for &slot in args.reserved_slots { + match used.get_mut(slot) { + Some(used) if !*used => *used = true, + _ => return Err(EINVAL), + } + self.register_map[slot] = RegisterMapEntry::reserved(); } self.chip_id = args.chip_id; @@ -336,6 +326,9 @@ impl HwData25G83 { } self.perf_a.fill(&states_a, 1); self.perf_b.fill(&states_b, 1); + if ext.chip.is_some_and(|chip| chip.perf_a_replicate_lanes) { + self.perf_a.replicate_first_lane(states_a[0].cells); + } for (index, state) in states_b.iter().enumerate() { self.perf_b_freq_mhz[index] = state.freq_mhz; self.perf_a_states[index] = perf.states_a[index].into(); @@ -344,7 +337,9 @@ impl HwData25G83 { self.perf_a_relative = perf.relative_a; self.perf_b_relative = perf.relative_b; self.unk_1848 = [0x3f82_8f5c; 11]; - self.cs_perf.fill(ext.cs_opps, perf.sram_min_mv)?; + if ext.chip.is_none_or(|chip| chip.cs_perf_table) { + self.cs_perf.fill(ext.cs_opps, perf.sram_min_mv)?; + } self.afr_perf.fill(ext.afr_opps, perf.sram_min_mv)?; self.unk_2544 = 5; @@ -375,16 +370,46 @@ impl HwData25G83 { self.unk_26c0 = 1; self.unk_26c4 = 1; self.unk_26f4 = 1; + // SAFETY: `HwData25G83` consists of integers only and has no padding, so any bytes are a + // valid value. + let bytes = unsafe { + core::slice::from_raw_parts_mut((self as *mut Self).cast::(), size_of::()) + }; + if let Some(chip) = ext.chip { + overlay_words(bytes, chip.hw_data_words)?; + } Ok(()) } } +fn overlay_words(bytes: &mut [u8], words: Words) -> Result { + for &(offset, value, repeat) in words { + for index in 0..repeat { + let at = offset + 4 * index; + bytes + .get_mut(at..at + 4) + .ok_or(EINVAL)? + .copy_from_slice(&value.to_le_bytes()); + } + } + Ok(()) +} + /// Power configuration control period, in 24 MHz clocks (8 ms). const PERIOD_CLOCKS: u32 = 200_000; /// Temperature sensors read by the die and MTR temperature limiters. const TEMP_SENSOR_MASK: u32 = 0x2a22_2aa2; -/// Offset of the per-chip leakage coefficients in the power configuration. +pub(crate) struct Chip { + pub(crate) max_power_mw: u32, + pub(crate) leakage: [u32; 56], + pub(crate) aon_table: &'static [u32], + pub(crate) hw_data_words: Words, + pub(crate) power_config_words: Words, + pub(crate) perf_a_replicate_lanes: bool, + pub(crate) cs_perf_table: bool, +} + const LEAKAGE_OFFSET: usize = 0x642c; /// Offset in the power configuration of the GPU address of the configuration's status (the /// configuration named by the root, `+0x80`). @@ -597,10 +622,12 @@ impl PowerConfig25G83 { self.0[AON_POINTER..AON_POINTER + 8].copy_from_slice(&aon_va.to_le_bytes()); self.0[ENTROPY..ENTROPY + 4].copy_from_slice(&entropy.to_le_bytes()); } + + pub(crate) fn apply_chip(&mut self, chip: &Chip) -> Result { + overlay_words(&mut self.0, chip.power_config_words) + } } -/// The AON temperature-source rows the power configuration names: the GPU cluster 0, GPU -/// cluster 1 and SoC sensors. pub(crate) const AON_TABLE: [u32; 9] = [ u32::from_be_bytes(*b"g0TR"), 0x006b_0001, diff --git a/drivers/gpu/drm/asahi/g17/fw/kick.rs b/drivers/gpu/drm/asahi/g17/fw/kick.rs index ca2b6604a302b..2a035524bdffb 100644 --- a/drivers/gpu/drm/asahi/g17/fw/kick.rs +++ b/drivers/gpu/drm/asahi/g17/fw/kick.rs @@ -507,10 +507,46 @@ impl KickEntry { } } +pub(crate) fn ksm_slot_descriptor( + ring_va: u64, + data_master: super::queue::DataMaster, + priority: u8, +) -> Result { + if ring_va & !RenderRegistration::ADDRESS_MASK != 0 || priority >= Policy::PRIORITIES { + return Err(EINVAL); + } + let term = (3 * (4 - u64::from(priority)) + 0x3fe).wrapping_shl(54); + Ok(((data_master as u64 + 1) << 60) | ring_va | term) +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct KsmQueue { + pub(crate) qid: u8, + pub(crate) descriptor: u64, +} + +impl KsmQueue { + pub(crate) fn new( + qid: u8, + ring_va: u64, + data_master: super::queue::DataMaster, + priority: u8, + ) -> Result { + if qid > QID_MAX { + return Err(EINVAL); + } + Ok(Self { + qid, + descriptor: ksm_slot_descriptor(ring_va, data_master, priority)?, + }) + } +} + /// The complete write-only installation of a fragment/tiling queue pair. /// Firmware must see both queues configured before either outer command. pub(crate) struct RenderRegistration { writes: [(usize, u64); 8], + queues: [KsmQueue; 2], } impl RenderRegistration { @@ -540,8 +576,7 @@ impl RenderRegistration { if qid > QID_MAX || va & !Self::ADDRESS_MASK != 0 { return Err(EINVAL); } - let completion = (3 * (4 - u64::from(priority)) + 0x3fe).wrapping_shl(54); - let payload = ((engine as u64 + 1) << 60) | va | completion; + let payload = ksm_slot_descriptor(va, engine, priority)?; let (low, high) = if qid < 64 { (1u64 << qid, 0) } else { @@ -554,10 +589,30 @@ impl RenderRegistration { (Self::VALID_LOW, low), ]); } - Ok(Self { writes }) + Ok(Self { + writes, + queues: [ + KsmQueue::new( + fragment.0, + fragment.1, + super::queue::DataMaster::Fragment, + priority, + )?, + KsmQueue::new( + tiling.0, + tiling.1, + super::queue::DataMaster::Tiling, + priority, + )?, + ], + }) } pub(crate) fn writes(&self) -> &[(usize, u64); 8] { &self.writes } + + pub(crate) fn queues(&self) -> &[KsmQueue; 2] { + &self.queues + } } diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index a6bc1704d1418..44c3527f2c237 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -70,6 +70,10 @@ impl Deflake { sub: [0xa0, 0x820], viewport: false, }; + pub(crate) const FOUR_CLUSTERS: Deflake = Deflake { + sub: [0x120, 0xda0], + viewport: false, + }; } /// The register programs of a firmware build's render descriptors. diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 39c8cfcd77a2a..cb38da617e24f 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -36,7 +36,22 @@ pub(crate) enum InitGraph { V25G83, } +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum QueueRegistration { + Firmware, + PoweredRenderPairs, + Mailbox, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub(crate) enum FirmwareVersion { + V26A428, + V25G83, + V25F84, +} + /// The layouts and rules of one G17 firmware build. +#[derive(Copy, Clone)] pub(crate) struct Variant { /// The initialization graph. pub(crate) graph: InitGraph, @@ -49,8 +64,7 @@ pub(crate) struct Variant { pub(crate) qid_floor: usize, /// Host words of a queue's pointer block. pub(crate) pointers: PointerLayout, - /// The host registers each render pair through the GPU's MMIO queue ports. - pub(crate) mmio_pair_registration: bool, + pub(crate) registration: QueueRegistration, /// Render kick entries may name an MCache table. pub(crate) mcache: bool, /// Renders may set the few-primitives auxiliary framebuffer flag. @@ -65,6 +79,10 @@ pub(crate) struct Variant { pub(crate) lead_commands: bool, /// Register programs of the render descriptors. pub(crate) programs: super::render::RegisterPrograms, + pub(crate) compute: super::compute::ComputeProgram, + pub(crate) opening_record: bool, + pub(crate) split_firmware_area: bool, + pub(crate) version: FirmwareVersion, } /// A firmware build a SoC supports, identified by its loaded TEXT. @@ -98,13 +116,17 @@ pub(crate) const V26A428: Variant = Variant { producer: 0x40, recovery_target: Some(0x50), }, - mmio_pair_registration: true, + registration: QueueRegistration::PoweredRenderPairs, mcache: true, few_primitives: true, ray_traversal: true, fragment_busy: true, lead_commands: false, programs: super::render::RegisterPrograms::G17P, + compute: super::compute::ComputeProgram::G17P, + opening_record: true, + split_firmware_area: false, + version: FirmwareVersion::V26A428, }; /// The M4 Pro firmware (25G83, `mBoot-18000.161.10`). @@ -122,7 +144,7 @@ pub(crate) const V25G83: Variant = Variant { }, // Whether this build tolerates the MMIO registration is unknown; the M4 Pro has only run // without it. - mmio_pair_registration: false, + registration: QueueRegistration::Firmware, mcache: false, // A fragment stage with the few-primitives flag hangs. few_primitives: false, @@ -133,9 +155,30 @@ pub(crate) const V25G83: Variant = Variant { fragment_busy: false, lead_commands: true, programs: super::render::RegisterPrograms::V25G83, + compute: super::compute::ComputeProgram::G17P, + opening_record: true, + split_firmware_area: false, + version: FirmwareVersion::V25G83, }; +pub(crate) const V25F84: Variant = Variant { + version: FirmwareVersion::V25F84, + qid_floor: 0, + registration: QueueRegistration::Mailbox, + compute: super::compute::ComputeProgram::V25F84, + opening_record: false, + split_firmware_area: true, + ..V25G83 +}; + +impl Variant { + pub(crate) const fn is_25f84(&self) -> bool { + matches!(self.version, FirmwareVersion::V25F84) + } +} + const _: () = { assert!(super::queue::PointerBlock::valid_layout(&V26A428.pointers)); assert!(super::queue::PointerBlock::valid_layout(&V25G83.pointers)); + assert!(super::queue::PointerBlock::valid_layout(&V25F84.pointers)); }; diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index c8763f40871d1..56b39db036c2e 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -838,7 +838,7 @@ impl InitData { clusters, }; if variant.graph == super::fw::variant::InitGraph::V25G83 { - return self.build_25g83(&env, cfg, metrics, active_cores); + return self.build_25g83(&env, cfg, metrics, active_cores, variant); } self.map_register_windows(&env, cfg.windows)?; let tables = self.build_descriptor_tables(&env, abi::private::SIZE)?; diff --git a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs index 670f4b96dcb76..9aeab2a4f7fb1 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs @@ -18,6 +18,7 @@ use kernel::{c_str, prelude::*}; struct Objects { views: [usize; 5], firmware_area: usize, + firmware_area_2: Option, power_state: usize, aon: usize, } @@ -30,6 +31,7 @@ impl InitData { cfg: &Config, metrics: &KernelObject, active_cores: u32, + variant: &crate::g17::fw::Variant, ) -> Result { self.status_a = v::private::STATUS_A; self.status_b = v::private::STATUS_B; @@ -39,9 +41,9 @@ impl InitData { let first_block = self.build_render_pool(env)?; self.build_compute_pools(env, first_block)?; let bundle = self.build_bundle(env, cfg, metrics)?; - let objects = self.build_25g83_objects(env)?; + let objects = self.build_25g83_objects(env, variant.split_firmware_area)?; self.views = Some(objects.views); - self.initialize_25g83(env, cfg, active_cores, &tables, &bundle, &objects)?; + self.initialize_25g83(env, cfg, active_cores, &tables, &bundle, &objects, variant)?; if cfg.uat_quirks.clean_tables { // Write back every host-built byte before the firmware may read it. for object in self.objects.iter_mut() { @@ -53,8 +55,7 @@ impl InitData { Ok(()) } - /// The views, the firmware area, the power-state object and the AON table. - fn build_25g83_objects(&mut self, env: &BuildEnv<'_>) -> Result { + fn build_25g83_objects(&mut self, env: &BuildEnv<'_>, split: bool) -> Result { use mmu::{PROT_FW_PRIV_RW as FP, PROT_FW_SHARED_RW as FS}; use CpuMap::WriteCombined as WC; let mut views = [0; 5]; @@ -63,12 +64,17 @@ impl InitData { } let firmware_area = self.add(env.fixed(v::kernel_window::FIRMWARE_AREA, mmu::UAT_PGSZ, FP, WC)?)?; + let firmware_area_2 = match split { + true => Some(self.add(env.dynamic(mmu::UAT_PGSZ, mmu::UAT_PGSZ as u64, FP, WC)?)?), + false => None, + }; let power_state = self.add(env.fixed(v::kernel_window::POWER_STATE, mmu::UAT_PGSZ, FS, WC)?)?; let aon = self.add(env.fixed(v::kernel_window::AON_TABLE, mmu::UAT_PGSZ, FP, WC)?)?; Ok(Objects { views, firmware_area, + firmware_area_2, power_state, aon, }) @@ -84,15 +90,25 @@ impl InitData { tables: &DescriptorTables, bundle: &BundleObjects, objects: &Objects, + variant: &crate::g17::fw::Variant, ) -> Result { let bundle_va = self.object(self.bundle)?.gpu_va(); let cluster_va = self.object(self.cluster)?.gpu_va(); let config_va = cluster_va + v::private::CONFIG as u64; let status_a_va = cluster_va + v::private::STATUS_A as u64; let status_b_va = cluster_va + v::private::STATUS_B as u64; - let aon_va = self.object(objects.aon)?.gpu_va(); + let chip = cfg.chip; + let aon_table = chip.map_or(v::AON_TABLE.as_slice(), |chip| chip.aon_table); + let aon_va = match aon_table.is_empty() { + true => 0, + false => self.object(objects.aon)?.gpu_va(), + }; let power_state_va = self.object(objects.power_state)?.gpu_va(); let firmware_area_va = self.object(objects.firmware_area)?.gpu_va(); + let firmware_area_2_va = match objects.firmware_area_2 { + Some(index) => self.object(index)?.gpu_va(), + None => firmware_area_va, + }; let mut view_va = [0; 5]; for (va, index) in view_va.iter_mut().zip(objects.views) { *va = self.object(index)?.gpu_va(); @@ -101,14 +117,28 @@ impl InitData { let opps = Self::opps(env.dev, cfg.opps, env.clusters)?; let cs_opps = Self::opp_table(env.dev, c_str!("apple,cs-opp"))?; let afr_opps = Self::opp_table(env.dev, c_str!("apple,afr-opp"))?; - let calibration = Calibration::read(env.dev)?; - calibration.check_opps(&opps)?; + let calibration = if chip.is_none() { + let calibration = Calibration::read(env.dev)?; + calibration.check_opps(&opps)?; + Some(calibration) + } else { + None + }; + let (perf, max_power_mw, leakage) = match (calibration.as_ref(), chip) { + (Some(calibration), None) => ( + &calibration.perf, + calibration.max_power_mw, + &calibration.leakage, + ), + (None, Some(chip)) => (cfg.perf, chip.max_power_mw, &chip.leakage), + _ => return Err(EINVAL), + }; let hw_args = abi::HwDataArgs { chip_id: cfg.chip_id, windows: cfg.windows, reserved_slots: cfg.reserved_slots, opps: &opps, - perf: &calibration.perf, + perf, completion_rings: bundle.completion_rings, qos_va: self.object(bundle.qos)?.gpu_va(), }; @@ -116,6 +146,7 @@ impl InitData { active_cores, cs_opps: &cs_opps, afr_opps: &afr_opps, + chip, }; // SAFETY: Plain C entropy source. let entropy = unsafe { kernel::bindings::get_random_u32() }; @@ -126,28 +157,29 @@ impl InitData { })?; self.object_mut(self.bundle)? .initialize::(v::POWER_CONFIG, |power| { - power.init( - config_va, - aon_va, - entropy, - calibration.max_power_mw, - &calibration.leakage, - ); + power.init(config_va, aon_va, entropy, max_power_mw, leakage); + if let Some(chip) = chip { + power.apply_chip(chip)?; + } Ok(()) })?; self.object_mut(self.region_c)? .initialize::(0, |region| { region.init(cfg.region_c); - region.max_power_mw = calibration.max_power_mw; + region.max_power_mw = max_power_mw; Ok(()) })?; - for (index, word) in v::AON_TABLE.iter().enumerate() { + for (index, word) in aon_table.iter().enumerate() { self.object_mut(objects.aon)?.write(index * 4, *word)?; } // Status A and B: only the boot words and the firmware-control pointers. + let mut status_a = abi::StatusBlock::new(); + if variant.is_25f84() && crate::module_parameters::t6041_fw_log.value() != 0 { + status_a.unk_0 = 3; + } self.object_mut(self.cluster)? - .write(v::private::STATUS_A, abi::StatusBlock::new())?; + .write(v::private::STATUS_A, status_a)?; self.object_mut(self.cluster)? .write(v::private::STATUS_B, abi::StatusBlock::new())?; // Status B keeps the A18 Pro's field offsets up to its recovery words. @@ -180,19 +212,24 @@ impl InitData { .initialize::(main_offset, |main| { main.init_primary(&args, &views); main.view_va = view_va; - main.firmware_area_va = [firmware_area_va; 2]; + main.firmware_area_va = [firmware_area_va, firmware_area_2_va]; main.unk_18 = (cluster_va + v::private::MAIN_HEADER as u64).to_le_bytes(); // The secondary role's words, on the single instance. main.unk_300 = 4; main.power_config_va = bundle_va + v::POWER_CONFIG as u64; main.unk_32c = 0; main.unk_344 = 0; + if variant.is_25f84() && crate::module_parameters::t6041_fw_log.value() != 0 { + main.log_enable = 1; + } Ok(()) })?; - self.object_mut(self.bundle)?.write( - main_offset + abi::MainConfig::CONTROL_RING, - abi::OpeningRecord::new(abi::Role::Primary), - )?; + if variant.opening_record { + self.object_mut(self.bundle)?.write( + main_offset + abi::MainConfig::CONTROL_RING, + abi::OpeningRecord::new(abi::Role::Primary), + )?; + } // The merged root. let root = abi::RootArgs { @@ -214,12 +251,11 @@ impl InitData { } } - // The opening control record is staged in slot 0. - for (offset, value) in [ - (0, 0), - (0x10, 0), - (abi::ChannelState::PRODUCER, abi::OPENING_PRODUCER), - ] { + let producer = match variant.opening_record { + true => abi::OPENING_PRODUCER, + false => 0, + }; + for (offset, value) in [(0, 0), (0x10, 0), (abi::ChannelState::PRODUCER, producer)] { self.object(self.cluster)? .word(self.state_offset(Role::Primary) + abi::CONTROL_STATE_OFFSET + offset)? .store(value, Ordering::Relaxed); diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index 6495ad2d98c13..b96715eaae321 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -191,6 +191,7 @@ pub(crate) struct Config { pub(crate) fabric_bridge: bool, /// The SoC's values of region C. pub(crate) region_c: &'static fw::initdata::RegionCConfig, + pub(crate) chip: Option<&'static fw::initdata::v25g83::Chip>, /// Physical address of the firmware's per-slot recovery keys, read while the firmware is /// halted to attribute a fault; `None` where it is unknown (recovery then relies on the /// firmware's own blame). @@ -212,6 +213,7 @@ pub(crate) struct CoreLayout { pub(crate) operand_state: fw::compute::OperandState, /// A render's deflake buffer. pub(crate) deflake: fw::render::Deflake, + pub(crate) compute_blocks: usize, } /// Where a G17 target's crash log buffers live. @@ -1108,8 +1110,9 @@ impl Shared { self.crashed.store(true, Ordering::Release); dev_err!( self.dev.as_ref(), - "GPU firmware ({:?}) crashed, device lost\n", - role + "GPU firmware ({:?}) crashed, device lost (crash log {} bytes)\n", + role, + crashlog.map_or(0, |log| log.len()) ); // The GPU dev coredump support this used is gone; the crash log is not published. @@ -1147,6 +1150,9 @@ impl Gpu { None }; let variant = Self::firmware_variant(dev, &cfg.firmware)?; + variant.compute.select(); + fw::compute_pool::set_base_blocks(cfg.core_layout.compute_blocks) + .inspect_err(|_| dev_err!(dev.as_ref(), "Invalid compute pool size\n"))?; let id = regs.get_g17_id()?; if let Some(calibration) = calibration { calibration.check_cores(&id.core_masks)?; @@ -1450,16 +1456,27 @@ impl Gpu { session.uat.confirm_firmware_root_entries(root_entries)?; } let mailbox: *const Mailbox = &*session.primary.rtkit; - session.port.attach(session.init.port_views(mailbox)?, session.bridge.regs.clone())?; + session.port.attach( + session.init.port_views(mailbox)?, + session.bridge.regs.clone(), + shared.variant.registration, + )?; session.uat.mirror_secondary_root()?; if cfg.fabric_bridge { session.bridge.regs.enable_g17_gating()?; } let ack = shared.variant.init_ack; - session.primary.init(session.init.root_va(Role::Primary)?, ack)?; + let root_va = session.init.root_va(Role::Primary)?; + dev_info!( + dev.as_ref(), + "bring-up: initialization data built, publishing root {:#x}\n", + root_va + ); + session.primary.init(root_va, ack)?; if ack == fw::variant::InitAck::Status { Self::wait_for_init_accepted(dev, &session.init, &session.primary)?; } + dev_info!(dev.as_ref(), "bring-up: initialization data accepted\n"); if let Some(secondary) = session.secondary.as_mut() { secondary.init(session.init.root_va(Role::Secondary)?, ack)?; } @@ -1469,10 +1486,25 @@ impl Gpu { secondary.notify(MSG_POWER_ON_DONE)?; } } - Self::wait_for_opening(dev, &session.init, &session.primary, session.secondary.as_ref())?; + if shared.variant.opening_record { + Self::wait_for_opening( + dev, + &session.init, + &session.primary, + session.secondary.as_ref(), + )?; + dev_info!(dev.as_ref(), "bring-up: opening record retired\n"); + } else { + dev_info!( + dev.as_ref(), + "bring-up: no opening record staged; device-control counters {:?}\n", + session.init.control_counters()? + ); + } session .uat .activate_render_context(&session._render_global)?; + dev_info!(dev.as_ref(), "bring-up: firmware running\n"); Ok(firmware) } diff --git a/drivers/gpu/drm/asahi/g17/port.rs b/drivers/gpu/drm/asahi/g17/port.rs index 8c405c8cfb90f..5c76384db68f5 100644 --- a/drivers/gpu/drm/asahi/g17/port.rs +++ b/drivers/gpu/drm/asahi/g17/port.rs @@ -91,8 +91,8 @@ unsafe impl Sync for Views {} #[pin_data] pub(super) struct Port { views: SetOnce, - /// Register access for tracing the core power state at the doorbell. regs: SetOnce, + registration: SetOnce, /// Compute work-ring slots reserved by publishers that have not exposed them yet. #[pin] rings: SpinLock<[u32; PRIORITIES]>, @@ -167,6 +167,7 @@ impl Port { pin_init!(Self { views: SetOnce::new(), regs: SetOnce::new(), + registration: SetOnce::new(), rings <- new_spinlock!([0; PRIORITIES], "g17::Port::rings"), qos <- new_spinlock!(qos::Accounting::new(), "g17::Port::qos"), rx: SetOnce::new(), @@ -187,17 +188,37 @@ impl Port { } /// Called once while the session is built, before any publisher can see the port. - pub(super) fn attach(&self, views: Views, regs: crate::regs::G17Resources) -> Result { - // SAFETY: The session's primary coprocessor owns the mailbox and outlives the port, and - // with it every use of the handle. + pub(super) fn attach( + &self, + views: Views, + regs: crate::regs::G17Resources, + registration: super::fw::variant::QueueRegistration, + ) -> Result { + // SAFETY: The session owns the mailbox and outlives every port user. let rx = unsafe { (*views.mailbox).rx_util_min_handle() }; - if self.views.populate(views) && self.regs.populate(regs) && self.rx.populate(rx) { + if self.views.populate(views) + && self.regs.populate(regs) + && self.registration.populate(registration) + && self.rx.populate(rx) + { Ok(()) } else { Err(EBUSY) } } + pub(super) fn register_queues(&self, queues: &[super::fw::kick::KsmQueue]) -> Result { + use super::fw::variant::QueueRegistration; + if self.registration.as_ref() != Some(&QueueRegistration::Mailbox) { + return Ok(()); + } + let regs = self.regs.as_ref().ok_or(ENODEV)?; + for queue in queues { + regs.register_ksm_queue(queue.qid, queue.descriptor)?; + } + Ok(()) + } + fn views(&self) -> Result<&Views> { self.views.as_ref().ok_or(ENODEV) } diff --git a/drivers/gpu/drm/asahi/g17/queue/compute.rs b/drivers/gpu/drm/asahi/g17/queue/compute.rs index e1bda516ee102..2b954b7908ed1 100644 --- a/drivers/gpu/drm/asahi/g17/queue/compute.rs +++ b/drivers/gpu/drm/asahi/g17/queue/compute.rs @@ -2004,6 +2004,7 @@ pub(crate) trait Host { ) -> Result; fn qos_cancel(&mut self, publication: qos::Publication) -> Result; fn publish_qid(&mut self, id: kick::Id) -> Result; + fn register_queue(&mut self, queue: &crate::g17::fw::kick::KsmQueue) -> Result; fn publish_outer(&mut self, priority: u8, slot: &crate::g17::fw::channels::WorkSlot) -> Result; fn notify_compute(&mut self, priority: u8, message: u64) -> Result; @@ -2211,9 +2212,16 @@ impl Queue { NATIVE_WAIT_STRIDE, record)?; wait_vas.push(address, GFP_KERNEL)?; } + let kick_ring_va = self.kick.low_va(); let prepared_kick = self.kick.prepare_entry(&kick_args)?; let mut producer; if first { + host.register_queue(&crate::g17::fw::kick::KsmQueue::new( + qid, + kick_ring_va, + DataMaster::Compute, + policy.priority(), + )?)?; producer = self.graph.first_descriptor(descriptor_high)?; host.prepare_compute_shared()?; packet.mark_published(); diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index 108c9fe19a462..fb25209c908dd 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -213,7 +213,10 @@ impl Registry { buffers <- BufferIds::new(), metrics: Arc::new(MetricsIds::new(), GFP_KERNEL)?, qos_ids: QosIds::new()?, - admission: admission::Pool::with_waiters(kick::queue_limit(variant.qid_floor), queues, renders)?, + admission: { + let admission = admission::Pool::with_waiters(kick::queue_limit(variant.qid_floor), queues, renders)?; + admission + }, render: render::RenderState::new(clusters, deflake, descriptor_flags)?, identities: queue::Identities::new(), compute: { @@ -1390,6 +1393,9 @@ impl compute::Host for ComputeHost<'_> { fn publish_qid(&mut self, id: kick::Id) -> Result { self.qids.publish(id) } + fn register_queue(&mut self, queue: &super::fw::kick::KsmQueue) -> Result { + self.port.register_queues(core::slice::from_ref(queue)) + } fn publish_outer(&mut self, _priority: u8, slot: &super::fw::channels::WorkSlot) -> Result { self.port.publish_compute(self.outer.take().ok_or(EIO)?, slot).map(|_| ()) } @@ -1440,6 +1446,9 @@ impl compute::Host for DirectHost<'_> { // An installed queue published its identity with its first command. Ok(()) } + fn register_queue(&mut self, _queue: &super::fw::kick::KsmQueue) -> Result { + Err(EAGAIN) + } fn publish_outer(&mut self, _priority: u8, slot: &super::fw::channels::WorkSlot) -> Result { self.port.publish_compute(self.outer.take().ok_or(EIO)?, slot).map(|_| ()) } diff --git a/drivers/gpu/drm/asahi/g17/runtime/render.rs b/drivers/gpu/drm/asahi/g17/runtime/render.rs index 6657f83e9e738..fc2e01be761ae 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/render.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/render.rs @@ -572,10 +572,14 @@ impl crate::g17::queue::render::Host for Host<'_> { /// check let the firmware gate the cores between the host's configuration /// writes while several clients installed their first pairs at once. fn install_pair(&mut self, registration: &crate::g17::fw::kick::RenderRegistration) -> Result { - // Firmware builds without MMIO registration install the pair from its queue - // configuration alone. - if !self.firmware.variant.mmio_pair_registration { - return Ok(()); + use crate::g17::fw::variant::QueueRegistration; + match self.firmware.variant.registration { + QueueRegistration::Firmware => return Ok(()), + QueueRegistration::Mailbox => { + self.firmware.port.register_queues(registration.queues())?; + return Ok(()); + } + QueueRegistration::PoweredRenderPairs => {} } if !self.registration_powered { self.firmware.acquire_registration_power()?; diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs index 5149f87a4ea1d..c54ffb5c208fe 100644 --- a/drivers/gpu/drm/asahi/hw/mod.rs +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -14,6 +14,7 @@ const MAX_POWERZONES: usize = 5; pub(crate) mod t600x; pub(crate) mod t602x; pub(crate) mod t6040; +pub(crate) mod t6041; mod t604x; pub(crate) mod t8103; pub(crate) mod t8112; diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 4c36c592dccba..bc59194378ea1 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -52,6 +52,7 @@ pub(crate) const CONFIG: Config = Config { native_gpu_permissions: true, }, region_c: ®ION_C, + chip: None, adopt_root_entries: false, fabric_bridge: false, // The firmware's per-slot recovery keys are not known for this build; recovery relies on @@ -105,6 +106,7 @@ pub(crate) const CONFIG: Config = Config { cores: Some((2, 20)), operand_state: fw::compute::OperandState::TWO_BY_TEN, deflake: fw::render::Deflake::TWO_CLUSTERS, + compute_blocks: fw::freelist::COMPUTE_BLOCKS, }, }; diff --git a/drivers/gpu/drm/asahi/hw/t6041.rs b/drivers/gpu/drm/asahi/hw/t6041.rs new file mode 100644 index 0000000000000..263f458b57b3b --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t6041.rs @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +use crate::g17::fw::{ + self, + initdata::{v25g83, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, +}; +use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; +use crate::hw::{GpuGen, GpuVariant}; +use super::t604x::{instances, window}; +use crate::mmu::UatQuirks; + +pub(crate) const CONFIG: Config = Config { + chip_id: CHIP_ID, + identity: Identity { + family: 0xa, + variant: 4, + num_dies: 1, + gpu_variant: GpuVariant::C, + usc_generation: 3, + hal_generation: 200, + }, + uat_ias: 42, + uat_oas: 42, + secondary_root_offset: None, + crash_buffers: CrashBuffers::FirmwareData, + // Unverified placement; use the M4 Pro layout. + pm_metrics: FixedBuffer { + va: 0x10_0008_0000, + size: 0x8000, + }, + windows: ®ISTER_WINDOWS, + reserved_slots: &RESERVED_SLOTS, + perf: &PERF, + opps: OPPS, + uat_quirks: UatQuirks { + reserved_tables: true, + clean_tables: true, + page_tlbi: true, + handoff_lock: false, + handoff_slot: 0xffff_ffff, + native_gpu_permissions: true, + }, + region_c: ®ION_C, + chip: Some(&CHIP), + adopt_root_entries: false, + fabric_bridge: false, + recovery_slot_keys: None, + gpu_generation: GpuGen::G16, + firmware: fw::variant::Firmware::Identified(&[fw::variant::Build { + os_version: [26, 5, 2], + name: "macOS 26.5.2 (25F84, iBoot mBoot-18000.121.3)", + images: &[crate::fwid::FirmwareBuild { + name: "Apple GFX 25F84 for T6041 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x5983a, 0x5991d], + text_sha256: [ + 0x6d, 0xa4, 0xd4, 0x42, 0x35, 0x35, 0x14, 0x44, 0xf6, 0xec, 0x00, 0x33, 0xf1, 0x9c, + 0xbf, 0x98, 0xd3, 0x43, 0x91, 0x28, 0x83, 0xe4, 0xe2, 0x94, 0xc1, 0x8b, 0xb8, 0xb8, + 0xa3, 0xc1, 0x83, 0xe8, + ], + }], + variant: &fw::variant::V25F84, + }]), + core_layout: CoreLayout { + cores: Some((4, 40)), + operand_state: fw::compute::OperandState::FOUR_BY_TEN, + deflake: fw::render::Deflake::FOUR_CLUSTERS, + compute_blocks: 160, + }, +}; + +pub(crate) const CHIP_ID: u32 = 0x6041; + +pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ + window(0, 0x5_0241_4000, 0x0, 0x4000, 0, 2), + instances( + window(3, 0x2_201c_4000, 0x8000, 0x1_8000, 0, 2), + 8, + 0x200_0000, + ), + window(4, 0x5_0244_c000, 0xd_0000, 0x4000, 0, 0), + window(9, 0x5_003d_0000, 0xd_8000, 0x4000, 0, 2), + window(10, 0x5_003c_0000, 0xe_0000, 0x4000, 0, 0), + window(11, 0x5_003d_8000, 0xe_8000, 0x4000, 0, 2), + window(12, 0x4_8565_c000, 0xf_0000, 0x4000, 0, 2), + window(14, 0x5_0040_0000, 0xf_8000, 0x1_0000, 0, 0), + window(15, 0x5_0cdc_0000, 0x11_0000, 0x4000, 0, 0), + window(17, 0x2_8800_0000, 0x11_8000, 0x2_1400, 0, 2), + instances( + window(20, 0x2_8b00_0000, 0x14_0000, 0x4_0000, 0, 2), + 4, + 0x20_0000, + ), + window(22, 0x2_8900_0000, 0x24_8000, 0x8000, 0, 2), + window(26, 0x2_88d0_4000, 0x25_8000, 0x8000, 0xd0_4000, 2), + window(28, 0x2_88d5_8000, 0x26_8000, 0x8000, 0xd5_8000, 2), + window(29, 0x2_88d1_0000, 0x27_8000, 0x4000, 0xd1_0000, 2), + window(31, 0x2_88d4_0000, 0x28_0000, 0x4000, 0xd4_0000, 2), + window(32, 0x2_88d6_0000, 0x28_8000, 0x2_0000, 0xd6_0000, 2), + window(33, 0x2_88d4_4000, 0x2b_0000, 0x4000, 0xd4_4000, 2), + window(34, 0x2_88d4_c000, 0x2b_8000, 0x200, 0xd4_c000, 2), + window(35, 0x2_88e0_0000, 0x2c_0000, 0x4000, 0xe0_0000, 2), + window(37, 0x2_88e4_0000, 0x2c_8000, 0x4000, 0xe4_0000, 2), + window(39, 0x2_88e0_8000, 0x2d_0000, 0x8000, 0, 2), + window(40, 0x2_88e1_c000, 0x2e_0000, 0x4000, 0xe1_c000, 2), + window(41, 0x2_88e1_f800, 0x2e_b800, 0x4000, 0, 2), + window(43, 0x2_88e6_0000, 0x2f_8000, 0x58, 0xe6_0000, 2), +]; + +const RESERVED_SLOTS: [usize; 14] = [2, 5, 6, 7, 8, 27, 30, 38, 42, 46, 47, 48, 49, 52]; + +pub(crate) const PERF: PerfConfig = PerfConfig { + states_a: [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15], + states_b: [0, 1, 2, 3, 4, 6, 7, 9, 11, 13, 15], + relative_a: [0, 10, 18, 25, 31, 40, 50, 62, 70, 78, 100], + relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + sram_min_mv: 810, +}; + +pub(crate) const OPPS: usize = 16; + +const _: () = { + let mut index = 0; + while index < PERF_STATES { + assert!((PERF.states_a[index] as usize) < OPPS); + assert!((PERF.states_b[index] as usize) < OPPS); + index += 1; + } +}; + +const MAX_POWER_MW: u32 = 129_702; + +const REGION_C: RegionCConfig = RegionCConfig { + unk_54: 0x0fa0, + unk_78: [1, 0, 0], + max_power_mw: MAX_POWER_MW, + unk_9c: 0, + unk_a0: 0, + unk_7c4: 1500, + unk_9e0: [ + 1, + 0x0102_0200, + 0x010a_2801, + 0x2802_0101, + 0x0101_010a, + 0x010a_2802, + 0x2801_0101, + 0x0001_010a, + ], +}; + +const CHIP: v25g83::Chip = v25g83::Chip { + max_power_mw: MAX_POWER_MW, + leakage: { + let mut words = [0; 56]; + words[0] = 0x44f9_c000; + words[1] = 0x44f7_2000; + words[2] = 0x44f4_b000; + words[3] = 0x44f1_5000; + words[32] = 0x43e4_0000; + words[33] = 0x43e4_0000; + words[34] = 0x43e4_0000; + words[35] = 0x43e4_0000; + words[52] = 0x4404_0000; + words[54] = 0x43df_8000; + words + }, + aon_table: &[], + hw_data_words: HW_DATA_WORDS, + power_config_words: POWER_CONFIG_WORDS, + perf_a_replicate_lanes: true, + cs_perf_table: false, +}; + +const HW_DATA_WORDS: &[(usize, u32, usize)] = &[ + (0xe0, 0x2008, 1), + (0xe8, 0x20080000, 1), + (0xf4, 0x2008, 1), + (0xf8, 0x24cb, 1), + (0xfc, 0xc98a2cfa, 1), + (0x100, 0xf4f624cb, 1), + (0x104, 0x1877e917, 1), + (0x108, 0x38d924cb, 1), + (0x10c, 0xbdab0000, 1), + (0x2d8, 0xfff82000, 1), + (0x2dc, 0xd32d2cdb, 1), + (0x2e0, 0xf5002000, 1), + (0x2e4, 0x21dae926, 1), + (0x2e8, 0x38b62000, 1), + (0x2ec, 0xc7420008, 1), + (0x3e0, 0x7fe0, 1), + (0x3e8, 0x7fe00000, 1), + (0x3f4, 0x7fe0, 1), + (0x3f8, 0x8000, 1), + (0x400, 0x80000000, 1), + (0x40c, 0x8000, 1), + (0x5d8, 0x4b232645, 1), + (0x5dc, 0xe98, 1), + (0x5e0, 0xd5a2ea5f, 1), + (0x5e4, 0x40003fff, 1), + (0x5e8, 0xca5e4000, 1), + (0x5ec, 0x4000f5a2, 1), + (0xf6c, 0x1, 1), + (0xf8c, 0xff, 1), + (0xfac, 0x1, 1), + (0xfb8, 0x1d, 1), + (0x2594, 0x2e0000, 1), + (0x2610, 0x1, 1), +]; + +const POWER_CONFIG_WORDS: &[(usize, u32, usize)] = &[ + (0x0, 0x1, 1), + (0x44, 0xc, 1), + (0x9c, 0x43a90000, 1), + (0x200, 0x1, 1), + (0x208, 0x162, 1), + (0x210, 0xd89165f9, 1), + (0x218, 0x1254c, 1), + (0x698, 0xd8917807, 1), + (0x69c, 0x13, 1), + (0x6a8, 0x129a8, 1), + (0x728, 0x4a714, 1), + (0x7a8, 0x4a714, 1), + (0x920, 0x3dd00000, 1), + (0x9ec, 0xbbfca403, 1), + (0x9f0, 0x28, 1), + (0xa70, 0x4, 1), + (0xa7c, 0x0, 1), + (0xa84, 0x3f800000, 1), + (0xa8c, 0x3e0b4396, 1), + (0xa9c, 0x3e99999a, 1), + (0xaa8, 0x28, 1), + (0xab8, 0x43b10000, 1), + (0xac0, 0x162, 1), + (0xad0, 0x0, 1), + (0xae0, 0xc7fca200, 1), + (0xaf0, 0x3e8, 1), + (0xafc, 0x3f800000, 1), + (0xb00, 0x3e0b4396, 1), + (0xb04, 0x3e99999a, 1), + (0xb08, 0x162, 1), + (0xb2c, 0x2, 1), + (0xb88, 0x42a00000, 1), + (0xb90, 0x7, 1), + (0xbc8, 0x3f7eb852, 1), + (0xbcc, 0x3ba3d70a, 1), + (0xbd0, 0x3f64c986, 1), + (0xbd4, 0x403ea7f0, 1), + (0x21f4, 0x42200000, 1), + (0x2210, 0x28, 1), + (0x2264, 0x1020, 1), + (0x226c, 0x2a222aa2, 1), + (0x2294, 0x40baaaaa, 1), + (0x22a4, 0x42080000, 1), + (0x22b0, 0x28, 1), + (0x22c8, 0x1020, 1), + (0x2868, 0x389d8, 1), + (0x2874, 0x154, 1), + (0x28d8, 0x47fd5300, 1), + (0x29bc, 0x74, 1), + (0x29c8, 0x3f772c23, 1), + (0x29d0, 0x3d0d3dcb, 1), + (0x29d8, 0x3cf5c290, 1), + (0x29e8, 0x3f19999a, 1), + (0x2a04, 0x4304ad79, 1), + (0x2a0c, 0x162, 1), + (0x2a2c, 0xc7fd10a9, 1), + (0x2a44, 0x3f772c23, 1), + (0x2a48, 0x3d0d3dcb, 1), + (0x2a4c, 0x3cf5c290, 1), + (0x2a50, 0x3f19999a, 1), + (0x2a54, 0x162, 1), + (0x2b18, 0x2ef94, 2), + (0x2b20, 0x2e89, 1), + (0x2b24, 0xe36, 1), + (0x2b40, 0x1, 1), + (0x2b94, 0x46023400, 1), + (0x2b9c, 0x208d, 1), + (0x2bc4, 0x47800000, 1), + (0x2bcc, 0x3e8, 1), + (0x2be8, 0x208d, 1), + (0x2c40, 0x28, 1), + (0x2c88, 0x3e8, 1), + (0x2cec, 0x1, 1), + (0x2cf0, 0xf, 1), + (0x2cf4, 0x3, 1), + (0x2cfc, 0x6, 1), + (0x2f0c, 0x2a222aa2, 1), + (0x3140, 0x29, 1), + (0x3144, 0x24dd, 1), + (0x3148, 0x4, 1), + (0x314c, 0x20, 1), + (0x3150, 0x29, 1), + (0x3154, 0x10d2c39d, 1), + (0x315c, 0x1d441, 1), + (0x31e0, 0x29, 1), + (0x31e4, 0x24a0, 1), + (0x31e8, 0x4, 1), + (0x31ec, 0x20, 1), + (0x31f0, 0x29, 1), + (0x31f4, 0x10f5cfb4, 1), + (0x31fc, 0x1d4d8, 1), + (0x3230, 0x29, 1), + (0x3234, 0x24ba, 1), + (0x3238, 0x4, 1), + (0x323c, 0x20, 1), + (0x3240, 0x29, 1), + (0x3244, 0x10fe2d62, 1), + (0x324c, 0x1d554, 1), + (0x3280, 0x28, 1), + (0x3284, 0x245c, 1), + (0x3288, 0x4, 1), + (0x328c, 0x20, 1), + (0x3290, 0x28, 1), + (0x3294, 0x10ec3bb1, 1), + (0x329c, 0x1d5bc, 1), + (0x32d0, 0x28, 1), + (0x32d4, 0x246e, 1), + (0x32d8, 0x4, 1), + (0x32dc, 0x20, 1), + (0x32e0, 0x29, 1), + (0x32e4, 0x1101cc20, 1), + (0x32ec, 0x1d640, 1), + (0x3320, 0x28, 1), + (0x3324, 0x2439, 1), + (0x3328, 0x4, 1), + (0x332c, 0x20, 1), + (0x3330, 0x28, 1), + (0x3334, 0x10f01dbd, 1), + (0x333c, 0x1d6dd, 1), + (0x33c0, 0x29, 1), + (0x33c4, 0x2497, 1), + (0x33c8, 0x4, 1), + (0x33cc, 0x20, 1), + (0x33d0, 0x29, 1), + (0x33d4, 0x111077fe, 1), + (0x33dc, 0x1d847, 1), + (0x3460, 0x29, 1), + (0x3464, 0x2480, 1), + (0x3468, 0x4, 1), + (0x346c, 0x20, 1), + (0x3470, 0x29, 1), + (0x3474, 0x110f91ba, 1), + (0x347c, 0x1d8fa, 1), + (0x3500, 0x28, 1), + (0x3504, 0x243a, 1), + (0x3508, 0x4, 1), + (0x350c, 0x20, 1), + (0x3510, 0x28, 1), + (0x3514, 0x10f2bfe8, 1), + (0x351c, 0x1d9a7, 1), + (0x3550, 0x28, 1), + (0x3554, 0x246d, 1), + (0x3558, 0x4, 1), + (0x355c, 0x20, 1), + (0x3560, 0x28, 1), + (0x3564, 0x113b3172, 1), + (0x356c, 0x1da43, 1), + (0x35a0, 0x28, 1), + (0x35a4, 0x2483, 1), + (0x35a8, 0x4, 1), + (0x35ac, 0x20, 1), + (0x35b0, 0x29, 1), + (0x35b4, 0x114027af, 1), + (0x35bc, 0x1dace, 1), + (0x3640, 0x28, 1), + (0x3644, 0x2486, 1), + (0x3648, 0x4, 1), + (0x364c, 0x20, 1), + (0x3650, 0x29, 1), + (0x3654, 0x112421ab, 1), + (0x365c, 0x1db07, 1), + (0x36e0, 0x29, 1), + (0x36e4, 0x24ce, 1), + (0x36e8, 0x4, 1), + (0x36ec, 0x20, 1), + (0x36f0, 0x29, 1), + (0x36f4, 0x114de7b5, 1), + (0x36fc, 0x1dbaa, 1), + (0x3730, 0x29, 1), + (0x3734, 0x24a1, 1), + (0x3738, 0x4, 1), + (0x373c, 0x20, 1), + (0x3740, 0x29, 1), + (0x3744, 0x11342213, 1), + (0x374c, 0x1dc2e, 1), + (0x3780, 0x28, 1), + (0x3784, 0x2446, 1), + (0x3788, 0x4, 1), + (0x378c, 0x20, 1), + (0x3790, 0x28, 1), + (0x3794, 0x111ec867, 1), + (0x379c, 0x1dcb7, 1), + (0x37d0, 0x28, 1), + (0x37d4, 0x2453, 1), + (0x37d8, 0x4, 1), + (0x37dc, 0x20, 1), + (0x37e0, 0x29, 1), + (0x37e4, 0x11273f2d, 1), + (0x37ec, 0x1dd49, 1), + (0x3820, 0x28, 1), + (0x3824, 0x2470, 1), + (0x3828, 0x4, 1), + (0x382c, 0x20, 1), + (0x3830, 0x28, 1), + (0x3834, 0x1131becf, 1), + (0x383c, 0x1ddc8, 1), + (0x38c0, 0x29, 1), + (0x38c4, 0x249c, 1), + (0x38c8, 0x4, 1), + (0x38cc, 0x20, 1), + (0x38d0, 0x29, 1), + (0x38d4, 0x1153d78e, 1), + (0x38dc, 0x1df60, 1), + (0x3960, 0x29, 1), + (0x3964, 0x2484, 1), + (0x3968, 0x4, 1), + (0x396c, 0x20, 1), + (0x3970, 0x29, 1), + (0x3974, 0x114b8bd5, 1), + (0x397c, 0x1dfe3, 1), + (0x3a00, 0x29, 1), + (0x3a04, 0x2522, 1), + (0x3a08, 0x4, 1), + (0x3a0c, 0x20, 1), + (0x3a10, 0x29, 1), + (0x3a14, 0x116ebefa, 1), + (0x3a1c, 0x1e077, 1), + (0x3a50, 0x29, 1), + (0x3a54, 0x2494, 1), + (0x3a58, 0x4, 1), + (0x3a5c, 0x20, 1), + (0x3a60, 0x29, 1), + (0x3a64, 0x11455238, 1), + (0x3a6c, 0x1e0d8, 1), + (0x3aa0, 0x29, 1), + (0x3aa4, 0x24a1, 1), + (0x3aa8, 0x4, 1), + (0x3aac, 0x20, 1), + (0x3ab0, 0x29, 1), + (0x3ab4, 0x1157064b, 1), + (0x3abc, 0x1e165, 1), + (0x3b1c, 0x2522, 1), + (0x3b20, 0x4, 1), + (0x3b24, 0x20, 1), + (0x3b28, 0x29, 1), + (0x3b2c, 0x116cd390, 1), + (0x3b34, 0x1dbf1, 1), + (0x3b40, 0x29, 1), + (0x3b44, 0x34, 1), + (0x3b5c, 0x2a222aa2, 1), + (0x62c4, 0x1000000, 1), + (0x62cc, 0x1010202, 1), + (0x62d0, 0x1010a28, 1), + (0x62d4, 0xa280201, 1), + (0x62d8, 0x2010101, 1), + (0x62dc, 0x1010a28, 1), + (0x62e0, 0xa280101, 1), + (0x62e4, 0x45000101, 1), + (0x62e8, 0x4000200, 1), + (0x62f4, 0x4a000000, 1), + (0x62f8, 0x100, 1), + (0x6304, 0xd7000000, 1), + (0x6308, 0x29, 1), + (0x6328, 0x67325452, 1), + (0x632c, 0x67335452, 1), + (0x6360, 0xf, 1), + (0x63c8, 0x1, 1), + (0x63cc, 0xd88be35c, 1), + (0x63d0, 0x13, 1), + (0x63d8, 0x13, 1), + (0x63dc, 0xd891fc7b, 1), + (0x63e0, 0x13, 1), + (0x63e4, 0xd8633257, 1), + (0x63e8, 0x13, 1), + (0x63ec, 0xd863f8ef, 1), + (0x63f0, 0x13, 1), + (0x63f4, 0xd8903ed2, 1), + (0x63f8, 0x13, 1), + (0x63fc, 0xd89165f9, 1), + (0x6400, 0x13, 1), + (0x6404, 0xd891fc7b, 1), + (0x6408, 0x13, 1), + (0x6414, 0xab2ab62, 1), + (0x6418, 0x7, 1), + (0x641c, 0x30d40, 1), + (0x6428, 0x1, 1), + (0x650c, 0x89778246, 1), +]; diff --git a/drivers/gpu/drm/asahi/hw/t8140.rs b/drivers/gpu/drm/asahi/hw/t8140.rs index 47dc4d583a5fc..3bbc698889997 100644 --- a/drivers/gpu/drm/asahi/hw/t8140.rs +++ b/drivers/gpu/drm/asahi/hw/t8140.rs @@ -51,6 +51,7 @@ pub(crate) const CONFIG: Config = Config { opps: OPPS, uat_quirks: crate::mmu::UatQuirks::DEFAULT, region_c: &crate::g17::fw::initdata::REGION_C_26A428, + chip: None, adopt_root_entries: true, fabric_bridge: true, firmware: crate::g17::fw::variant::Firmware::Fixed(&crate::g17::fw::variant::V26A428), @@ -59,6 +60,7 @@ pub(crate) const CONFIG: Config = Config { cores: None, operand_state: crate::g17::fw::compute::OperandState::ONE_BY_SIX, deflake: crate::g17::fw::render::Deflake::ONE_CLUSTER, + compute_blocks: crate::g17::fw::freelist::COMPUTE_BLOCKS, }, }; diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs index e8b845cba989f..2b04726e43878 100644 --- a/drivers/gpu/drm/asahi/regs.rs +++ b/drivers/gpu/drm/asahi/regs.rs @@ -46,6 +46,16 @@ const FAULT_INFO: usize = 0x17030; const G17_HOST_IRQ_SUMMARY: usize = 0xe01000; const G17_HOST_POWERED: u32 = 0xf; +const KSM_MAILBOX_TAG: usize = 0x4090; +const KSM_MAILBOX_DATA: usize = 0x4098; +const KSM_MAILBOX_FLAGS: u64 = (1 << 42) | (1 << 53); +const KSM_SLOT_DESCRIPTOR: u64 = 0x21008; +const KSM_SLOT_TIMESTAMP: u64 = 0x21020; +const KSM_SELECT: usize = 0x103a8; +const KSM_RUN_SET: usize = 0x21068; +const KSM_RUN_CLEAR: usize = 0x21078; +const KSM_RUNNING: [usize; 2] = [0x21038, 0x21040]; + const ID_VERSION: usize = 0xd04000; const ID_UNK08: usize = 0xd04008; const ID_COUNTS_1: usize = 0xd04010; @@ -602,6 +612,52 @@ impl G17Resources { Ok(true) } + /// Registers a queue KSM slot through the firmware mailbox. + pub(crate) fn register_ksm_queue(&self, qid: u8, descriptor: u64) -> Result { + if qid > 127 { + return Err(EINVAL); + } + let sgx = self.sgx.try_access().ok_or(ENODEV)?; + let sgx = (&*sgx).as_view(); + let tag = |reg: u64| reg | KSM_MAILBOX_FLAGS | (u64::from(qid) << 43); + let bit = 1u64 << (qid & 63); + let (low, high) = if qid < 64 { (bit, 0) } else { (0, bit) }; + let running = KSM_RUNNING[usize::from(qid >= 64)]; + if sgx.try_read64(running)? & bit != 0 { + sgx.try_write64(high, KSM_RUN_CLEAR + 8)?; + sgx.try_write64(low, KSM_RUN_CLEAR)?; + } + sgx.try_write64(tag(KSM_SLOT_DESCRIPTOR), KSM_MAILBOX_TAG)?; + sgx.try_write64(descriptor, KSM_MAILBOX_DATA)?; + sgx.try_write64(tag(KSM_SLOT_TIMESTAMP), KSM_MAILBOX_TAG)?; + sgx.try_read64(KSM_MAILBOX_DATA)?; + sgx.try_write64(tag(KSM_SLOT_DESCRIPTOR), KSM_MAILBOX_TAG)?; + let mut readback = sgx.try_read64(KSM_MAILBOX_DATA)?; + let direct = readback != descriptor; + if direct { + sgx.try_write64(u64::from(qid), KSM_SELECT)?; + sgx.try_write64(descriptor, KSM_SLOT_DESCRIPTOR as usize)?; + readback = sgx.try_read64(KSM_SLOT_DESCRIPTOR as usize)?; + } + if readback != descriptor { + dev_err!( + self.dev.as_ref(), + "KSM queue {} slot descriptor {:#x} did not take (reads {:#x})\n", + qid, + descriptor, + readback + ); + return Err(EIO); + } + if sgx.try_read64(running)? & bit == 0 { + sgx.try_write64(high, KSM_RUN_SET + 8)?; + sgx.try_write64(low, KSM_RUN_SET)?; + } + sgx.try_write64(tag(KSM_SLOT_TIMESTAMP), KSM_MAILBOX_TAG)?; + sgx.try_read64(KSM_MAILBOX_DATA)?; + Ok(()) + } + fn sgx_read32(&self) -> u32 { if let Some(sgx) = self.sgx.try_access() { (&*sgx).as_view().relaxed().read32(OFF) From 91f4fbe136c043dc127f14d27929c98c68654027 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:12:16 -0400 Subject: [PATCH 05/16] drm/asahi: isolate 25F84 recovery and serial-render workarounds Keep the reason-0 and cancellation handling scoped to 25F84, with serial rendering explicitly opt-in. Concurrent-render firmware restarts and complete recovery remain unresolved. Assisted-by: LLM --- drivers/gpu/drm/asahi/asahi_core.rs | 4 ++++ drivers/gpu/drm/asahi/g17/queue/admission.rs | 16 ++++++++++++++++ drivers/gpu/drm/asahi/g17/queue/mod.rs | 10 +++++++++- drivers/gpu/drm/asahi/g17/recovery.rs | 16 +++++++++++++--- .../gpu/drm/asahi/g17/recovery/attribution.rs | 2 ++ drivers/gpu/drm/asahi/g17/runtime.rs | 4 ++++ drivers/gpu/drm/asahi/g17/runtime/events.rs | 4 +++- 7 files changed, 51 insertions(+), 5 deletions(-) diff --git a/drivers/gpu/drm/asahi/asahi_core.rs b/drivers/gpu/drm/asahi/asahi_core.rs index fb7b6e2b042ab..c53abbd8e7ae7 100644 --- a/drivers/gpu/drm/asahi/asahi_core.rs +++ b/drivers/gpu/drm/asahi/asahi_core.rs @@ -216,6 +216,10 @@ module! { default: 1, description: "G16: validate CDM/VDM stream records", }, + t6041_serial_render: u32 { + default: 0, + description: "T6041 25F84 diagnostic workaround: admit one render lease at a time (0: normal concurrency)", + }, t6041_fw_log: u32 { default: 0, description: "T6041 bring-up: enable the firmware's log at boot (main configuration +0x250 = 1, status A debug flags 3)", diff --git a/drivers/gpu/drm/asahi/g17/queue/admission.rs b/drivers/gpu/drm/asahi/g17/queue/admission.rs index ed73323e34e6f..5490825f8b486 100644 --- a/drivers/gpu/drm/asahi/g17/queue/admission.rs +++ b/drivers/gpu/drm/asahi/g17/queue/admission.rs @@ -62,6 +62,7 @@ fn wait_slice_ms(deadline_ns: u64, now_ns: u64) -> Result { } struct State { + serial_render: bool, active: u64, refs: [u8; cfg::RENDER_SLOTS], owners: [u64; cfg::RENDER_SLOTS], @@ -88,6 +89,7 @@ impl State { const MASK: u64 = (u64::MAX >> (64 - cfg::RENDER_SLOTS)) & !1; const fn new() -> Self { Self { + serial_render: false, active: 0, refs: [0; cfg::RENDER_SLOTS], owners: [0; cfg::RENDER_SLOTS], @@ -334,8 +336,15 @@ impl State { ) } + fn serial_busy(&self) -> bool { + self.serial_render && self.refs.iter().any(|&count| count != 0) + } + /// Whether `pick` would return a slot, without taking it. fn can_pick(&self, owner: u64, plan: Plan) -> bool { + if self.serial_busy() { + return false; + } let usable = |slot: usize| { self.owners[slot] == owner && (self.quarantined | self.kill_hold) & (1u64 << slot) == 0 @@ -357,6 +366,9 @@ impl State { } fn pick(&mut self, owner: u64, plan: Plan) -> Option { + if self.serial_busy() { + return None; + } match plan { Plan::Any => self.acquire(owner), Plan::Wait => None, @@ -466,6 +478,10 @@ impl Pool { GFP_KERNEL, ) } + pub(crate) fn serialize_render(&self) { + self.state.lock().serial_render = true; + } + /// Every pair or lease return, owner close and quarantine change. fn returned(&self) { self.released.notify_all(); diff --git a/drivers/gpu/drm/asahi/g17/queue/mod.rs b/drivers/gpu/drm/asahi/g17/queue/mod.rs index d22c0f617f374..128b5d7e0d99e 100644 --- a/drivers/gpu/drm/asahi/g17/queue/mod.rs +++ b/drivers/gpu/drm/asahi/g17/queue/mod.rs @@ -158,6 +158,7 @@ impl Queue { fn prune_frontier(&mut self, index: usize) { let failure = &mut self.failures[index]; + let transient_ok = self.variant.is_25f84(); // Without room the entries are released here, as they always could be. let mut finished = if self.finished.reserve(self.frontiers[index].len(), GFP_KERNEL).is_ok() { Some(&mut self.finished) @@ -174,7 +175,14 @@ impl Queue { } // One earliest failed ordinal covers every later prefix without // retaining its packet, mappings, or every failed fence. - if status < 0 && failure.as_ref().is_none_or(|(first, _)| *sequence < *first) { + let transient = transient_ok + && [ECANCELED, ENODATA, EBUSY] + .iter() + .any(|error| error.to_errno() == status); + if status < 0 + && !transient + && failure.as_ref().is_none_or(|(first, _)| *sequence < *first) + { *failure = Some((*sequence, fences.completed.clone())); } false diff --git a/drivers/gpu/drm/asahi/g17/recovery.rs b/drivers/gpu/drm/asahi/g17/recovery.rs index dcd8d59e4c412..8e815d3a3c737 100644 --- a/drivers/gpu/drm/asahi/g17/recovery.rs +++ b/drivers/gpu/drm/asahi/g17/recovery.rs @@ -285,7 +285,8 @@ pub(super) fn service( host.recovery_mut().pending = None; let (listed, exact, fallback) = (entries.listed, entries.exact, entries.fallback); - let (cancelled, blame) = classify(host, entries, request.blamed, attribution); + let lenient = sources.spare_reason_zero && sources.reason == Some(0); + let (cancelled, blame) = classify(host, entries, request.blamed, attribution, lenient); if listed != 0 { pr_warn!( "G17: GPU recovery: {} commands were in flight ({} matched by stamp, {} by queue); they fail instead of completing\n", @@ -340,7 +341,7 @@ fn read_entries(host: &mut H) -> Entries { /// Also returns whether the firmware's blamed QID is still guilty after attribution: a blamed /// render that had not started while others had (or with no culprit at all) is innocent, and its /// work is spared like that of every other innocent VM instead of failing with EIO. -fn classify(queues: &mut Q, entries: Entries, blamed: Option, attribution: Attribution) -> (u128, bool) { +fn classify(queues: &mut Q, entries: Entries, blamed: Option, attribution: Attribution, lenient: bool) -> (u128, bool) { let mut guilty_qids = entries.guilty | blamed.map_or(0, |qid| 1u128 << qid); let cancelled = entries.cancelled; match attribution { @@ -389,6 +390,9 @@ fn classify(queues: &mut Q, entries: Entries, blamed: Option, att if unresolved || (count == 0 && collateral == 0 && !no_culprit) { return (0, blame); } + if lenient && count != 0 { + pr_warn!("G17: GPU recovery reason 0 is not a decoded fault; a first-time blamed VM stays usable\n"); + } let spared = queues.classify_work(&|status| { guilty[..count] .iter() @@ -396,7 +400,13 @@ fn classify(queues: &mut Q, entries: Entries, blamed: Option, att known .as_ref() .filter(|known| Arc::ptr_eq(known, status)) - .map(|_| Class::Guilty) + .map(|_| { + if lenient && status.reported_error() == 0 { + Class::Spared + } else { + Class::Guilty + } + }) }) .unwrap_or(Class::Spared) }); diff --git a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs index 38ad8612a0b99..fc910df58b370 100644 --- a/drivers/gpu/drm/asahi/g17/recovery/attribution.rs +++ b/drivers/gpu/drm/asahi/g17/recovery/attribution.rs @@ -17,6 +17,7 @@ pub(super) struct Source { pub(crate) struct Sources { pub(crate) reason: Option, + pub(crate) spare_reason_zero: bool, mask: u32, records: [Source; SLOTS], count: usize, @@ -52,6 +53,7 @@ impl Sources { pub(crate) fn sample(mask: u32, mut read: impl FnMut(usize) -> Option<(u64, u32)>) -> Self { let mut sources = Self { reason: None, + spare_reason_zero: false, mask, records: [Source::default(); SLOTS], count: 0, diff --git a/drivers/gpu/drm/asahi/g17/runtime.rs b/drivers/gpu/drm/asahi/g17/runtime.rs index fb25209c908dd..605397ec795cf 100644 --- a/drivers/gpu/drm/asahi/g17/runtime.rs +++ b/drivers/gpu/drm/asahi/g17/runtime.rs @@ -215,6 +215,10 @@ impl Registry { qos_ids: QosIds::new()?, admission: { let admission = admission::Pool::with_waiters(kick::queue_limit(variant.qid_floor), queues, renders)?; + if variant.is_25f84() && crate::module_parameters::t6041_serial_render.value() != 0 { + admission.serialize_render(); + pr_warn!("T6041 25F84: serial render admission enabled (one outstanding render lease)\n"); + } admission }, render: render::RenderState::new(clusters, deflake, descriptor_flags)?, diff --git a/drivers/gpu/drm/asahi/g17/runtime/events.rs b/drivers/gpu/drm/asahi/g17/runtime/events.rs index 74eb6b7228c72..2c8f38f103a79 100644 --- a/drivers/gpu/drm/asahi/g17/runtime/events.rs +++ b/drivers/gpu/drm/asahi/g17/runtime/events.rs @@ -134,7 +134,9 @@ impl recovery::Memory for Service<'_> { self.firmware.init.host_recovery() } fn fault_sources(&self) -> Result { - self.firmware.init.fault_sources() + let mut sources = self.firmware.init.fault_sources()?; + sources.spare_reason_zero = self.firmware.variant.is_25f84(); + Ok(sources) } fn transition(&self, expected: u32, next: u32) -> Result { From f3492cd2470e1870472a346bfe5256c885b57e86 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:12:29 -0400 Subject: [PATCH 06/16] drm/apple: support T6041 desktop 26.5.2 DCP firmware Add gated 26.5.2 firmware acceptance and headerless powerlog parsing for the T6041 display controller. Assisted-by: LLM --- drivers/gpu/drm/apple/dcp.c | 14 +++++++++++++ drivers/gpu/drm/apple/parser.c | 36 ++++++++++++++++++++++++++++++++ drivers/gpu/drm/apple/parser.h | 2 ++ drivers/gpu/drm/apple/systemep.c | 8 +++++++ 4 files changed, 60 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index b6af560ffc50a..7b3a9a304f369 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -238,6 +238,11 @@ bool dcp_t6041_ext_enabled(void) #include "iomfb_v26_6_swap.h" #include "dcp-peer-v26_6.inc" +static bool t6041_fw_26_5; +module_param(t6041_fw_26_5, bool, 0444); +MODULE_PARM_DESC(t6041_fw_26_5, + "T6041: drive DCP firmware 26.5.2 with the desktop 26.6 interface (unqualified)"); + /* * The backlight maximum of a desktop 26.x panel whose node has no * apple,max-brightness: the J616s panel's user-accessible SDR maximum (ADT @@ -4623,6 +4628,15 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (ret >= 0 && compat_ret >= 0 && !strcmp(fw_str, "26.6.2") && !strcmp(compat_str, "26.6.2")) return DCP_FIRMWARE_V_26_6_DESKTOP; + if (ret >= 0 && compat_ret >= 0 && !strcmp(fw_str, "26.5.2") && + !strcmp(compat_str, "26.5.2")) { + if (t6041_fw_26_5) { + dev_warn(dev, "T6041 DCP firmware 26.5.2 is unqualified; trying the desktop 26.6 interface\n"); + return DCP_FIRMWARE_V_26_6_DESKTOP; + } + dev_err(dev, "T6041 display not started: DCP firmware 26.5.2 is unqualified (appledrm.t6041_fw_26_5=1 tries it)\n"); + return DCP_FIRMWARE_UNKNOWN; + } dev_err(dev, "T6041 display not started: DCP firmware %s (compat %s) is not 26.6.2\n", ret >= 0 ? fw_str : "missing", compat_ret >= 0 ? compat_str : "missing"); diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c index e5a8c8e6cc700..eafbe6cac84d6 100644 --- a/drivers/gpu/drm/apple/parser.c +++ b/drivers/gpu/drm/apple/parser.c @@ -2159,3 +2159,39 @@ int parse_panel_edid_data(const void *blob, size_t len, const u8 **edid, size_t ret = parse(blob, len, &ctx); return ret ?: parse_panel_edid_value(&ctx, 0, &budget, true, edid, size); } +int parse_system_log_mnits_pairs(const void *blob, size_t size, + struct dcp_system_ev_mnits *entry) +{ + struct dcp_parse_ctx ctx = { .blob = blob, .len = size, .pos = 0 }; + s64 mnits = -1, idac = -1, timestamp = 0; + int ret = 0; + + if (size > U32_MAX) + return -EINVAL; + + while (round_up(ctx.pos, 4) + sizeof(u32) <= size) { + char *key = parse_string(&ctx); + + if (IS_ERR(key)) + break; + if (!strcmp(key, "mNits")) + ret = parse_int(&ctx, &mnits); + else if (!strcmp(key, "iDAC")) + ret = parse_int(&ctx, &idac); + else if (!strcmp(key, "timestamp")) + ret = parse_int(&ctx, ×tamp); + else + ret = skip(&ctx); + kfree(key); + if (ret) + break; + } + + if (mnits < 0 || mnits > U32_MAX) + return -EINVAL; + + entry->millinits = mnits; + entry->idac = idac < 0 ? 0 : idac; + entry->timestamp = timestamp; + return 0; +} diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h index 5e573371e0072..e2cf7b3f86ca8 100644 --- a/drivers/gpu/drm/apple/parser.h +++ b/drivers/gpu/drm/apple/parser.h @@ -255,5 +255,7 @@ struct dcp_system_ev_mnits { int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry); +int parse_system_log_mnits_pairs(const void *blob, size_t size, + struct dcp_system_ev_mnits *entry); #endif diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 44e239c6016b0..392c2ca58f312 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -87,6 +87,13 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype ret = parse(data, data_size, &parse_ctx); if (ret) { + if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && + !parse_system_log_mnits_pairs(data, data_size, &mnits)) { + dev_info(dcp->dev, "systemep: powerlog mNits %u.%03u iDAC %u\n", + mnits.millinits / 1000, mnits.millinits % 1000, + mnits.idac); + goto have_mnits; + } dev_warn(service->ep->dcp->dev, "systemep: failed to parse report: %d\n", ret); return ret; } @@ -100,6 +107,7 @@ static int powerlog_report(struct apple_epic_service *service, enum epic_subtype dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n", mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac); +have_mnits: if ((dcp->fw_compat == DCP_FIRMWARE_H17P && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) || dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { From 3ca17e7bc31f68ca54943acf0068ab656c36b3f6 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:12:36 -0400 Subject: [PATCH 07/16] drm/apple: wait for flip completion before retiring a commit Wait for the flip notification before completing commit cleanup so framebuffer ownership follows scanout completion. Assisted-by: LLM --- drivers/gpu/drm/apple/apple_drv.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 8fc8cb7f7a6ab..5744a4a8a9743 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -470,7 +470,14 @@ static void apple_atomic_commit_tail(struct drm_atomic_commit *state) dcp_retain_framebuffer(to_apple_crtc(old->crtc)->dcp, entry); } - drm_atomic_helper_commit_tail_rpm(state); + drm_atomic_helper_commit_modeset_disables(state->dev, state); + drm_atomic_helper_commit_modeset_enables(state->dev, state); + drm_atomic_helper_commit_planes(state->dev, state, + DRM_PLANE_COMMIT_ACTIVE_ONLY); + drm_atomic_helper_fake_vblank(state); + drm_atomic_helper_commit_hw_done(state); + drm_atomic_helper_wait_for_flip_done(state->dev, state); + drm_atomic_helper_cleanup_planes(state->dev, state); } static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = { From 3db1129407bea190ffa6953d416f1bb12bdd7a33 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:23:29 -0400 Subject: [PATCH 08/16] soc: apple: use explicit word access for T6041 AOP bootargs Give bootarg access width its own hardware setting and patch through one aligned buffer. Preserve surrounding bytes and verify changed words. Synthetic MMIO checks cover malformed tables, bounds and failed readback. Assisted-by: LLM --- drivers/soc/apple/aop.rs | 46 ++++++++++++++- tools/asahi/aop-bootargs-tests.py | 98 +++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 tools/asahi/aop-bootargs-tests.py diff --git a/drivers/soc/apple/aop.rs b/drivers/soc/apple/aop.rs index c59719d048158..ef91dec09df76 100644 --- a/drivers/soc/apple/aop.rs +++ b/drivers/soc/apple/aop.rs @@ -1585,16 +1585,34 @@ impl AopData { &self, aop_mmio: &Mmio<'_, Region>, patches: &[(u32, u64)], + word_access: bool, ) -> Result<()> { let offset = aop_mmio.read32(BOOTARGS_OFFSET) as usize; let size = aop_mmio.read32(BOOTARGS_SIZE) as usize; if offset > aop_mmio.size() || size > aop_mmio.size() - offset { return Err(EINVAL); } - let mut arg_bytes = KVec::::from_elem(0, size, GFP_KERNEL)?; + let start = if word_access { offset & !3 } else { offset }; + let end = if word_access { + (offset + size).checked_add(3).ok_or(EINVAL)? & !3 + } else { + offset + size + }; + if end > aop_mmio.size() || (word_access && size == 0) { + return Err(EINVAL); + } + let mut bytes = KVec::::from_elem(0, end - start, GFP_KERNEL)?; let aop_io_array: Mmio<'_, [u8; AOP_MMIO_SIZE]> = aop_mmio.try_cast()?; let bootargs = io_project!(aop_io_array, [try: offset .. offset+size]); - bootargs.copy_to_slice(&mut arg_bytes); + if word_access { + for (i, chunk) in bytes.chunks_exact_mut(4).enumerate() { + chunk.copy_from_slice(&aop_mmio.try_read32(start + i * 4)?.to_le_bytes()); + } + } else { + bootargs.copy_to_slice(&mut bytes); + } + let first = offset - start; + let arg_bytes = &mut bytes[first..first + size]; let mut idx = 0; while idx < size { if size - idx < 8 { @@ -1618,7 +1636,21 @@ impl AopData { } idx += len; } - bootargs.copy_from_slice(&arg_bytes); + if word_access { + for (i, chunk) in bytes.chunks_exact(4).enumerate() { + let address = start + i * 4; + let value = u32::from_le_bytes(chunk.try_into().unwrap()); + if aop_mmio.try_read32(address)? == value { + continue; + } + aop_mmio.try_write32(value, address)?; + if aop_mmio.try_read32(address)? != value { + return Err(EIO); + } + } + } else { + bootargs.copy_from_slice(arg_bytes); + } Ok(()) } @@ -2519,6 +2551,7 @@ struct AopHwConfig { /// and lists in `memory-region`; check their IOMMU mappings before /// starting it (T6040). fw_regions: bool, + bootargs_word_access: bool, /// Halt the co-processor again when its start fails (T6040). halt_on_failed_start: bool, } @@ -2533,6 +2566,7 @@ const HW_CFG_T8103: AopHwConfig = AopHwConfig { quiesce_on_unbind: false, required_endpoints: &[], fw_regions: false, + bootargs_word_access: false, halt_on_failed_start: false, }; const HW_CFG_T8112: AopHwConfig = AopHwConfig { @@ -2545,6 +2579,7 @@ const HW_CFG_T8112: AopHwConfig = AopHwConfig { quiesce_on_unbind: false, required_endpoints: &[], fw_regions: false, + bootargs_word_access: false, halt_on_failed_start: false, }; const HW_CFG_T6000: AopHwConfig = AopHwConfig { @@ -2557,6 +2592,7 @@ const HW_CFG_T6000: AopHwConfig = AopHwConfig { quiesce_on_unbind: false, required_endpoints: &[], fw_regions: false, + bootargs_word_access: false, halt_on_failed_start: false, }; const HW_CFG_T6020: AopHwConfig = AopHwConfig { @@ -2569,6 +2605,7 @@ const HW_CFG_T6020: AopHwConfig = AopHwConfig { quiesce_on_unbind: false, required_endpoints: &[], fw_regions: false, + bootargs_word_access: false, halt_on_failed_start: false, }; /// T6040 (J616s): macOS writes the same four boot arguments as for the @@ -2585,6 +2622,7 @@ const HW_CFG_T6040: AopHwConfig = AopHwConfig { quiesce_on_unbind: true, required_endpoints: &[], fw_regions: true, + bootargs_word_access: true, halt_on_failed_start: true, }; /// T8140: the firmware is started with the boot arguments the bootloader @@ -2602,6 +2640,7 @@ const HW_CFG_T8140: AopHwConfig = AopHwConfig { quiesce_on_unbind: true, required_endpoints: &[0x20, 0x21, 0x22, 0x23, 0x2b], fw_regions: false, + bootargs_word_access: false, halt_on_failed_start: false, }; @@ -2661,6 +2700,7 @@ impl platform::Driver for AopDriver { (from_fourcc(b"alig"), cfg.alig), (from_fourcc(b"AOPt"), cfg.aopt), ], + cfg.bootargs_word_access, )?; } let rtkit = rtkit::RtKit::::new(pdev.as_ref(), None, 0, data.clone())?; diff --git a/tools/asahi/aop-bootargs-tests.py b/tools/asahi/aop-bootargs-tests.py new file mode 100644 index 0000000000000..aba3842077664 --- /dev/null +++ b/tools/asahi/aop-bootargs-tests.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise production bootarg patching with synthetic MMIO.""" +import argparse +import os +from pathlib import Path +import subprocess +import tempfile + +HARNESS = r''' +#![allow(dead_code)] +use std::{cell::{Cell,RefCell},marker::PhantomData,mem,ops::{Deref,DerefMut}}; +type Result = std::result::Result; +const EINVAL:i32=-22; const EIO:i32=-5; const GFP_KERNEL:u32=0; +const AOP_MMIO_SIZE:usize=128; const BOOTARGS_OFFSET:usize=16; const BOOTARGS_SIZE:usize=20; +struct KVec(Vec); +impl KVec{fn from_elem(v:T,n:usize,_:u32)->Result{Ok(Self(vec![v;n]))}} +impl Deref for KVec{type Target=Vec;fn deref(&self)->&Vec{&self.0}} +impl DerefMut for KVec{fn deref_mut(&mut self)->&mut Vec{&mut self.0}} +struct Region; +struct Device{bytes:RefCell>,writes:RefCell>,fail:Cell} +struct Mmio<'a,T>{dev:&'a Device,_p:PhantomData} +impl<'a,T> Mmio<'a,T>{ + fn size(&self)->usize{self.dev.bytes.borrow().len()} + fn read32(&self,at:usize)->u32{self.try_read32(at).unwrap()} + fn try_read32(&self,at:usize)->Result{assert_eq!(at%4,0);Ok(u32::from_le_bytes(self.dev.bytes.borrow()[at..at+4].try_into().unwrap()))} + fn try_write32(&self,v:u32,at:usize)->Result{assert_eq!(at%4,0);self.dev.writes.borrow_mut().push(at);if !self.dev.fail.get(){self.dev.bytes.borrow_mut()[at..at+4].copy_from_slice(&v.to_le_bytes());}Ok(())} + fn try_cast(&self)->Result>{Ok(Mmio{dev:self.dev,_p:PhantomData})} +} +struct IoSlice<'a>{dev:&'a Device,start:usize,end:usize} +impl IoSlice<'_>{ + fn copy_to_slice(&self,v:&mut[u8]){v.copy_from_slice(&self.dev.bytes.borrow()[self.start..self.end]);} + fn copy_from_slice(&self,v:&[u8]){self.dev.bytes.borrow_mut()[self.start..self.end].copy_from_slice(v);} +} +macro_rules! io_project {($io:ident,[try: $start:ident .. $($end:tt)*])=>{IoSlice{dev:$io.dev,start:$start,end:$($end)*}};} +struct Aop; +impl Aop { METHOD } +fn fixture(offset:usize,table:&[u8])->Device{ + let mut bytes=vec![0xa5;AOP_MMIO_SIZE];bytes[16..20].copy_from_slice(&(offset as u32).to_le_bytes());bytes[20..24].copy_from_slice(&(table.len() as u32).to_le_bytes()); + if offset+table.len()<=bytes.len(){bytes[offset..offset+table.len()].copy_from_slice(table);} + Device{bytes:RefCell::new(bytes),writes:RefCell::new(Vec::new()),fail:Cell::new(false)} +} +fn patch(dev:&Device,word:bool)->Result{Aop.patch_bootargs(&Mmio{dev,_p:PhantomData::>},&[(1,0x12345678),(2,0x87654321)],word)} +fn main(){ + let table:[u8;24]=[1,0,0,0,4,0,0,0,0,0,0,0,2,0,0,0,4,0,0,0,0,0,0,0]; + for offset in 32..36 {for word in [false,true] { + let d=fixture(offset,&table);let mut expected=d.bytes.borrow().clone();expected[offset+8..offset+12].copy_from_slice(&0x12345678u32.to_le_bytes());expected[offset+20..offset+24].copy_from_slice(&0x87654321u32.to_le_bytes()); + assert_eq!(patch(&d,word),Ok(()));assert_eq!(*d.bytes.borrow(),expected,"adjacent bytes changed"); + assert!(d.writes.borrow().iter().all(|&x|x%4==0&&x>=offset&!3&&x+4<=(offset+table.len()+3)&!3)); + if word {assert!(!d.writes.borrow().is_empty());}else{assert!(d.writes.borrow().is_empty());} + let writes=d.writes.borrow().len();assert_eq!(patch(&d,word),Ok(()));assert_eq!(d.writes.borrow().len(),writes,"unchanged words rewritten"); + }} + let mut malformed=table;malformed[16..20].copy_from_slice(&9u32.to_le_bytes()); + for word in [false,true]{let d=fixture(35,&malformed);let before=d.bytes.borrow().clone();assert_eq!(patch(&d,word),Err(EINVAL));assert_eq!(*d.bytes.borrow(),before);assert!(d.writes.borrow().is_empty());} + for tail in 1..8 {let d=fixture(35,&table[..12+tail]);assert_eq!(patch(&d,true),Err(EINVAL));assert!(d.writes.borrow().is_empty());} + let d=fixture(35,&table);d.fail.set(true);assert_eq!(patch(&d,true),Err(EIO),"readback error ignored"); + assert_eq!(patch(&fixture(35,&[]),true),Err(EINVAL));assert_eq!(patch(&fixture(35,&[]),false),Ok(())); + assert_eq!(patch(&fixture(120,&table),true),Err(EINVAL)); + println!("PASS aligned/unaligned byte and word access, unchanged writes, malformed tables, bounds and failed readback"); +} +''' + + +def method(source): + start = source.index(' fn patch_bootargs(') + at = source.index('{', start) + depth = 1 + end = at + 1 + while depth: + depth += (source[end] == '{') - (source[end] == '}') + end += 1 + return source[start:end] + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--source', type=Path, default=Path(__file__).resolve().parents[2] / 'drivers/soc/apple/aop.rs') + args = parser.parse_args() + production = method(args.source.read_text()) + macros = ''.join('macro_rules! ' + name + ' {($($t:tt)*)=>{()};}\n' for name in ['dev_info', 'dev_err'] if name + '!' in production) + code = HARNESS.replace('METHOD', production).replace('struct Aop;', macros + 'struct Aop;') + with tempfile.TemporaryDirectory(prefix='aop-bootargs-') as tmp: + p = Path(tmp) / 'test.rs' + binary = p.with_suffix('') + p.write_text(code) + subprocess.run([os.environ.get('RUSTC', 'rustc'), '--edition=2021', str(p), '-o', str(binary)], check=True) + subprocess.run([str(binary)], check=True) + bad = code.replace('if aop_mmio.try_read32(address)? != value {', 'if false {') + assert bad != code + p.write_text(bad) + subprocess.run([os.environ.get('RUSTC', 'rustc'), '--edition=2021', str(p), '-o', str(binary)], check=True) + result = subprocess.run([str(binary)], capture_output=True, text=True) + assert result.returncode != 0 and 'readback error ignored' in result.stderr + print('PASS negative control: missing write verification') + + +if __name__ == '__main__': + main() From 285fe12ac4db0005ae5ce64fa3cfd23670fdaa3d Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:24:57 -0400 Subject: [PATCH 09/16] ASoC: apple: add protected J616c AOP audio Describe the six amplifiers and headphone jack in the J616c board DT. Stop playback when the protection lease is lost. Assisted-by: LLM --- arch/arm64/boot/dts/apple/t6041-j616c.dts | 203 ++++++++++++++++++++++ sound/soc/apple/macaudio.c | 5 +- 2 files changed, 207 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t6041-j616c.dts b/arch/arm64/boot/dts/apple/t6041-j616c.dts index 61d14c1f9694c..813a14be78d3c 100644 --- a/arch/arm64/boot/dts/apple/t6041-j616c.dts +++ b/arch/arm64/boot/dts/apple/t6041-j616c.dts @@ -684,3 +684,206 @@ apple,afr-opp = <&gpu_afr_opp>; apple,firmware-version = <26 5 2>; }; + +/ { + aliases { + aop = &aop; + }; + speaker_sdz: regulator-sn012776-sdz { + compatible = "regulator-fixed"; + regulator-name = "sn012776-sdz"; + gpios = <&pinctrl 54 GPIO_ACTIVE_HIGH>; + enable-active-high; + }; + + sound: sound { + compatible = "apple,j616-macaudio", "apple,macaudio"; + model = "MacBook Pro J616"; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Speakers"; + dai-tdm-idle-mode-0 = "zero"; + dai-tdm-idle-mode-1 = "zero"; + dai-tdm-slot-tx-idle-mask-0 = <0xf0f0f0>; + dai-tdm-slot-tx-idle-mask-1 = <0x0f0f0f>; + + cpu { + sound-dai = <&aop_audio 3>; + }; + + codec { + sound-dai = <&speaker_left_woof1>, <&speaker_right_woof1>, + <&speaker_left_woof2>, <&speaker_right_woof2>, + <&speaker_left_tweet>, <&speaker_right_tweet>; + }; + }; + }; + + sound_jack: sound-jack { + compatible = "apple,j616-macaudio-jack", "apple,macaudio"; + model = "MacBook Pro J616 Jack"; + #address-cells = <1>; + #size-cells = <0>; + + dai-link@0 { + reg = <0>; + link-name = "Headphone Jack"; + + cpu { + sound-dai = <&aop_audio 4>; + }; + + codec { + sound-dai = <&jack_codec>; + }; + }; + }; +}; + +&aop { + status = "okay"; +}; + +&aop_mbox { + status = "okay"; +}; + +&aop_dart { + status = "okay"; +}; + +&admac0 { + status = "okay"; +}; + +&admac1 { + status = "okay"; +}; + +&aop_audio { + apple,chassis-name = "J616"; + apple,machine-kind = "MacBook Pro"; +}; + +&i2c1 { + status = "okay"; + + speaker_left_woof1: codec@38 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x38>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 1"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <0>; + ti,vmon-slot-no = <2>; + }; + + speaker_left_woof2: codec@39 { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x39>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Woofer 2"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <8>; + ti,vmon-slot-no = <10>; + }; + + speaker_left_tweet: codec@3a { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3a>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Left Tweeter"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <16>; + ti,vmon-slot-no = <18>; + }; +}; + +&i2c3 { + status = "okay"; + + speaker_right_woof1: codec@3b { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3b>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 1"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <4>; + ti,vmon-slot-no = <6>; + }; + + speaker_right_woof2: codec@3c { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3c>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Woofer 2"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <12>; + ti,vmon-slot-no = <14>; + }; + + speaker_right_tweet: codec@3d { + compatible = "ti,sn012776", "ti,tas2764"; + reg = <0x3d>; + SDZ-supply = <&speaker_sdz>; + apple,sdz-group-fail-closed; + #sound-dai-cells = <0>; + sound-name-prefix = "Right Tweeter"; + interrupts-extended = <&pinctrl 55 IRQ_TYPE_LEVEL_LOW>; + ti,imon-slot-no = <20>; + ti,vmon-slot-no = <22>; + }; +}; + +&i2c2 { + status = "okay"; + + jack_codec: codec@4b { + compatible = "cirrus,cs42l84"; + reg = <0x4b>; + reset-gpios = <&pinctrl_nub 58 GPIO_ACTIVE_HIGH>; + #sound-dai-cells = <0>; + interrupts-extended = <&pinctrl 106 IRQ_TYPE_LEVEL_LOW>; + sound-name-prefix = "Jack"; + }; +}; + +&aop { nonposted-mmio; }; + +&aop_als { status = "disabled"; }; +&aop_las { status = "disabled"; }; + +&admac1 { dma-channels = <26>; nonposted-mmio; }; +&admac0 { nonposted-mmio; }; +&aop_dart { nonposted-mmio; }; +&i2c1 { nonposted-mmio; }; +&i2c2 { nonposted-mmio; }; +&i2c3 { nonposted-mmio; }; + +/ { + reserved-memory { + aop_mmio_pmgr: aop-mmio-pmgr@508304000 { + reg = <0x5 0x08304000 0x0 0x4000>; + iommu-addresses = <&aop 0x5 0x08304000 0x0 0x4000>; + no-map; + }; + }; +}; + +&aop { + dma-noncoherent; + memory-region = <&aop_mmio_pmgr>; +}; diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c index 1d1c8924eb2fb..32b567836594a 100644 --- a/sound/soc/apple/macaudio.c +++ b/sound/soc/apple/macaudio.c @@ -512,6 +512,9 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma) atomic_inc(&ma->sw_gain_lock_epoch); schedule_work(&ma->sw_gain_stop_work); } + if (!unlock && !ma->pm_quiesced && ma->cfg->fe_cpu_is_platform && + ma->cfg->amp == AMP_SN012776) + schedule_work(&ma->sw_gain_stop_work); ma->speaker_volume_unlocked = unlock; snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &ma->speaker_lock_kctl->id); @@ -665,7 +668,7 @@ static void macaudio_sw_gain_stop_work(struct work_struct *work) struct macaudio_snd_data *ma = container_of(work, struct macaudio_snd_data, sw_gain_stop_work); - macaudio_stop_playback(ma, true); + macaudio_stop_playback(ma, ma->cfg->amp == AMP_MAX98360A); } static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target, From 5f7fc7bdac48106ceb0e8b68808fe5740f7db58b Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:25:10 -0400 Subject: [PATCH 10/16] media: apple: avd: admit qualified HEVC on J616c Limit admission to the qualified J616c machine and firmware layout. Apply the capability per device without allocating or changing the shared variant descriptor. Assisted-by: LLM --- drivers/media/platform/apple/avd/avd-drv.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index bf5bf96f4c62a..b0e593a59a700 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -11,6 +11,7 @@ * Copyright (C) 2011 Samsung Electronics Co., Ltd. */ +#include #include #include #include @@ -870,8 +871,17 @@ static const struct of_device_id avd_of_match[] = { MODULE_DEVICE_TABLE(of, avd_of_match); +static bool avd_t6041_hevc_supported(struct avd_dev *avd) +{ + return of_machine_is_compatible("apple,j616c") && + avd->variant == &avd_t6040_variant && + (avd->variant->quirks & AVD_QUIRK_HEVC_V5_HEADER) && + avd->fw->size == 0x12000; +} + static int avd_probe(struct platform_device *pdev) { + unsigned int variant_caps; struct avd_dev *avd; const struct of_device_id *match; const char *codec_info = NULL; @@ -919,12 +929,15 @@ static int avd_probe(struct platform_device *pdev) return ret; } + variant_caps = avd->variant->capabilities; + if (avd_t6041_hevc_supported(avd)) + variant_caps |= AVD_CAPABILITY_HEVC; + /* Determine firmware and HW variant capabilities */ if (avd->fw->size >= AVD_CODEC_INFO_MAX_SIZE) codec_info = avd->fw->data + avd->fw->size - AVD_CODEC_INFO_MAX_SIZE; if (codec_info && strncmp(codec_info, "CODECS:", 7) == 0) { unsigned int caps = 0; - unsigned int variant_caps = avd->variant->capabilities; if (strnstr(codec_info, "av1", AVD_CODEC_INFO_MAX_SIZE)) caps |= variant_caps & AVD_CAPABILITY_AV1; @@ -940,7 +953,7 @@ static int avd_probe(struct platform_device *pdev) avd->capabilities = caps; } else { /* Backward compatibility to old firmware, do not upstream. */ - avd->capabilities = avd->variant->capabilities; + avd->capabilities = variant_caps; } ret = dma_set_mask_and_coherent(avd->dev, From 1ee76914e83b232e692f724ab89e27a66d41e35d Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:25:22 -0400 Subject: [PATCH 11/16] watchdog: apple: set ownership and honour nowayout Register the watchdog against its platform device and apply the configured nowayout policy. Assisted-by: LLM --- drivers/watchdog/apple_wdt.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/watchdog/apple_wdt.c b/drivers/watchdog/apple_wdt.c index f533953294f62..e1b8fa6a83972 100644 --- a/drivers/watchdog/apple_wdt.c +++ b/drivers/watchdog/apple_wdt.c @@ -53,6 +53,11 @@ #define APPLE_WDT_TIMEOUT_DEFAULT 30 +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + struct apple_wdt { struct watchdog_device wdd; void __iomem *regs; @@ -178,6 +183,7 @@ static int apple_wdt_probe(struct platform_device *pdev) platform_set_drvdata(pdev, wdt); + wdt->wdd.parent = dev; wdt->wdd.ops = &apple_wdt_ops; wdt->wdd.info = &apple_wdt_info; wdt->wdd.max_hw_heartbeat_ms = U32_MAX / wdt->clk_rate * 1000; @@ -187,6 +193,7 @@ static int apple_wdt_probe(struct platform_device *pdev) if (wdt_ctrl & APPLE_WDT_CTRL_RESET_EN) set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); + watchdog_set_nowayout(&wdt->wdd, nowayout); watchdog_init_timeout(&wdt->wdd, 0, dev); apple_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout); watchdog_stop_on_unregister(&wdt->wdd); From fc821a82ee210626ffa3248999ebca81cabe9a51 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Wed, 28 Oct 2026 17:25:33 -0400 Subject: [PATCH 12/16] tools: check T6041 initialization data against explicit captures Build the production initialization objects with their soft-float types and compare constants with explicit capture inputs. Keep T6040 per-chip calibration validation separate; include no captures or local defaults. Assisted-by: LLM --- tools/asahi/g17-t6041-initdata-tests.py | 322 ++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 tools/asahi/g17-t6041-initdata-tests.py diff --git a/tools/asahi/g17-t6041-initdata-tests.py b/tools/asahi/g17-t6041-initdata-tests.py new file mode 100644 index 0000000000000..34815f80cf383 --- /dev/null +++ b/tools/asahi/g17-t6041-initdata-tests.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Check T6041 initialization data against caller-supplied captures.""" + +import argparse +import re +import struct +import subprocess +import sys +import tempfile +from pathlib import Path + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + +def item(source, start): + """Extract an item and its attributes.""" + at = source.index(start) + line = source.rfind("\n", 0, at) + 1 + while True: + previous = source.rfind("\n", 0, line - 1) + 1 + if line == 0 or not source[previous:line].lstrip().startswith("#["): + break + line = previous + depth = 0 + end = at + while True: + c = source[end] + if c in "([{": + depth += 1 + elif c in ")]}": + depth -= 1 + if depth == 0 and c == "}" and not re.match( + r"(pub\(crate\) )?(const|static) (?!fn )", source[at:] + ): + return source[line : end + 1] + elif c == ";" and depth == 0: + return source[line : end + 1] + end += 1 + + +HEADER = r""" +#![allow(dead_code, unused_imports, unused_variables, clippy::all)] +use core::mem::size_of; +#[derive(Debug)] +pub struct Error(i32); +pub const EINVAL: Error = Error(-22); +pub const EOVERFLOW: Error = Error(-75); +impl From for Error { + fn from(_: core::num::TryFromIntError) -> Self { EINVAL } +} +pub type Result = core::result::Result; +pub unsafe trait Zeroable {} +macro_rules! static_assert { ($e:expr $(,)?) => { const _: () = assert!($e); }; } +pub fn clear(object: &mut T) { + unsafe { core::ptr::write_bytes(object as *mut T, 0, 1) } +} +const PAGE_SIZE: usize = 0x4000; +""" + +INITDATA_ITEMS = [ + "pub(crate) const REGISTER_MAP_SLOTS", + "pub(crate) struct RegisterWindow", + "const RESERVED_SLOT_UNK_20", + "pub(crate) struct RegisterMapEntry", + "unsafe impl Zeroable for RegisterMapEntry", + "impl RegisterMapEntry", + "pub(crate) const PERF_STATES", + "const PERF_LANES", + "pub(crate) const OPP_CELLS", + "pub(crate) struct Opp ", + "pub(crate) struct PerfConfig", + "pub(crate) struct PerfGroup", + "unsafe impl Zeroable for PerfGroup", + "struct PerfState", + "impl PerfGroup", + "fn perf_states", + "pub(crate) struct CompletionRingDescriptor", + "unsafe impl Zeroable for CompletionRingDescriptor", + "const COMPLETION_RING_DESCRIPTORS", + "pub(crate) const COMPLETION_RING_INDEX", + "const COMPLETION_RING_ENTRIES", + "const COMPLETION_RING_ENTRY_SIZE", + "pub(crate) struct CompletionRing ", + "pub(crate) struct HwDataArgs", + "type Words", + "fn fill_words", + "pub(crate) const CONTEXT_SWITCH_TIMER_MASK", + "pub(crate) const CONTEXT_SWITCH_TIMER_MULTIPLIER", + "pub(crate) struct RegionCConfig", + "pub(crate) struct RegionC ", + "unsafe impl Zeroable for RegionC ", + "impl RegionC ", +] + +SOC_ITEMS = [ + "pub(crate) const CHIP_ID", + "pub(crate) const REGISTER_WINDOWS", + "pub(crate) const PERF", + "pub(crate) const OPPS", + "const REGION_C", +] + +T6041_ITEMS = SOC_ITEMS + [ + "const RESERVED_SLOTS", + "const MAX_POWER_MW", + "const CHIP: ", + "const HW_DATA_WORDS", + "const POWER_CONFIG_WORDS", +] + +T6041_OPP_HZ = [0, 338, 618, 796, 924, 952, 1056, 1062, 1182, 1182, 1312, 1242, 1380, 1326, + 1470, 1578] +T6041_OPP_MV = [ + [125, 600, 665, 705, 725, 770, 770, 810, 810, 860, 860, 900, 900, 940, 940, 1020], + [125, 600, 665, 705, 725, 770, 770, 810, 810, 860, 860, 895, 895, 925, 925, 1020], + [125, 600, 660, 700, 715, 770, 770, 805, 805, 860, 860, 900, 900, 940, 940, 1020], + [125, 600, 660, 705, 730, 780, 780, 810, 810, 860, 860, 900, 900, 940, 940, 1020], +] +T6041_AUX = [(24, 585000), (338, 585000), (618, 635000), (796, 685000), (992, 720000), + (1188, 770000), (1380, 855000), (1470, 885000)] + + +def opps(name, hz, mv): + rows = [] + for index, freq in enumerate(hz): + cells = [m[index] * 1000 for m in mv] + [0] * (16 - len(mv)) + rows.append( + "Opp { freq_hz: %d, volt_uv: [%s], cells: %d }" + % (freq * 1_000_000, ", ".join(map(str, cells)), len(mv)) + ) + return "const %s: [Opp; %d] = [%s];\n" % (name, len(rows), ",\n".join(rows)) + + +def aux(name, rows): + body = ",\n".join( + "Opp { freq_hz: %d, volt_uv: [%d, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], cells: 1 }" + % (mhz * 1_000_000, uv) + for mhz, uv in rows + ) + return "const %s: [Opp; %d] = [%s];\n" % (name, len(rows), body) + + +MAIN = r""" +fn build( + chip_id: u32, + windows: &[RegisterWindow], + reserved: &[usize], + perf: &PerfConfig, + opps: &[Opp], + aux: &[Opp], + chip: &v25g83::Chip, + region_c: &RegionCConfig, + cores: u32, +) -> (Vec, Vec, Vec) { + let args = HwDataArgs { + chip_id, + windows, + reserved_slots: reserved, + opps, + perf, + completion_rings: [ + CompletionRing { low_va: 0x70_03f0_0000, high_va: 0xffff_fc20_0000_0000 }, + CompletionRing { low_va: 0x70_03f2_8000, high_va: 0xffff_fc20_0002_8000 }, + ], + qos_va: 0xffff_fc20_0004_0000, + }; + let ext = v25g83::HwDataArgs25G83 { active_cores: cores, cs_opps: aux, afr_opps: aux, chip: Some(chip) }; + let mut hw: Box = unsafe { Box::new(core::mem::zeroed()) }; + hw.init(&args, &ext).unwrap(); + let mut power: Box = unsafe { Box::new(core::mem::zeroed()) }; + power.init(0xffff_fc20_000d_3440, 0, 0, chip.max_power_mw, &chip.leakage); + power.apply_chip(chip).unwrap(); + let mut region: Box = unsafe { Box::new(core::mem::zeroed()) }; + region.init(region_c); + region.max_power_mw = region_c.max_power_mw; + let hw_bytes = unsafe { + core::slice::from_raw_parts((&*hw as *const v25g83::HwData25G83).cast::(), + size_of::()) + }.to_vec(); + let region_bytes = unsafe { + core::slice::from_raw_parts((&*region as *const RegionC).cast::(), size_of::()) + }.to_vec(); + (hw_bytes, power.0.to_vec(), region_bytes) +} + +fn main() { + let out = std::env::args().nth(1).unwrap(); + let (hw, power, region) = build(t6041::CHIP_ID, &t6041::REGISTER_WINDOWS, + &t6041::RESERVED_SLOTS, &t6041::PERF, &T6041_OPPS, &T6041_AUX, &t6041::CHIP, + &t6041::REGION_C, 40); + println!("T6041 initialization objects built"); + std::fs::write(format!("{}/t6041-hwdata.bin", out), hw).unwrap(); + std::fs::write(format!("{}/t6041-power-config.bin", out), power).unwrap(); + std::fs::write(format!("{}/t6041-region-c.bin", out), region).unwrap(); +} +""" + + +def program(): + initdata = (DRIVER / "g17/fw/initdata.rs").read_text() + v25g83 = (DRIVER / "g17/fw/initdata/v25g83.rs").read_text() + t6041 = (DRIVER / "hw/t6041.rs").read_text() + v25g83 = re.sub(r"use super::\{[^}]*\};", "use super::*;", v25g83, count=1) + v25g83 = v25g83.replace("use kernel::prelude::*;", "") + float_source = (DRIVER / "float.rs").read_text().split("impl<'a> TryFrom Date: Wed, 28 Oct 2026 17:25:42 -0400 Subject: [PATCH 13/16] arm64: dts: apple: preserve T604x CPU order for m1n1 m1n1 compares DT CPUs with its physical MPIDR enumeration. Appending the Max cores to the Pro topology put CPU 9 out of order and stopped boot before kernel entry. Define the full family topology in hardware order and remove the extra cores from J616s. Check the compiled DTB order for both SoCs. Assisted-by: LLM --- arch/arm64/boot/dts/apple/t6040-j616s.dts | 11 ++++++++ arch/arm64/boot/dts/apple/t6041.dtsi | 31 --------------------- arch/arm64/boot/dts/apple/t604x.dtsi | 33 +++++++++++++++++++++++ tools/asahi/t604x-cpu-order-tests.py | 32 ++++++++++++++++++++++ 4 files changed, 76 insertions(+), 31 deletions(-) create mode 100644 tools/asahi/t604x-cpu-order-tests.py diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index 82562dec27db6..d1383c7a51344 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -14,6 +14,17 @@ #include #include +/delete-node/ &cpu_p05; +/delete-node/ &cpu_p15; + +&{/cpus/cpu-map/cluster1} { + /delete-node/ core5; +}; + +&{/cpus/cpu-map/cluster2} { + /delete-node/ core5; +}; + / { compatible = "apple,j616s", "apple,t6040", "apple,arm-platform"; model = "Apple MacBook Pro (16-inch, M4 Pro, Nov 2024)"; diff --git a/arch/arm64/boot/dts/apple/t6041.dtsi b/arch/arm64/boot/dts/apple/t6041.dtsi index f185e7db4d5d3..66dc7015bfc1a 100644 --- a/arch/arm64/boot/dts/apple/t6041.dtsi +++ b/arch/arm64/boot/dts/apple/t6041.dtsi @@ -248,37 +248,6 @@ /delete-node/ &opp_p1; / { - cpus { - cpu-map { - cluster1 { core5 { cpu = <&cpu_p05>; }; }; - cluster2 { core5 { cpu = <&cpu_p15>; }; }; - }; - cpu_p05: cpu@10105 { - compatible = "apple,brava-p"; - device_type = "cpu"; - reg = <0x0 0x10105>; - enable-method = "spin-table"; - cpu-release-addr = <0 0>; - next-level-cache = <&l2_cache_p0>; - operating-points-v2 = <&opp_p0>; - capacity-dmips-mhz = <1024>; - performance-domains = <&cpufreq_p0>; - #cooling-cells = <2>; - }; - cpu_p15: cpu@10205 { - compatible = "apple,brava-p"; - device_type = "cpu"; - reg = <0x0 0x10205>; - enable-method = "spin-table"; - cpu-release-addr = <0 0>; - next-level-cache = <&l2_cache_p1>; - operating-points-v2 = <&opp_p1>; - capacity-dmips-mhz = <1024>; - performance-domains = <&cpufreq_p1>; - #cooling-cells = <2>; - }; - }; - opp_p0: opp-table-p0 { compatible = "operating-points-v2"; opp-shared; diff --git a/arch/arm64/boot/dts/apple/t604x.dtsi b/arch/arm64/boot/dts/apple/t604x.dtsi index d50888b229d75..f81aba8d8c012 100644 --- a/arch/arm64/boot/dts/apple/t604x.dtsi +++ b/arch/arm64/boot/dts/apple/t604x.dtsi @@ -68,6 +68,9 @@ core4 { cpu = <&cpu_p04>; }; + core5 { + cpu = <&cpu_p05>; + }; }; cluster2 { @@ -86,9 +89,13 @@ core4 { cpu = <&cpu_p14>; }; + core5 { + cpu = <&cpu_p15>; + }; }; }; + /* m1n1 requires CPU nodes in MPIDR order. */ cpu_e0: cpu@0 { compatible = "apple,brava-e"; device_type = "cpu"; @@ -205,6 +212,19 @@ #cooling-cells = <2>; }; + cpu_p05: cpu@10105 { + compatible = "apple,brava-p"; + device_type = "cpu"; + reg = <0x0 0x10105>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; + next-level-cache = <&l2_cache_p0>; + operating-points-v2 = <&opp_p0>; + capacity-dmips-mhz = <1024>; + performance-domains = <&cpufreq_p0>; + #cooling-cells = <2>; + }; + cpu_p10: cpu@10200 { compatible = "apple,brava-p"; device_type = "cpu"; @@ -269,6 +289,19 @@ performance-domains = <&cpufreq_p1>; #cooling-cells = <2>; }; + + cpu_p15: cpu@10205 { + compatible = "apple,brava-p"; + device_type = "cpu"; + reg = <0x0 0x10205>; + enable-method = "spin-table"; + cpu-release-addr = <0 0>; + next-level-cache = <&l2_cache_p1>; + operating-points-v2 = <&opp_p1>; + capacity-dmips-mhz = <1024>; + performance-domains = <&cpufreq_p1>; + #cooling-cells = <2>; + }; }; l2_cache_e: l2-cache-cluster0 { diff --git a/tools/asahi/t604x-cpu-order-tests.py b/tools/asahi/t604x-cpu-order-tests.py new file mode 100644 index 0000000000000..0d9ef830f231e --- /dev/null +++ b/tools/asahi/t604x-cpu-order-tests.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Check the CPU order required by m1n1's sequential MPIDR validation.""" +import argparse +import subprocess + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('dtb') + parser.add_argument('--soc', choices=['t6040', 't6041'], required=True) + args = parser.parse_args() + + def get(*options): + return subprocess.check_output(['fdtget', *options, args.dtb, '/cpus'], text=True) + + actual = [] + for node in get('-l').split(): + if not node.startswith('cpu@'): + continue + cells = subprocess.check_output( + ['fdtget', '-t', 'x', args.dtb, '/cpus/' + node, 'reg'], text=True).split() + actual.append((int(cells[0], 16) << 32) | int(cells[1], 16)) + cores = 5 if args.soc == 't6040' else 6 + expected = list(range(4)) + [base + core for base in [0x10100, 0x10200] + for core in range(cores)] + assert actual == expected, f'CPU MPIDR order: {actual!r}; expected {expected!r}' + print(f'PASS: {args.soc} CPU order ({len(actual)} cores)') + + +if __name__ == '__main__': + main() From c048e0eb6bc8821cc8e4abb774897376ebfc15e0 Mon Sep 17 00:00:00 2001 From: Artyom Astafurov Date: Fri, 30 Oct 2026 14:21:39 -0400 Subject: [PATCH 14/16] drm/asahi: scope experimental TA mode 1 to T6041 25F84 Keep all shared firmware profiles at TA 0x1c890 = 0 and enable mode 1 only in T6041's admitted 25F84 build. Pass that device/build policy through RenderArgs without changing register ordering, merge-buffer writes, fragment programs or serial-render admission. Add KUnit coverage for the policy boundary and constructor output, including SCS array shape and unchanged fragment/G17P arrays. Motivated by AxxzyWasTaken's separate J616C/25G83 implementation results: https://github.com/aurora-silicon/linux/pull/525#issuecomment-6486668315 Those results do not validate this branch or its 25F84 firmware; the cluster merge-buffer explanation remains a hypothesis. Validation: patch application/reversal, whitespace and source-scope checks. Not yet compiled, KUnit-executed or hardware-tested. Keep the existing asahi.t6041_serial_render=1 workaround pending the concurrent-render test matrix documented in the PR. --- drivers/gpu/drm/asahi/g17/fw/render.rs | 150 +++++++++++++++++++++- drivers/gpu/drm/asahi/g17/fw/variant.rs | 4 + drivers/gpu/drm/asahi/g17/queue/render.rs | 1 + drivers/gpu/drm/asahi/hw/t6041.rs | 8 +- 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index 44c3527f2c237..ba3980abf5f3a 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -370,6 +370,8 @@ pub(crate) struct RenderArgs<'a> { pub(crate) fragment: RenderStage, /// Register programs of the firmware build. pub(crate) programs: RegisterPrograms, + /// Device/build policy for register 0x1c890 in V25G83-format arrays. + pub(crate) ta_mode_1: bool, /// Deflake-buffer layout of the GPU. pub(crate) deflake: Deflake, } @@ -690,7 +692,7 @@ fn ta_registers_25g83( push(find(0x1c880, 0)?); push(RegisterWrite::new(0x1c948, gtp(4)?)); push(RegisterWrite::new(0x1c888, gtp(5)?)); - push(RegisterWrite::new(0x1c890, 0)); + push(RegisterWrite::new(0x1c890, u64::from(args.ta_mode_1))); push(RegisterWrite::new(0x1c918, 0)); push(find(0x1c079, 0)?); push(find(0x1c9d8, 0)?); @@ -1567,3 +1569,149 @@ impl FragmentSlot { Ok(Some(table)) } } + +#[kunit_tests(asahi_g17_ta_mode)] +mod ta_mode_tests { + use super::*; + use crate::g17::fw::{ + compute::{IntersectionTables, ScratchRequest}, + render_rt::RayQueryStage, + variant::{Firmware, Variant, V25F84, V25G83, V26A428}, + }; + + fn selected(firmware: &Firmware) -> &'static Variant { + match firmware { + Firmware::Fixed(variant) => variant, + Firmware::Identified(builds) => { + assert_eq!(builds.len(), 1); + builds[0].variant + } + } + } + + #[test] + fn policy_is_local_to_the_admitted_t6041_build() { + let max = selected(&crate::hw::t6041::CONFIG.firmware); + let pro = selected(&crate::hw::t6040::CONFIG.firmware); + assert!(max.is_25f84()); + assert_eq!(max.programs, RegisterPrograms::V25G83); + assert!(max.ta_mode_1); + assert!(!pro.ta_mode_1); + assert!(!V25F84.ta_mode_1); + assert!(!V25G83.ta_mode_1); + assert!(!V26A428.ta_mode_1); + } + + fn fixture() -> (RenderPass, RenderResources) { + // SAFETY: These two types contain only integers, bools and structs of + // those types, for which all-zero bytes are valid. Required render + // dimensions and user-window addresses are filled below. + let mut pass: RenderPass = unsafe { core::mem::zeroed() }; + // SAFETY: RenderResources contains only integers and integer structs. + let mut resources: RenderResources = unsafe { core::mem::zeroed() }; + pass.width = 64; + pass.height = 64; + pass.layers = 1; + pass.utile_width = 32; + pass.utile_height = 32; + pass.sample_size = 4; + pass.vdm_va = USER_BASE + 0x10000; + resources.tilemap_va = USER_BASE + 0x20000; + resources.layermeta_va = USER_BASE + 0x30000; + resources.heapmeta_va = USER_BASE + 0x40000; + resources.tpc_va = USER_BASE + 0x50000; + resources.deflake_va = USER_BASE + 0x60000; + resources.gtp_va = core::array::from_fn(|i| USER_BASE + 0x70000 + i as u64 * 0x1000); + (pass, resources) + } + + fn args<'a>(pass: &'a RenderPass, resources: &'a RenderResources, scs: bool) -> RenderArgs<'a> { + let stage = RenderStage { + qid: 1, + descriptor_va: USER_BASE + 0x80000, + state_word: 1, + stamp_va: USER_BASE + 0x90000, + aux_stamp_va: USER_BASE + 0x90008, + }; + // Exercise the optional SCS entry even though the currently admitted + // 25F84/25G83 profiles disable ray traversal. + let ray = RayQueryStage { + intersection: IntersectionTables::NONE, + scratch: ScratchRequest::new(if scs { (1 << 32) | (1 << 24) | 8 } else { 0 }).unwrap(), + }; + RenderArgs { + pass, + resources, + ray_query: [ray; 2], + ordinal: 0, + ta: stage, + fragment: stage, + programs: RegisterPrograms::V25G83, + ta_mode_1: false, + deflake: Deflake::FOUR_CLUSTERS, + } + } + + #[test] + fn ta_mode_changes_exactly_one_write_with_or_without_scs() { + let (mut pass, resources) = fixture(); + for (width, height, layers) in [(64, 64, 1), (3456, 2234, 2)] { + pass.width = width; + pass.height = height; + pass.layers = layers; + let geometry = Geometry::new(&pass).unwrap(); + let words = PassWords::new(&pass).unwrap(); + for scs in [false, true] { + let mut args = args(&pass, &resources, scs); + let (before, count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); + args.ta_mode_1 = selected(&crate::hw::t6041::CONFIG.firmware).ta_mode_1; + let (after, new_count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); + assert_eq!(count, 83 + usize::from(scs)); + assert_eq!(new_count, count); + let slot = after[79 + usize::from(scs)].reg; + assert_eq!(slot, 0x14308); + let mut changed = 0; + for (old, new) in before.iter().zip(&after).take(count) { + let old = (old.reg, old.value); + let new = (new.reg, new.value); + if old.0 == 0x1c890 { + assert_eq!(old.1, 0); + assert_eq!(new, (0x1c890, 1)); + changed += 1; + } else { + // Includes every merge-buffer write and 0x1c9a8. + assert_eq!(old, new); + } + } + assert_eq!(changed, 1); + } + } + } + + #[test] + fn mode_does_not_change_fragment_or_g17p_arrays() { + let (pass, resources) = fixture(); + let geometry = Geometry::new(&pass).unwrap(); + let words = PassWords::new(&pass).unwrap(); + for scs in [false, true] { + let mut args = args(&pass, &resources, scs); + let (before, count) = fragment_registers_25g83(&args, &geometry, &words).unwrap(); + args.ta_mode_1 = true; + let (after, new_count) = fragment_registers_25g83(&args, &geometry, &words).unwrap(); + assert_eq!(count, 91 + usize::from(scs)); + assert_eq!(new_count, count); + for (old, new) in before.iter().zip(&after).take(count) { + assert_eq!((old.reg, old.value), (new.reg, new.value)); + } + args.programs = RegisterPrograms::G17P; + args.ta_mode_1 = false; + let before = ta_registers(&args, &geometry, &words).unwrap(); + args.ta_mode_1 = true; + let after = ta_registers(&args, &geometry, &words).unwrap(); + for (old, new) in before.iter().zip(&after) { + assert_eq!((old.reg, old.value), (new.reg, new.value)); + assert_ne!({ new.reg }, 0x1c890); + } + } + } +} diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index cb38da617e24f..11a79fe992404 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -79,6 +79,8 @@ pub(crate) struct Variant { pub(crate) lead_commands: bool, /// Register programs of the render descriptors. pub(crate) programs: super::render::RegisterPrograms, + /// Use 0x1c890 = 1 in V25G83-format tiling arrays (device/build policy). + pub(crate) ta_mode_1: bool, pub(crate) compute: super::compute::ComputeProgram, pub(crate) opening_record: bool, pub(crate) split_firmware_area: bool, @@ -123,6 +125,7 @@ pub(crate) const V26A428: Variant = Variant { fragment_busy: true, lead_commands: false, programs: super::render::RegisterPrograms::G17P, + ta_mode_1: false, compute: super::compute::ComputeProgram::G17P, opening_record: true, split_firmware_area: false, @@ -155,6 +158,7 @@ pub(crate) const V25G83: Variant = Variant { fragment_busy: false, lead_commands: true, programs: super::render::RegisterPrograms::V25G83, + ta_mode_1: false, compute: super::compute::ComputeProgram::G17P, opening_record: true, split_firmware_area: false, diff --git a/drivers/gpu/drm/asahi/g17/queue/render.rs b/drivers/gpu/drm/asahi/g17/queue/render.rs index 2254118f18140..08eaf696a437b 100644 --- a/drivers/gpu/drm/asahi/g17/queue/render.rs +++ b/drivers/gpu/drm/asahi/g17/queue/render.rs @@ -457,6 +457,7 @@ impl Pair { aux_stamp_va: addresses.stamps[1][1], }, programs: self.variant.programs, + ta_mode_1: self.variant.ta_mode_1, deflake: deflake_layout, }; self.memory.graph.claim(ordinal)?; diff --git a/drivers/gpu/drm/asahi/hw/t6041.rs b/drivers/gpu/drm/asahi/hw/t6041.rs index 263f458b57b3b..40ae028eca940 100644 --- a/drivers/gpu/drm/asahi/hw/t6041.rs +++ b/drivers/gpu/drm/asahi/hw/t6041.rs @@ -60,7 +60,13 @@ pub(crate) const CONFIG: Config = Config { 0xa3, 0xc1, 0x83, 0xe8, ], }], - variant: &fw::variant::V25F84, + // Experimental TA-mode workaround: reported on another J616C/25G83 + // implementation, not yet hardware-verified here on 25F84. Keep the + // shared firmware profiles (in particular M4 Pro's) unchanged. + variant: &fw::variant::Variant { + ta_mode_1: true, + ..fw::variant::V25F84 + }, }]), core_layout: CoreLayout { cores: Some((4, 40)), From 09b2032d0930bfb4a94d7cd52359b2a32f7f1861 Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Tue, 3 Nov 2026 11:36:07 +0000 Subject: [PATCH 15/16] drm/asahi: add the T6041 25G83 profile captured on the J616C The 32-core M4 Max in the J616C boots through a macOS 26.6.2 stub (iBoot mBoot-18000.161.10), which loads a T6041 image of the 25G83 GPU firmware the M4 Pro runs. Its loaded TEXT (0x60000 bytes, GKTS and ECAP records at 0x59839 and 0x5991c, DATA 0x138000) normalizes to sha256 aa113ed62079bcfdeef0f86c2a8d7eae9f907b03be28ccf298c943951d0effc5. T6041 admits only its 25F84 image, on 40 cores, so this unit's GPU is refused. Admit that image as a second T6041 build. A firmware build can now name the SoC configuration it was measured with; once the build is identified, that configuration replaces the probed one. The 25F84 build names none and keeps its static profile; T6040 and T8140 only gain the empty fields. Built on the host from the driver sources, the T6041 25F84 hardware data, power configuration and region C, and the T6040 builder output, are byte-identical before and after this change. The 25G83 configuration (hw/t6041_25g83.rs) is the one the J616C has run with on this firmware: - 4 clusters with 32 enabled cores (core mask 0xf6_fdbf6fdb), which the probe requires. - Operand-state banks for all 40 core slots (0x80000 bytes), as for 25F84; the enabled slots run to 39. With banks for the 32 enabled cores only (0x68000 bytes, followed by the queue context), a compute load beside rendering hit reason-0 recoveries in 5 of 5 runs, and in 0 of 4 with 40 banks. - Four-cluster deflake and TA mode 1, as for 25F84. Clustered tiling hangs this GPU until the cluster buffers are sized for four clusters. - A compute pool of 96 blocks, which the J616C has run with since 23 Oct 2026; 21 blocks hang its firmware on grids of more than ten workgroups. - The captured register map: six memory controller instances in slot 3 (apple,sgx-active-mcc-bit-vector 0x3f, 32 MiB apart), which moves every window after it 0x30000 further than on the M4 Pro, and no reserved slots. Its hardware data, power configuration, region C and AON table are applied, not built: the contents built on this unit and captured from it on 25 Oct 2026 (hw/t6041_captured.rs), with only the runtime words written over them (completion-ring descriptors and QoS pointer; configuration-status pointer, AON pointer and random word). They equal what the 25G83 builders write for the M4 Pro except for the chip ID, the core count, the register map, four GPU cluster temperature sources, a second sensor-mask word, and the values below. Some captured values exist only because the device tree the J616C boots with has no GPU operating-point, CS or AFR tables and none of the calibration properties the T6040 builder reads (apple,perf-map-a/b, apple,perf-relative-a/b, apple,max-power-mw, the leakage coefficients): this unit's binned operating points in the performance, CS and AFR tables, its 110586 mW power limit and the coefficient derived from it, and the four clusters' leakage coefficients. With those tables in its device tree, the builders could produce them instead. The GPU is not started if the captured contents do not fit: it must have the captured 32 cores, the captured chip ID and register map must equal the configuration's (with no reserved slots), and the FNV-1a 64 hashes of the hardware data and power configuration without their runtime words, and of region C, must equal the capture's. On the host, the written contents hash to those values, and the checks refuse another chip ID, the 25F84 and T6040 register windows, a missing or duplicated window and reserved slots. Not run on hardware: the J616C has run these contents on this firmware only with the 7.2 port of the G17 driver. --- .../gpu/drm/asahi/g17/fw/initdata/v25g83.rs | 180 ++++- drivers/gpu/drm/asahi/g17/fw/render.rs | 31 +- drivers/gpu/drm/asahi/g17/fw/variant.rs | 3 + drivers/gpu/drm/asahi/g17/initdata/v25g83.rs | 187 +++-- drivers/gpu/drm/asahi/g17/mod.rs | 18 +- drivers/gpu/drm/asahi/hw/mod.rs | 2 + drivers/gpu/drm/asahi/hw/t6040.rs | 2 + drivers/gpu/drm/asahi/hw/t6041.rs | 52 +- drivers/gpu/drm/asahi/hw/t6041_25g83.rs | 192 +++++ drivers/gpu/drm/asahi/hw/t6041_captured.rs | 710 ++++++++++++++++++ drivers/gpu/drm/asahi/hw/t8140.rs | 1 + 11 files changed, 1277 insertions(+), 101 deletions(-) create mode 100644 drivers/gpu/drm/asahi/hw/t6041_25g83.rs create mode 100644 drivers/gpu/drm/asahi/hw/t6041_captured.rs diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index 6e2453f77ab52..5e15dba2b0e6e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -2,7 +2,7 @@ use super::{ - fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, + fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, RegionC, RegisterMapEntry, Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, COMPLETION_RING_INDEX, PERF_STATES, REGISTER_MAP_SLOTS, }; @@ -356,6 +356,24 @@ impl HwData25G83 { self.unk_25fc = 1; self.unk_2614 = 1; self.unk_261c = 0x100; + self.set_runtime_words(args); + self.unk_26c0 = 1; + self.unk_26c4 = 1; + self.unk_26f4 = 1; + // SAFETY: `HwData25G83` consists of integers only and has no padding, so any bytes are a + // valid value. + let bytes = unsafe { + core::slice::from_raw_parts_mut((self as *mut Self).cast::(), size_of::()) + }; + if let Some(chip) = ext.chip { + overlay_words(bytes, chip.hw_data_words)?; + } + Ok(()) + } + + /// Writes the words the host sets at every boot: the completion-ring descriptors and the + /// QoS pointer. + fn set_runtime_words(&mut self, args: &HwDataArgs<'_>) { for (ring, index) in args.completion_rings.iter().zip(COMPLETION_RING_INDEX) { self.completion_rings[index as usize] = CompletionRingDescriptor { low_va: ring.low_va, @@ -367,19 +385,80 @@ impl HwData25G83 { }; } self.qos_va = args.qos_va; - self.unk_26c0 = 1; - self.unk_26c4 = 1; - self.unk_26f4 = 1; - // SAFETY: `HwData25G83` consists of integers only and has no padding, so any bytes are a - // valid value. - let bytes = unsafe { - core::slice::from_raw_parts_mut((self as *mut Self).cast::(), size_of::()) - }; - if let Some(chip) = ext.chip { - overlay_words(bytes, chip.hw_data_words)?; + } + + /// Initializes the hardware data from captured contents, then writes the runtime words. + /// + /// The captured chip identifier and register map must be the ones `args` names: the + /// firmware finds its register windows through the map, and the driver maps exactly + /// `args.windows`. + pub(crate) fn init_captured(&mut self, captured: &Captured, args: &HwDataArgs<'_>) -> Result { + fill_words(self.bytes_mut(), captured.hw_data); + if !args.reserved_slots.is_empty() || { self.chip_id } != args.chip_id { + return Err(EINVAL); + } + let mut expected = [[0u8; size_of::()]; REGISTER_MAP_SLOTS]; + let mut used = [false; REGISTER_MAP_SLOTS]; + for window in args.windows { + match used.get_mut(window.slot) { + Some(used) if !*used => *used = true, + _ => return Err(EINVAL), + } + expected[window.slot] = RegisterMapEntry::window(window).to_bytes(); + } + let map = core::mem::offset_of!(Self, register_map); + let bytes = self.bytes_mut(); + for (slot, entry) in expected.iter().enumerate() { + let at = map + slot * entry.len(); + if bytes[at..at + entry.len()] != entry[..] { + return Err(EINVAL); + } } + self.set_runtime_words(args); Ok(()) } + + /// The runtime words, which [`Self::hash_captured`] leaves out. + const RUNTIME: [(usize, usize); 2] = [(COMPLETION_DESCRIPTORS, 0x80), (QOS_POINTER, 8)]; + + /// Hash of captured contents: everything but the runtime words. + pub(crate) fn hash_captured(&self) -> u64 { + hash_without(self.bytes(), &Self::RUNTIME) + } + + fn bytes(&self) -> &[u8] { + // SAFETY: `HwData25G83` consists of integers only and has no padding. + unsafe { + core::slice::from_raw_parts((self as *const Self).cast::(), size_of::()) + } + } + + fn bytes_mut(&mut self) -> &mut [u8] { + // SAFETY: `HwData25G83` consists of integers only and has no padding, so every byte is + // initialized and any byte pattern is a valid value. + unsafe { + core::slice::from_raw_parts_mut((self as *mut Self).cast::(), size_of::()) + } + } +} + +impl RegisterMapEntry { + /// The entry as the firmware reads it. + fn to_bytes(&self) -> [u8; size_of::()] { + let mut bytes = [0; size_of::()]; + for (at, field) in [ + (0x0, &self.phys.to_le_bytes()[..]), + (0x8, &self.va.to_le_bytes()[..]), + (0x10, &self.total_size.to_le_bytes()[..]), + (0x14, &self.element_size.to_le_bytes()[..]), + (0x18, &self.unk_18.to_le_bytes()[..]), + (0x20, &self.unk_20.to_le_bytes()[..]), + (0x24, &self.unk_24.to_le_bytes()[..]), + ] { + bytes[at..at + field.len()].copy_from_slice(field); + } + bytes + } } fn overlay_words(bytes: &mut [u8], words: Words) -> Result { @@ -617,15 +696,90 @@ impl PowerConfig25G83 { for offset in [0x9e0, 0x2a04] { self.0[offset..offset + 4].copy_from_slice(&power.to_le_bytes()); } + self.set_runtime_words(config_va, aon_va, entropy); + } + + pub(crate) fn apply_chip(&mut self, chip: &Chip) -> Result { + overlay_words(&mut self.0, chip.power_config_words) + } + + /// Initializes the power configuration from captured contents, then writes the runtime + /// words. + pub(crate) fn init_captured( + &mut self, + words: Words, + config_va: u64, + aon_va: u64, + entropy: u32, + ) { + fill_words(&mut self.0, words); + self.set_runtime_words(config_va, aon_va, entropy); + } + + /// Writes the pointers and the random word ([`Self::RUNTIME`]). + fn set_runtime_words(&mut self, config_va: u64, aon_va: u64, entropy: u32) { self.0[CONFIG_STATUS_POINTER..CONFIG_STATUS_POINTER + 8] .copy_from_slice(&(config_va + 0x80).to_le_bytes()); self.0[AON_POINTER..AON_POINTER + 8].copy_from_slice(&aon_va.to_le_bytes()); self.0[ENTROPY..ENTROPY + 4].copy_from_slice(&entropy.to_le_bytes()); } - pub(crate) fn apply_chip(&mut self, chip: &Chip) -> Result { - overlay_words(&mut self.0, chip.power_config_words) + /// The pointers and the random word, which [`Self::hash`] leaves out. + const RUNTIME: [(usize, usize); 3] = + [(CONFIG_STATUS_POINTER, 8), (AON_POINTER, 8), (ENTROPY, 4)]; + + /// Hash of the contents without the pointers and the random word. + pub(crate) fn hash(&self) -> u64 { + hash_without(&self.0, &Self::RUNTIME) + } +} + +/// 25G83 initialization contents captured on one unit, which a configuration whose device +/// tree has no inputs for the builders above applies instead of what they produce. Only the +/// runtime words are written over them. +pub(crate) struct Captured { + /// The [`HwData25G83`], without its runtime words. + pub(crate) hw_data: Words, + /// The [`PowerConfig25G83`] and its auxiliary data, without the runtime words. + pub(crate) power_config: Words, + /// Region C. + pub(crate) region_c: Words, + /// The AON temperature-source rows. + pub(crate) aon_table: &'static [u32], + /// Enabled GPU cores the contents were captured with. The captured performance tables + /// are that unit's binned operating points; another core count is another part. + pub(crate) active_cores: u32, + /// Hashes ([`hash_without`]) of the captured hardware data without its runtime words + /// ([`HwData25G83::hash_captured`]), power configuration ([`PowerConfig25G83::hash`]) and + /// region C, which what the driver wrote must equal. + pub(crate) expected: [u64; 3], +} + +impl RegionC { + /// Initializes region C from captured contents. + pub(crate) fn init_captured(&mut self, words: Words) { + // SAFETY: `RegionC` consists of integers only and is packed, so every byte is + // initialized and any byte pattern is a valid value. + let bytes = unsafe { + core::slice::from_raw_parts_mut((self as *mut Self).cast::(), size_of::()) + }; + fill_words(bytes, words); + } +} + +/// FNV-1a 64 of `bytes` with the `(offset, length)` ranges in `skip` left out. +pub(crate) fn hash_without(bytes: &[u8], skip: &[(usize, usize)]) -> u64 { + let mut hash = 0xcbf2_9ce4_8422_2325u64; + for (offset, byte) in bytes.iter().enumerate() { + if skip + .iter() + .any(|&(at, len)| (at..at + len).contains(&offset)) + { + continue; + } + hash = (hash ^ u64::from(*byte)).wrapping_mul(0x100_0000_01b3); } + hash } pub(crate) const AON_TABLE: [u32; 9] = [ diff --git a/drivers/gpu/drm/asahi/g17/fw/render.rs b/drivers/gpu/drm/asahi/g17/fw/render.rs index ba3980abf5f3a..b31485541371e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/render.rs +++ b/drivers/gpu/drm/asahi/g17/fw/render.rs @@ -1589,13 +1589,32 @@ mod ta_mode_tests { } } + /// The variants of the builds T6041 admits: 25F84, then 25G83. + fn t6041_variants() -> [&'static Variant; 2] { + let Firmware::Identified(builds) = crate::hw::t6041::CONFIG.firmware else { + panic!("T6041 must identify its loaded firmware"); + }; + assert_eq!(builds.len(), 2); + [builds[0].variant, builds[1].variant] + } + + /// The TA mode of every admitted T6041 build. + fn t6041_ta_mode_1() -> bool { + let [f84, g83] = t6041_variants(); + assert_eq!(f84.ta_mode_1, g83.ta_mode_1); + f84.ta_mode_1 + } + #[test] - fn policy_is_local_to_the_admitted_t6041_build() { - let max = selected(&crate::hw::t6041::CONFIG.firmware); + fn policy_is_local_to_the_admitted_t6041_builds() { + let [f84, g83] = t6041_variants(); let pro = selected(&crate::hw::t6040::CONFIG.firmware); - assert!(max.is_25f84()); - assert_eq!(max.programs, RegisterPrograms::V25G83); - assert!(max.ta_mode_1); + assert!(f84.is_25f84()); + assert!(!g83.is_25f84()); + for max in [f84, g83] { + assert_eq!(max.programs, RegisterPrograms::V25G83); + assert!(max.ta_mode_1); + } assert!(!pro.ta_mode_1); assert!(!V25F84.ta_mode_1); assert!(!V25G83.ta_mode_1); @@ -1664,7 +1683,7 @@ mod ta_mode_tests { for scs in [false, true] { let mut args = args(&pass, &resources, scs); let (before, count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); - args.ta_mode_1 = selected(&crate::hw::t6041::CONFIG.firmware).ta_mode_1; + args.ta_mode_1 = t6041_ta_mode_1(); let (after, new_count) = ta_registers_25g83(&args, &geometry, &words).unwrap(); assert_eq!(count, 83 + usize::from(scs)); assert_eq!(new_count, count); diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index 11a79fe992404..19559abad629d 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -97,6 +97,9 @@ pub(crate) struct Build { pub(crate) images: &'static [crate::fwid::FirmwareBuild], /// Layouts and rules of the build. pub(crate) variant: &'static Variant, + /// The SoC configuration measured with this build, which replaces the probed one when + /// the build is identified; `None` keeps the probed configuration. + pub(crate) config: Option<&'static crate::g17::Config>, } /// How a SoC's firmware build is determined. diff --git a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs index 9aeab2a4f7fb1..838c88870c1e8 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs @@ -98,7 +98,10 @@ impl InitData { let status_a_va = cluster_va + v::private::STATUS_A as u64; let status_b_va = cluster_va + v::private::STATUS_B as u64; let chip = cfg.chip; - let aon_table = chip.map_or(v::AON_TABLE.as_slice(), |chip| chip.aon_table); + let aon_table = match cfg.captured { + Some(captured) => captured.aon_table, + None => chip.map_or(v::AON_TABLE.as_slice(), |chip| chip.aon_table), + }; let aon_va = match aon_table.is_empty() { true => 0, false => self.object(objects.aon)?.gpu_va(), @@ -114,61 +117,65 @@ impl InitData { *va = self.object(index)?.gpu_va(); } - let opps = Self::opps(env.dev, cfg.opps, env.clusters)?; - let cs_opps = Self::opp_table(env.dev, c_str!("apple,cs-opp"))?; - let afr_opps = Self::opp_table(env.dev, c_str!("apple,afr-opp"))?; - let calibration = if chip.is_none() { - let calibration = Calibration::read(env.dev)?; - calibration.check_opps(&opps)?; - Some(calibration) + if let Some(captured) = cfg.captured { + self.write_captured_25g83(env, cfg, captured, active_cores, bundle, config_va, aon_va)?; } else { - None - }; - let (perf, max_power_mw, leakage) = match (calibration.as_ref(), chip) { - (Some(calibration), None) => ( - &calibration.perf, - calibration.max_power_mw, - &calibration.leakage, - ), - (None, Some(chip)) => (cfg.perf, chip.max_power_mw, &chip.leakage), - _ => return Err(EINVAL), - }; - let hw_args = abi::HwDataArgs { - chip_id: cfg.chip_id, - windows: cfg.windows, - reserved_slots: cfg.reserved_slots, - opps: &opps, - perf, - completion_rings: bundle.completion_rings, - qos_va: self.object(bundle.qos)?.gpu_va(), - }; - let hw_args_25g83 = v::HwDataArgs25G83 { - active_cores, - cs_opps: &cs_opps, - afr_opps: &afr_opps, - chip, - }; - // SAFETY: Plain C entropy source. - let entropy = unsafe { kernel::bindings::get_random_u32() }; - self.object_mut(self.bundle)? - .initialize::(0, |hw| { - hw.init(&hw_args, &hw_args_25g83)?; - Ok(()) - })?; - self.object_mut(self.bundle)? - .initialize::(v::POWER_CONFIG, |power| { - power.init(config_va, aon_va, entropy, max_power_mw, leakage); - if let Some(chip) = chip { - power.apply_chip(chip)?; - } - Ok(()) - })?; - self.object_mut(self.region_c)? - .initialize::(0, |region| { - region.init(cfg.region_c); - region.max_power_mw = max_power_mw; - Ok(()) - })?; + let opps = Self::opps(env.dev, cfg.opps, env.clusters)?; + let cs_opps = Self::opp_table(env.dev, c_str!("apple,cs-opp"))?; + let afr_opps = Self::opp_table(env.dev, c_str!("apple,afr-opp"))?; + let calibration = if chip.is_none() { + let calibration = Calibration::read(env.dev)?; + calibration.check_opps(&opps)?; + Some(calibration) + } else { + None + }; + let (perf, max_power_mw, leakage) = match (calibration.as_ref(), chip) { + (Some(calibration), None) => ( + &calibration.perf, + calibration.max_power_mw, + &calibration.leakage, + ), + (None, Some(chip)) => (cfg.perf, chip.max_power_mw, &chip.leakage), + _ => return Err(EINVAL), + }; + let hw_args = abi::HwDataArgs { + chip_id: cfg.chip_id, + windows: cfg.windows, + reserved_slots: cfg.reserved_slots, + opps: &opps, + perf, + completion_rings: bundle.completion_rings, + qos_va: self.object(bundle.qos)?.gpu_va(), + }; + let hw_args_25g83 = v::HwDataArgs25G83 { + active_cores, + cs_opps: &cs_opps, + afr_opps: &afr_opps, + chip, + }; + // SAFETY: Plain C entropy source. + let entropy = unsafe { kernel::bindings::get_random_u32() }; + self.object_mut(self.bundle)? + .initialize::(0, |hw| { + hw.init(&hw_args, &hw_args_25g83)?; + Ok(()) + })?; + self.object_mut(self.bundle)? + .initialize::(v::POWER_CONFIG, |power| { + power.init(config_va, aon_va, entropy, max_power_mw, leakage); + if let Some(chip) = chip { + power.apply_chip(chip)?; + } + Ok(()) + })?; + self.object_mut(self.region_c)? + .initialize::(0, |region| { + region.init(cfg.region_c); + region.max_power_mw = max_power_mw; + Ok(()) + })?; + } for (index, word) in aon_table.iter().enumerate() { self.object_mut(objects.aon)?.write(index * 4, *word)?; } @@ -263,4 +270,74 @@ impl InitData { fence(Ordering::SeqCst); Ok(()) } + + /// Writes the hardware data, power configuration and region C captured on one unit, with + /// this boot's runtime words over them, and checks the result against the capture's hashes. + /// No operating points are read from the device tree: the captured tables hold the unit's. + fn write_captured_25g83( + &mut self, + env: &BuildEnv<'_>, + cfg: &Config, + captured: &v::Captured, + active_cores: u32, + bundle: &BundleObjects, + config_va: u64, + aon_va: u64, + ) -> Result { + if active_cores != captured.active_cores { + dev_err!( + env.dev.as_ref(), + "25G83 initialization data was captured with {} cores, the GPU has {}\n", + captured.active_cores, + active_cores + ); + return Err(EINVAL); + } + let hw_args = abi::HwDataArgs { + chip_id: cfg.chip_id, + windows: cfg.windows, + reserved_slots: cfg.reserved_slots, + opps: &[], + perf: cfg.perf, + completion_rings: bundle.completion_rings, + qos_va: self.object(bundle.qos)?.gpu_va(), + }; + // SAFETY: Plain C entropy source. + let entropy = unsafe { kernel::bindings::get_random_u32() }; + let mut hashes = [0; 3]; + self.object_mut(self.bundle)? + .initialize::(0, |hw| { + hw.init_captured(captured, &hw_args).inspect_err(|_| { + dev_err!( + env.dev.as_ref(), + "Captured 25G83 hardware data does not match chip {:#x} and its register windows\n", + cfg.chip_id + ); + })?; + hashes[0] = hw.hash_captured(); + Ok(()) + })?; + self.object_mut(self.bundle)? + .initialize::(v::POWER_CONFIG, |power| { + power.init_captured(captured.power_config, config_va, aon_va, entropy); + hashes[1] = power.hash(); + Ok(()) + })?; + self.object_mut(self.region_c)? + .initialize::(0, |region| { + region.init_captured(captured.region_c); + Ok(()) + })?; + hashes[2] = v::hash_without(self.object_mut(self.region_c)?.bytes_mut(), &[]); + if hashes != captured.expected { + dev_err!( + env.dev.as_ref(), + "25G83 initialization data differs from its capture: {:#x?}, expected {:#x?}\n", + hashes, + captured.expected + ); + return Err(EINVAL); + } + Ok(()) + } } diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index b96715eaae321..2a0f8d7c5bc98 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -192,6 +192,9 @@ pub(crate) struct Config { /// The SoC's values of region C. pub(crate) region_c: &'static fw::initdata::RegionCConfig, pub(crate) chip: Option<&'static fw::initdata::v25g83::Chip>, + /// 25G83 initialization contents captured on one unit, written instead of what the + /// builders produce; `None` builds them. + pub(crate) captured: Option<&'static fw::initdata::v25g83::Captured>, /// Physical address of the firmware's per-slot recovery keys, read while the firmware is /// halted to attribute a fault; `None` where it is unknown (recovery then relies on the /// firmware's own blame). @@ -1149,7 +1152,9 @@ impl Gpu { } else { None }; - let variant = Self::firmware_variant(dev, &cfg.firmware)?; + let (variant, measured) = Self::firmware_variant(dev, &cfg.firmware)?; + // A build measured with its own configuration of the SoC runs with that one. + let cfg = measured.unwrap_or(cfg); variant.compute.select(); fw::compute_pool::set_base_blocks(cfg.core_layout.compute_blocks) .inspect_err(|_| dev_err!(dev.as_ref(), "Invalid compute pool size\n"))?; @@ -1275,14 +1280,15 @@ impl Gpu { Ok(gpu) } - /// Returns the variant of the loaded firmware build. An identified SoC refuses a build it - /// does not know, before anything is written to the GPU. + /// Returns the variant of the loaded firmware build and the configuration measured with it, + /// if it has its own. An identified SoC refuses a build it does not know, before anything + /// is written to the GPU. fn firmware_variant( dev: &AsahiDrmDevice, firmware: &fw::variant::Firmware, - ) -> Result<&'static fw::Variant> { + ) -> Result<(&'static fw::Variant, Option<&'static Config>)> { let builds = match firmware { - fw::variant::Firmware::Fixed(variant) => return Ok(variant), + fw::variant::Firmware::Fixed(variant) => return Ok((variant, None)), fw::variant::Firmware::Identified(builds) => builds, }; let version = Self::os_firmware_version(dev); @@ -1293,7 +1299,7 @@ impl Gpu { for build in Iterator::chain(preferred, others) { if Self::identify_image(dev, build).is_ok() { dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); - return Ok(build.variant); + return Ok((build.variant, build.config)); } } dev_err!( diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs index c54ffb5c208fe..53ce20c85351a 100644 --- a/drivers/gpu/drm/asahi/hw/mod.rs +++ b/drivers/gpu/drm/asahi/hw/mod.rs @@ -15,6 +15,8 @@ pub(crate) mod t600x; pub(crate) mod t602x; pub(crate) mod t6040; pub(crate) mod t6041; +mod t6041_25g83; +mod t6041_captured; mod t604x; pub(crate) mod t8103; pub(crate) mod t8112; diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index bc59194378ea1..adb596a1921d4 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -53,6 +53,7 @@ pub(crate) const CONFIG: Config = Config { }, region_c: ®ION_C, chip: None, + captured: None, adopt_root_entries: false, fabric_bridge: false, // The firmware's per-slot recovery keys are not known for this build; recovery relies on @@ -100,6 +101,7 @@ pub(crate) const CONFIG: Config = Config { }, ], variant: &fw::variant::V25G83, + config: None, }]), // Measured on the 20-core M4 Pro; other core counts are refused at probe. core_layout: CoreLayout { diff --git a/drivers/gpu/drm/asahi/hw/t6041.rs b/drivers/gpu/drm/asahi/hw/t6041.rs index 40ae028eca940..a6979b1063de3 100644 --- a/drivers/gpu/drm/asahi/hw/t6041.rs +++ b/drivers/gpu/drm/asahi/hw/t6041.rs @@ -42,32 +42,42 @@ pub(crate) const CONFIG: Config = Config { }, region_c: ®ION_C, chip: Some(&CHIP), + captured: None, adopt_root_entries: false, fabric_bridge: false, recovery_slot_keys: None, gpu_generation: GpuGen::G16, - firmware: fw::variant::Firmware::Identified(&[fw::variant::Build { - os_version: [26, 5, 2], - name: "macOS 26.5.2 (25F84, iBoot mBoot-18000.121.3)", - images: &[crate::fwid::FirmwareBuild { - name: "Apple GFX 25F84 for T6041 (normalized loaded image)", - text_size: 0x60000, - data_size: 0x138000, - entropy: [0x5983a, 0x5991d], - text_sha256: [ - 0x6d, 0xa4, 0xd4, 0x42, 0x35, 0x35, 0x14, 0x44, 0xf6, 0xec, 0x00, 0x33, 0xf1, 0x9c, - 0xbf, 0x98, 0xd3, 0x43, 0x91, 0x28, 0x83, 0xe4, 0xe2, 0x94, 0xc1, 0x8b, 0xb8, 0xb8, - 0xa3, 0xc1, 0x83, 0xe8, - ], - }], - // Experimental TA-mode workaround: reported on another J616C/25G83 - // implementation, not yet hardware-verified here on 25F84. Keep the - // shared firmware profiles (in particular M4 Pro's) unchanged. - variant: &fw::variant::Variant { - ta_mode_1: true, - ..fw::variant::V25F84 + // 25F84 runs the static profile of this configuration; 25G83 runs the profile captured on + // the 32-core J616C ([`super::t6041_25g83::CONFIG`]). + firmware: fw::variant::Firmware::Identified(&[ + fw::variant::Build { + os_version: [26, 5, 2], + name: "macOS 26.5.2 (25F84, iBoot mBoot-18000.121.3)", + images: &[crate::fwid::FirmwareBuild { + name: "Apple GFX 25F84 for T6041 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x5983a, 0x5991d], + text_sha256: [ + 0x6d, 0xa4, 0xd4, 0x42, 0x35, 0x35, 0x14, 0x44, 0xf6, 0xec, 0x00, 0x33, 0xf1, + 0x9c, 0xbf, 0x98, 0xd3, 0x43, 0x91, 0x28, 0x83, 0xe4, 0xe2, 0x94, 0xc1, 0x8b, + 0xb8, 0xb8, 0xa3, 0xc1, 0x83, 0xe8, + ], + }], + // Experimental TA-mode workaround: reported on another J616C/25G83 + // implementation, not yet hardware-verified here on 25F84. Keep the + // shared firmware profiles (in particular M4 Pro's) unchanged. + variant: &fw::variant::Variant { + ta_mode_1: true, + ..fw::variant::V25F84 + }, + config: None, }, - }]), + fw::variant::Build { + config: Some(&super::t6041_25g83::CONFIG), + ..super::t6041_25g83::BUILD + }, + ]), core_layout: CoreLayout { cores: Some((4, 40)), operand_state: fw::compute::OperandState::FOUR_BY_TEN, diff --git a/drivers/gpu/drm/asahi/hw/t6041_25g83.rs b/drivers/gpu/drm/asahi/hw/t6041_25g83.rs new file mode 100644 index 0000000000000..a5eba18a13c5d --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t6041_25g83.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! T6041 (M4 Max) on the 25G83 firmware build: the profile captured on the 32-core J616C. +//! +//! The J616C boots through a macOS 26.6.2 stub (iBoot mBoot-18000.161.10), which loads a T6041 +//! image of the 25G83 build the M4 Pro runs ([`fw::variant::V25G83`]). [`super::t6041::CONFIG`] +//! selects this configuration when it identifies that image. +//! +//! The hardware data, power configuration, region C and AON table are the ones built on this +//! unit and captured from it ([`super::t6041_captured`]), with only the runtime words written +//! over them ([`Captured`]): the device tree the J616C boots with has no GPU operating-point, +//! CS or AFR tables and no calibration for the builders. The register windows are the captured +//! register map; the GPU is not started if the two differ. + +use super::t604x::{instances, window}; +use super::{t6041, t6041_captured as captured}; +use crate::g17::fw::{ + self, + initdata::{v25g83::Captured, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, +}; +use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; +use crate::hw::{GpuGen, GpuVariant}; +use crate::mmu::UatQuirks; + +pub(super) const CONFIG: Config = Config { + chip_id: t6041::CHIP_ID, + identity: Identity { + family: 0xa, + // ID register 0x0a04_1100 (J616C): family 0xa, variant 4, revision 0x11 (B1). + variant: 4, + num_dies: 1, + gpu_variant: GpuVariant::C, + usc_generation: 3, + hal_generation: 200, + }, + uat_ias: 42, + uat_oas: 42, + secondary_root_offset: None, + crash_buffers: CrashBuffers::FirmwareData, + pm_metrics: FixedBuffer { + va: 0x10_0008_0000, + size: 0x8000, + }, + windows: ®ISTER_WINDOWS, + reserved_slots: &[], + perf: &PERF, + opps: OPPS, + // The M4 Pro's policy, which the J616C has run with on this build. + uat_quirks: UatQuirks { + reserved_tables: true, + clean_tables: true, + page_tlbi: true, + handoff_lock: false, + handoff_slot: 0xffff_ffff, + native_gpu_permissions: true, + }, + region_c: ®ION_C, + chip: None, + captured: Some(&CAPTURED), + adopt_root_entries: false, + fabric_bridge: false, + recovery_slot_keys: None, + gpu_generation: GpuGen::G16, + firmware: fw::variant::Firmware::Identified(&[BUILD]), + // The cores the contents were captured with; other core counts are refused at probe. + core_layout: CoreLayout { + cores: Some((4, 32)), + // Banks for all forty core slots: the firmware indexes them by physical slot, and the + // J616C's enabled slots run to 39 (core mask 0xf6_fdbf6fdb). + operand_state: fw::compute::OperandState::FOUR_BY_TEN, + deflake: fw::render::Deflake::FOUR_CLUSTERS, + // The compute pool the J616C has run with since 23 Oct 2026; the M4 Pro's 21 blocks + // hang this GPU's firmware on grids of more than ten workgroups. + compute_blocks: 96, + }, +}; + +/// The 25G83 build as the J616C loads it. +pub(super) const BUILD: fw::variant::Build = fw::variant::Build { + os_version: [26, 6, 2], + name: "macOS 26.6.2 (25G83, iBoot mBoot-18000.161.10)", + images: &[crate::fwid::FirmwareBuild { + name: "Apple GFX 25G83 for T6041 (normalized loaded image)", + text_size: 0x60000, + data_size: 0x138000, + entropy: [0x59839, 0x5991c], + text_sha256: [ + 0xaa, 0x11, 0x3e, 0xd6, 0x20, 0x79, 0xbc, 0xfd, 0xee, 0xf0, 0xf8, 0x6c, 0x2a, 0x8d, + 0x7e, 0xae, 0x9f, 0x90, 0x7b, 0x03, 0xbe, 0x28, 0xcc, 0xf2, 0x98, 0xc9, 0x43, 0x95, + 0x1d, 0x0e, 0xff, 0xc5, + ], + }], + // Non-clustered tiling (TA mode 1), as for 25F84. Clustered tiling hangs the J616C unless + // the cluster buffers are sized for four clusters, which this driver does not do yet. + variant: &fw::variant::Variant { + ta_mode_1: true, + ..fw::variant::V25G83 + }, + config: None, +}; + +/// The captured register map: slot 3 holds the six memory controllers (ADT +/// `apple,sgx-active-mcc-bit-vector` 0x3f, 32 MiB apart), slot 20 the four AFR blocks. The +/// windows after slot 3 lie 0x30000 further than the M4 Pro's. +const REGISTER_WINDOWS: [RegisterWindow; 25] = [ + window(0, 0x5_0241_4000, 0x20_0000, 0x4000, 0, 2), + instances( + window(3, 0x2_201c_4000, 0x20_8000, 0x1_8000, 0, 2), + 6, + 0x200_0000, + ), + window(4, 0x5_0244_c000, 0x29_c000, 0x4000, 0, 0), + window(9, 0x5_003d_0000, 0x2a_4000, 0x4000, 0, 2), + window(10, 0x5_003c_0000, 0x2a_c000, 0x4000, 0, 0), + window(11, 0x5_003d_8000, 0x2b_4000, 0x4000, 0, 2), + window(12, 0x4_8565_c000, 0x2b_c000, 0x4000, 0, 2), + window(14, 0x5_0040_0000, 0x2c_4000, 0x1_0000, 0, 0), + window(15, 0x5_0cdc_0000, 0x2d_8000, 0x4000, 0, 0), + window(17, 0x2_8800_0000, 0x2e_0000, 0x2_1400, 0, 2), + instances( + window(20, 0x2_8b00_0000, 0x30_8000, 0x4_0000, 0, 2), + 4, + 0x20_0000, + ), + window(22, 0x2_8900_0000, 0x40_c000, 0x8000, 0, 2), + window(26, 0x2_88d0_4000, 0x41_8000, 0x8000, 0xd0_4000, 2), + window(28, 0x2_88d5_8000, 0x42_4000, 0x8000, 0xd5_8000, 2), + window(29, 0x2_88d1_0000, 0x43_0000, 0x4000, 0xd1_0000, 2), + window(31, 0x2_88d4_0000, 0x43_8000, 0x4000, 0xd4_0000, 2), + window(32, 0x2_88d6_0000, 0x44_0000, 0x2_0000, 0xd6_0000, 2), + window(33, 0x2_88d4_4000, 0x46_4000, 0x4000, 0xd4_4000, 2), + window(34, 0x2_88d4_c000, 0x46_c000, 0x200, 0xd4_c000, 2), + window(35, 0x2_88e0_0000, 0x47_4000, 0x4000, 0xe0_0000, 2), + window(37, 0x2_88e4_0000, 0x47_c000, 0x4000, 0xe4_0000, 2), + window(39, 0x2_88e0_8000, 0x48_4000, 0x8000, 0, 2), + window(40, 0x2_88e1_c000, 0x49_0000, 0x4000, 0xe1_c000, 2), + window(41, 0x2_88e1_f800, 0x49_b800, 0x4000, 0, 2), + window(43, 0x2_88e6_0000, 0x4a_4000, 0x58, 0xe6_0000, 2), +]; + +/// The ADT ladders of this unit (`apple,m4-index-a`, `apple,m4-index-b`, `apple,m4-relative-a`, +/// `apple,m4-relative-b`), which the captured performance tables hold. The builders, which +/// would use them, do not run for this configuration. +const PERF: PerfConfig = PerfConfig { + states_a: [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15], + states_b: [0, 1, 2, 3, 4, 5, 7, 9, 11, 13, 15], + relative_a: [0, 9, 16, 23, 29, 38, 49, 60, 69, 79, 100], + relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + sram_min_mv: 810, +}; + +const OPPS: usize = 16; + +const _: () = { + let mut index = 0; + while index < PERF_STATES { + assert!((PERF.states_a[index] as usize) < OPPS); + assert!((PERF.states_b[index] as usize) < OPPS); + index += 1; + } +}; + +/// Region C as captured: the M4 Pro's with this unit's power limit (110586 mW, +/// `apple,m4-max-power-mw`). Only the builders would use it. +const REGION_C: RegionCConfig = RegionCConfig { + unk_54: 0x0fa0, + unk_78: [1, 0, 0], + max_power_mw: 110_586, + unk_9c: 0, + unk_a0: 0, + unk_7c4: 1500, + unk_9e0: [ + 1, + 0x0102_0200, + 0x010a_2801, + 0x2802_0101, + 0x0101_010a, + 0x010a_2802, + 0x2801_0101, + 0x0001_010a, + ], +}; + +/// The contents captured on the J616C on 25 Oct 2026. +const CAPTURED: Captured = Captured { + hw_data: captured::HW_DATA, + power_config: captured::POWER_CONFIG, + region_c: captured::REGION_C, + aon_table: &captured::AON_TABLE, + active_cores: 32, + expected: captured::EXPECTED, +}; diff --git a/drivers/gpu/drm/asahi/hw/t6041_captured.rs b/drivers/gpu/drm/asahi/hw/t6041_captured.rs new file mode 100644 index 0000000000000..bbc8ef18e864d --- /dev/null +++ b/drivers/gpu/drm/asahi/hw/t6041_captured.rs @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! The 25G83 initialization contents of the M4 Max (T6041), captured on the J616C. +//! +//! Generated from `j616c-fwdata-dump-25oct.txt` (sha256 `fbf179298c02a67e…`), the nonzero +//! 32-byte rows of the hardware data, power configuration, region C and AON table that the +//! previous T6041 driver (j616c/b25, Gravity Linux encoders evaluated from the device tree) +//! built on this unit on 25 Oct 2026, the contents the GPU has run with since 22 Oct. +//! +//! The words the host writes at every boot are left out (zero here): the completion-ring +//! descriptors and QoS pointer of the hardware data, the configuration-status pointer, AON +//! pointer and random word of the power configuration. [`EXPECTED`] holds the hashes +//! ([`hash_without`](crate::g17::fw::initdata::v25g83::hash_without), without those words) of +//! the dumped objects; the GPU is not started if what the driver wrote differs. +//! +//! What differs from the M4 Pro constants: chip 0x6041, 32 enabled cores, this unit's binned +//! operating points (four voltage lanes, the 15-step ladders of the ADT), six memory controller +//! instances in register slot 3 (and the register windows after it 0x30000 further), the 110586 +//! mW power limit (`apple,m4-max-power-mw`, the M4 Pro's scaled to 32 cores), four GPU cluster +//! temperature sources, four clusters' leakage coefficients and the second temperature-sensor +//! mask word at `+0x3b5c`. + +/// The hardware data ([`HwData25G83`], `0x3db4` bytes at bundle `+0`), without the +/// completion-ring descriptors and the QoS pointer. +pub(super) const HW_DATA: &[(usize, u32, usize)] = &[ + (0x0004, 0x0000006f, 1), + (0x0008, 0xffc00000, 1), + (0x0014, 0x00000010, 1), + (0x001c, 0x00000010, 1), + (0x0020, 0xffff8000, 1), + (0x0024, 0x000002ff, 1), + (0x0028, 0x81400000, 1), + (0x002c, 0xfffffc21, 1), + (0x0640, 0x02414000, 1), + (0x0644, 0x00000005, 1), + (0x0648, 0x80200000, 1), + (0x064c, 0xfffffc21, 1), + (0x0650, 0x00004000, 2), + (0x0660, 0x00000002, 1), + (0x06b8, 0x201c4000, 1), + (0x06bc, 0x00000002, 1), + (0x06c0, 0x80208000, 1), + (0x06c4, 0xfffffc21, 1), + (0x06c8, 0x00090000, 1), + (0x06cc, 0x00018000, 1), + (0x06d8, 0x00000002, 1), + (0x06e0, 0x0244c000, 1), + (0x06e4, 0x00000005, 1), + (0x06e8, 0x8029c000, 1), + (0x06ec, 0xfffffc21, 1), + (0x06f0, 0x00004000, 2), + (0x07a8, 0x003d0000, 1), + (0x07ac, 0x00000005, 1), + (0x07b0, 0x802a4000, 1), + (0x07b4, 0xfffffc21, 1), + (0x07b8, 0x00004000, 2), + (0x07c8, 0x00000002, 1), + (0x07d0, 0x003c0000, 1), + (0x07d4, 0x00000005, 1), + (0x07d8, 0x802ac000, 1), + (0x07dc, 0xfffffc21, 1), + (0x07e0, 0x00004000, 2), + (0x07f8, 0x003d8000, 1), + (0x07fc, 0x00000005, 1), + (0x0800, 0x802b4000, 1), + (0x0804, 0xfffffc21, 1), + (0x0808, 0x00004000, 2), + (0x0818, 0x00000002, 1), + (0x0820, 0x8565c000, 1), + (0x0824, 0x00000004, 1), + (0x0828, 0x802bc000, 1), + (0x082c, 0xfffffc21, 1), + (0x0830, 0x00004000, 2), + (0x0840, 0x00000002, 1), + (0x0870, 0x00400000, 1), + (0x0874, 0x00000005, 1), + (0x0878, 0x802c4000, 1), + (0x087c, 0xfffffc21, 1), + (0x0880, 0x00010000, 2), + (0x0898, 0x0cdc0000, 1), + (0x089c, 0x00000005, 1), + (0x08a0, 0x802d8000, 1), + (0x08a4, 0xfffffc21, 1), + (0x08a8, 0x00004000, 2), + (0x08e8, 0x88000000, 1), + (0x08ec, 0x00000002, 1), + (0x08f0, 0x802e0000, 1), + (0x08f4, 0xfffffc21, 1), + (0x08f8, 0x00021400, 2), + (0x0908, 0x00000002, 1), + (0x0960, 0x8b000000, 1), + (0x0964, 0x00000002, 1), + (0x0968, 0x80308000, 1), + (0x096c, 0xfffffc21, 1), + (0x0970, 0x00100000, 1), + (0x0974, 0x00040000, 1), + (0x0980, 0x00000002, 1), + (0x09b0, 0x89000000, 1), + (0x09b4, 0x00000002, 1), + (0x09b8, 0x8040c000, 1), + (0x09bc, 0xfffffc21, 1), + (0x09c0, 0x00008000, 2), + (0x09d0, 0x00000002, 1), + (0x0a50, 0x88d04000, 1), + (0x0a54, 0x00000002, 1), + (0x0a58, 0x80418000, 1), + (0x0a5c, 0xfffffc21, 1), + (0x0a60, 0x00008000, 2), + (0x0a68, 0x00d04000, 1), + (0x0a70, 0x00000002, 1), + (0x0aa0, 0x88d58000, 1), + (0x0aa4, 0x00000002, 1), + (0x0aa8, 0x80424000, 1), + (0x0aac, 0xfffffc21, 1), + (0x0ab0, 0x00008000, 2), + (0x0ab8, 0x00d58000, 1), + (0x0ac0, 0x00000002, 1), + (0x0ac8, 0x88d10000, 1), + (0x0acc, 0x00000002, 1), + (0x0ad0, 0x80430000, 1), + (0x0ad4, 0xfffffc21, 1), + (0x0ad8, 0x00004000, 2), + (0x0ae0, 0x00d10000, 1), + (0x0ae8, 0x00000002, 1), + (0x0b18, 0x88d40000, 1), + (0x0b1c, 0x00000002, 1), + (0x0b20, 0x80438000, 1), + (0x0b24, 0xfffffc21, 1), + (0x0b28, 0x00004000, 2), + (0x0b30, 0x00d40000, 1), + (0x0b38, 0x00000002, 1), + (0x0b40, 0x88d60000, 1), + (0x0b44, 0x00000002, 1), + (0x0b48, 0x80440000, 1), + (0x0b4c, 0xfffffc21, 1), + (0x0b50, 0x00020000, 2), + (0x0b58, 0x00d60000, 1), + (0x0b60, 0x00000002, 1), + (0x0b68, 0x88d44000, 1), + (0x0b6c, 0x00000002, 1), + (0x0b70, 0x80464000, 1), + (0x0b74, 0xfffffc21, 1), + (0x0b78, 0x00004000, 2), + (0x0b80, 0x00d44000, 1), + (0x0b88, 0x00000002, 1), + (0x0b90, 0x88d4c000, 1), + (0x0b94, 0x00000002, 1), + (0x0b98, 0x8046c000, 1), + (0x0b9c, 0xfffffc21, 1), + (0x0ba0, 0x00000200, 2), + (0x0ba8, 0x00d4c000, 1), + (0x0bb0, 0x00000002, 1), + (0x0bb8, 0x88e00000, 1), + (0x0bbc, 0x00000002, 1), + (0x0bc0, 0x80474000, 1), + (0x0bc4, 0xfffffc21, 1), + (0x0bc8, 0x00004000, 2), + (0x0bd0, 0x00e00000, 1), + (0x0bd8, 0x00000002, 1), + (0x0c08, 0x88e40000, 1), + (0x0c0c, 0x00000002, 1), + (0x0c10, 0x8047c000, 1), + (0x0c14, 0xfffffc21, 1), + (0x0c18, 0x00004000, 2), + (0x0c20, 0x00e40000, 1), + (0x0c28, 0x00000002, 1), + (0x0c58, 0x88e08000, 1), + (0x0c5c, 0x00000002, 1), + (0x0c60, 0x80484000, 1), + (0x0c64, 0xfffffc21, 1), + (0x0c68, 0x00008000, 2), + (0x0c78, 0x00000002, 1), + (0x0c80, 0x88e1c000, 1), + (0x0c84, 0x00000002, 1), + (0x0c88, 0x80490000, 1), + (0x0c8c, 0xfffffc21, 1), + (0x0c90, 0x00004000, 2), + (0x0c98, 0x00e1c000, 1), + (0x0ca0, 0x00000002, 1), + (0x0ca8, 0x88e1f800, 1), + (0x0cac, 0x00000002, 1), + (0x0cb0, 0x8049b800, 1), + (0x0cb4, 0xfffffc21, 1), + (0x0cb8, 0x00004000, 2), + (0x0cc8, 0x00000002, 1), + (0x0cf8, 0x88e60000, 1), + (0x0cfc, 0x00000002, 1), + (0x0d00, 0x804a4000, 1), + (0x0d04, 0xfffffc21, 1), + (0x0d08, 0x00000058, 2), + (0x0d10, 0x00e60000, 1), + (0x0d18, 0x00000002, 1), + (0x0e90, 0x00006041, 1), + (0x0e94, 0x00000001, 2), + (0x0ea0, 0x00000001, 2), + (0x0eb8, 0x00000001, 1), + (0x0ec0, 0x00000001, 1), + (0x0ec8, 0x00000001, 1), + (0x0ed0, 0x00005dc0, 1), + (0x0ed4, 0x00000001, 1), + (0x0ed8, 0x00000008, 1), + (0x0ee0, 0x00000001, 3), + (0x0f04, 0x0000001f, 1), + (0x0f24, 0x07002004, 1), + (0x0f28, 0x00000006, 1), + (0x0f34, 0x00000001, 2), + (0x0f4c, 0x00000031, 1), + (0x0f88, 0x00000020, 1), + (0x0f8c, 0x0000000f, 1), + (0x0fb8, 0x0000001c, 1), + (0x0fbc, 0x00000004, 1), + (0x0fc0, 0x00000020, 1), + (0x0fc4, 0x0000000a, 1), + (0x0fcc, 0x00000152, 1), + (0x0fd0, 0x0000026a, 1), + (0x0fd4, 0x0000031c, 1), + (0x0fd8, 0x0000039c, 1), + (0x0fdc, 0x00000420, 1), + (0x0fe0, 0x0000049e, 1), + (0x0fe4, 0x00000520, 1), + (0x0fe8, 0x00000564, 1), + (0x0fec, 0x000005be, 1), + (0x0ff0, 0x0000062a, 1), + (0x1008, 0x0000007d, 4), + (0x1048, 0x00000258, 1), + (0x104c, 0x00000253, 1), + (0x1050, 0x00000267, 1), + (0x1054, 0x00000258, 1), + (0x1088, 0x0000028a, 1), + (0x108c, 0x00000285, 1), + (0x1090, 0x00000294, 2), + (0x10c8, 0x000002b7, 1), + (0x10cc, 0x000002b2, 1), + (0x10d0, 0x000002c1, 1), + (0x10d4, 0x000002bc, 1), + (0x1108, 0x000002d0, 2), + (0x1110, 0x000002d5, 1), + (0x1114, 0x000002cb, 1), + (0x1148, 0x00000302, 1), + (0x114c, 0x000002f8, 1), + (0x1150, 0x00000307, 1), + (0x1154, 0x00000302, 1), + (0x1188, 0x0000032f, 1), + (0x118c, 0x00000325, 3), + (0x11c8, 0x00000366, 1), + (0x11cc, 0x00000352, 1), + (0x11d0, 0x00000357, 1), + (0x11d4, 0x0000035c, 1), + (0x1208, 0x0000038e, 1), + (0x120c, 0x0000037a, 1), + (0x1210, 0x0000037f, 1), + (0x1214, 0x00000384, 1), + (0x1248, 0x000003b6, 1), + (0x124c, 0x000003a2, 1), + (0x1250, 0x000003a7, 1), + (0x1254, 0x000003ac, 1), + (0x1288, 0x00000406, 1), + (0x128c, 0x00000401, 1), + (0x1290, 0x00000406, 1), + (0x1294, 0x000003fc, 1), + (0x1408, 0x0000032a, 4), + (0x1448, 0x0000032a, 4), + (0x1488, 0x0000032a, 4), + (0x14c8, 0x0000032a, 4), + (0x1508, 0x0000032a, 4), + (0x1548, 0x0000032a, 4), + (0x1588, 0x0000032f, 1), + (0x158c, 0x0000032a, 3), + (0x15c8, 0x00000366, 1), + (0x15cc, 0x00000352, 1), + (0x15d0, 0x00000357, 1), + (0x15d4, 0x0000035c, 1), + (0x1608, 0x0000038e, 1), + (0x160c, 0x0000037a, 1), + (0x1610, 0x0000037f, 1), + (0x1614, 0x00000384, 1), + (0x1648, 0x000003b6, 1), + (0x164c, 0x000003a2, 1), + (0x1650, 0x000003a7, 1), + (0x1654, 0x000003ac, 1), + (0x1688, 0x00000406, 1), + (0x168c, 0x00000401, 1), + (0x1690, 0x00000406, 1), + (0x1694, 0x000003fc, 1), + (0x180c, 0x00000152, 1), + (0x1810, 0x0000026a, 1), + (0x1814, 0x0000031c, 1), + (0x1818, 0x0000039c, 1), + (0x181c, 0x000003b8, 1), + (0x1820, 0x00000426, 1), + (0x1824, 0x0000049e, 1), + (0x1828, 0x000004da, 1), + (0x182c, 0x0000052e, 1), + (0x1830, 0x0000062a, 1), + (0x1848, 0x3f828f5c, 11), + (0x18cc, 0x00000009, 1), + (0x18d0, 0x00000010, 1), + (0x18d4, 0x00000017, 1), + (0x18d8, 0x0000001d, 1), + (0x18dc, 0x00000026, 1), + (0x18e0, 0x00000031, 1), + (0x18e4, 0x0000003c, 1), + (0x18e8, 0x00000045, 1), + (0x18ec, 0x0000004f, 1), + (0x18f0, 0x00000064, 1), + (0x1910, 0x00000016, 1), + (0x1914, 0x00000024, 1), + (0x1918, 0x0000002f, 1), + (0x191c, 0x00000039, 1), + (0x1920, 0x00000044, 1), + (0x1924, 0x0000004e, 1), + (0x1928, 0x00000054, 1), + (0x192c, 0x0000005b, 1), + (0x1930, 0x00000064, 1), + (0x19cc, 0x00000001, 1), + (0x19d0, 0x00000002, 1), + (0x19d4, 0x00000003, 1), + (0x19d8, 0x00000004, 1), + (0x19dc, 0x00000006, 1), + (0x19e0, 0x00000008, 1), + (0x19e4, 0x0000000a, 1), + (0x19e8, 0x0000000c, 1), + (0x19ec, 0x0000000e, 1), + (0x19f0, 0x0000000f, 1), + (0x1a0c, 0x00000001, 1), + (0x1a10, 0x00000002, 1), + (0x1a14, 0x00000003, 1), + (0x1a18, 0x00000004, 1), + (0x1a1c, 0x00000005, 1), + (0x1a20, 0x00000007, 1), + (0x1a24, 0x00000009, 1), + (0x1a28, 0x0000000b, 1), + (0x1a2c, 0x0000000d, 1), + (0x1a30, 0x0000000f, 1), + (0x1a48, 0x00000007, 2), + (0x1a50, 0x00000018, 1), + (0x1a54, 0x00000152, 1), + (0x1a58, 0x0000026a, 1), + (0x1a5c, 0x0000031c, 1), + (0x1a60, 0x000003e0, 1), + (0x1a64, 0x000004a4, 1), + (0x1a68, 0x00000564, 1), + (0x1a6c, 0x000005be, 1), + (0x1a90, 0x00000249, 1), + (0x1a98, 0x00000249, 1), + (0x1aa0, 0x00000276, 1), + (0x1aa8, 0x000002a8, 1), + (0x1ab0, 0x000002d0, 1), + (0x1ab8, 0x00000302, 1), + (0x1ac0, 0x00000361, 1), + (0x1ac8, 0x00000384, 1), + (0x1b10, 0x0000032a, 1), + (0x1b18, 0x0000032a, 1), + (0x1b20, 0x0000032a, 1), + (0x1b28, 0x0000032a, 1), + (0x1b30, 0x0000032a, 1), + (0x1b38, 0x0000032a, 1), + (0x1b40, 0x00000361, 1), + (0x1b48, 0x00000384, 1), + (0x1b90, 0x00000007, 2), + (0x1b98, 0x00000018, 1), + (0x1b9c, 0x00000152, 1), + (0x1ba0, 0x0000026a, 1), + (0x1ba4, 0x0000031c, 1), + (0x1ba8, 0x000003e0, 1), + (0x1bac, 0x000004a4, 1), + (0x1bb0, 0x00000564, 1), + (0x1bb4, 0x000005be, 1), + (0x1bd8, 0x00000249, 1), + (0x1be0, 0x00000249, 1), + (0x1be8, 0x00000276, 1), + (0x1bf0, 0x000002a8, 1), + (0x1bf8, 0x000002d0, 1), + (0x1c00, 0x00000302, 1), + (0x1c08, 0x00000361, 1), + (0x1c10, 0x00000384, 1), + (0x1c58, 0x0000032a, 1), + (0x1c60, 0x0000032a, 1), + (0x1c68, 0x0000032a, 1), + (0x1c70, 0x0000032a, 1), + (0x1c78, 0x0000032a, 1), + (0x1c80, 0x0000032a, 1), + (0x1c88, 0x00000361, 1), + (0x1c90, 0x00000384, 1), + (0x1cd8, 0x0000000a, 1), + (0x1ce0, 0x00000152, 1), + (0x1ce4, 0x0000026a, 1), + (0x1ce8, 0x0000031c, 1), + (0x1cec, 0x0000039c, 1), + (0x1cf0, 0x000003b8, 1), + (0x1cf4, 0x00000426, 1), + (0x1cf8, 0x0000049e, 1), + (0x1cfc, 0x000004da, 1), + (0x1d00, 0x0000052e, 1), + (0x1d04, 0x0000062a, 1), + (0x1d1c, 0x0000007d, 4), + (0x1d5c, 0x00000258, 1), + (0x1d60, 0x00000253, 1), + (0x1d64, 0x00000267, 1), + (0x1d68, 0x00000258, 1), + (0x1d9c, 0x0000028a, 1), + (0x1da0, 0x00000285, 1), + (0x1da4, 0x00000294, 2), + (0x1ddc, 0x000002b7, 1), + (0x1de0, 0x000002b2, 1), + (0x1de4, 0x000002c1, 1), + (0x1de8, 0x000002bc, 1), + (0x1e1c, 0x000002d0, 2), + (0x1e24, 0x000002d5, 1), + (0x1e28, 0x000002cb, 1), + (0x1e5c, 0x00000302, 1), + (0x1e60, 0x000002f8, 1), + (0x1e64, 0x00000307, 1), + (0x1e68, 0x00000302, 1), + (0x1e9c, 0x0000032f, 1), + (0x1ea0, 0x00000325, 3), + (0x1edc, 0x00000366, 1), + (0x1ee0, 0x00000352, 1), + (0x1ee4, 0x00000357, 1), + (0x1ee8, 0x0000035c, 1), + (0x1f1c, 0x0000038e, 1), + (0x1f20, 0x0000037a, 1), + (0x1f24, 0x0000037f, 1), + (0x1f28, 0x00000384, 1), + (0x1f5c, 0x000003b6, 1), + (0x1f60, 0x000003a2, 1), + (0x1f64, 0x000003a7, 1), + (0x1f68, 0x000003ac, 1), + (0x1f9c, 0x00000406, 1), + (0x1fa0, 0x00000401, 1), + (0x1fa4, 0x00000406, 1), + (0x1fa8, 0x000003fc, 1), + (0x211c, 0x0000032a, 4), + (0x215c, 0x0000032a, 4), + (0x219c, 0x0000032a, 4), + (0x21dc, 0x0000032a, 4), + (0x221c, 0x0000032a, 4), + (0x225c, 0x0000032a, 4), + (0x229c, 0x0000032f, 1), + (0x22a0, 0x0000032a, 3), + (0x22dc, 0x00000366, 1), + (0x22e0, 0x00000352, 1), + (0x22e4, 0x00000357, 1), + (0x22e8, 0x0000035c, 1), + (0x231c, 0x0000038e, 1), + (0x2320, 0x0000037a, 1), + (0x2324, 0x0000037f, 1), + (0x2328, 0x00000384, 1), + (0x235c, 0x000003b6, 1), + (0x2360, 0x000003a2, 1), + (0x2364, 0x000003a7, 1), + (0x2368, 0x000003ac, 1), + (0x239c, 0x00000406, 1), + (0x23a0, 0x00000401, 1), + (0x23a4, 0x00000406, 1), + (0x23a8, 0x000003fc, 1), + (0x2544, 0x00000005, 1), + (0x2548, 0x00000006, 1), + (0x254c, 0x00000003, 1), + (0x2550, 0x00000007, 2), + (0x2570, 0x00000020, 1), + (0x2578, 0x00000001, 1), + (0x259c, 0x00000001, 3), + (0x25ac, 0x00000001, 3), + (0x25bc, 0xffffffff, 12), + (0x25f4, 0x00000002, 1), + (0x25fc, 0x00000001, 1), + (0x2614, 0x00000001, 1), + (0x261c, 0x00000100, 1), + (0x26c0, 0x00000001, 2), + (0x26f4, 0x00000001, 1), +]; + +/// The power configuration and its auxiliary data ([`PowerConfig25G83`], `0x6510` bytes at +/// bundle `+0x5380`), without the configuration-status and AON pointers and the random word. +pub(super) const POWER_CONFIG: &[(usize, u32, usize)] = &[ + (0x0004, 0x00030d40, 2), + (0x0010, 0x00000004, 1), + (0x0014, 0x3f800000, 1), + (0x002c, 0x00000001, 2), + (0x0040, 0x00000064, 1), + (0x0044, 0x00000001, 1), + (0x0048, 0x000003e8, 1), + (0x0050, 0x00000064, 1), + (0x0060, 0x00000004, 1), + (0x0064, 0x3f800000, 1), + (0x006c, 0x00000001, 2), + (0x0080, 0x00000064, 1), + (0x0084, 0x00000001, 1), + (0x0088, 0x000003e8, 1), + (0x0090, 0x00000064, 1), + (0x00c0, 0x3f828f5c, 11), + (0x09b8, 0x00000271, 1), + (0x09c4, 0x3f7f2e9f, 1), + (0x09cc, 0x3b516154, 1), + (0x09d4, 0x3ca59586, 1), + (0x09e0, 0x47d7fd00, 1), + (0x09e4, 0x40a90fdb, 1), + (0x09ec, 0xbc1427fc, 1), + (0x09f0, 0x00000064, 1), + (0x09f4, 0x000003e8, 2), + (0x0a04, 0x0001affa, 1), + (0x0a38, 0x000003e8, 1), + (0x0a70, 0x00000008, 1), + (0x0a7c, 0x3f000000, 1), + (0x0a84, 0x3f000000, 1), + (0x0a8c, 0x3e3bd7b3, 1), + (0x0a98, 0x47800000, 1), + (0x0a9c, 0x3ecf2e49, 1), + (0x0aa8, 0x00000064, 1), + (0x0aac, 0x000003e8, 1), + (0x0abc, 0x0001affa, 1), + (0x0ac8, 0x00000010, 1), + (0x0ad0, 0x0005dc00, 1), + (0x0b28, 0x00000050, 1), + (0x0b30, 0x00000055, 1), + (0x0b34, 0x00000032, 1), + (0x0b38, 0x00000006, 1), + (0x0b40, 0x00000006, 1), + (0x0b44, 0x00000001, 1), + (0x0b4c, 0x3f4ccccd, 1), + (0x0b50, 0x3f7eb852, 1), + (0x0b54, 0x3e4ccccd, 1), + (0x0b58, 0x3ba3d70a, 1), + (0x0b5c, 0x3f64c986, 2), + (0x0b68, 0x42be0000, 1), + (0x0b6c, 0x403ea7f0, 2), + (0x0b74, 0x41179436, 1), + (0x0b78, 0x00000064, 1), + (0x0b7c, 0x000003e8, 1), + (0x0b80, 0x00000064, 1), + (0x0b8c, 0x00000050, 1), + (0x0bc0, 0x00000064, 1), + (0x1bec, 0xff000000, 1), + (0x1bf0, 0xff7fff7f, 255), + (0x1fec, 0x007fff7f, 1), + (0x21f0, 0x47800000, 1), + (0x21f4, 0x42c80000, 1), + (0x21f8, 0x447a0000, 1), + (0x21fc, 0x0000062a, 1), + (0x2210, 0x00000064, 1), + (0x2218, 0x42c80000, 1), + (0x221c, 0x000003e8, 1), + (0x2224, 0x3f4ccccd, 1), + (0x2228, 0x3e4ccccd, 1), + (0x2268, 0x2a222aa2, 1), + (0x2270, 0x00001f40, 1), + (0x2278, 0x00000004, 1), + (0x228c, 0x3f800000, 1), + (0x2294, 0x414eeeee, 1), + (0x22a0, 0x47800000, 1), + (0x22a4, 0x42140000, 1), + (0x22b0, 0x00000064, 1), + (0x22b4, 0x000003e8, 2), + (0x22c4, 0x00002af8, 1), + (0x22f8, 0x000003e8, 1), + (0x2728, 0xb8520000, 1), + (0x272c, 0x00003f7e, 1), + (0x2730, 0xd70a0000, 1), + (0x2734, 0x00003ba3, 1), + (0x273c, 0x000043c8, 1), + (0x2748, 0x00004780, 1), + (0x274c, 0x000042c8, 1), + (0x2754, 0x0384bac8, 1), + (0x2758, 0x03e80000, 2), + (0x2764, 0x80000000, 1), + (0x2768, 0x26ac453b, 1), + (0x279c, 0x03e80000, 1), + (0x27b8, 0x00c80000, 1), + (0x2870, 0x000003e8, 1), + (0x28c0, 0x00000001, 1), + (0x28c8, 0x0001affa, 3), + (0x28e0, 0x3f800000, 1), + (0x28e4, 0x00000004, 1), + (0x28e8, 0x00000008, 1), + (0x28ec, 0x0002ee00, 1), + (0x29bc, 0x0000007c, 1), + (0x29c8, 0x3f77bdef, 1), + (0x29d0, 0x3d042108, 1), + (0x29d8, 0x3d25bea1, 1), + (0x29e4, 0x47800000, 1), + (0x29e8, 0x3f4f2e49, 1), + (0x29f4, 0x00000028, 1), + (0x29f8, 0x000003e8, 2), + (0x2a04, 0x47d7fd00, 1), + (0x2a08, 0x0001affa, 1), + (0x2a14, 0x000000f8, 1), + (0x2a1c, 0x005ad200, 1), + (0x2a3c, 0x000003e8, 1), + (0x2b4c, 0x00000032, 1), + (0x2b50, 0x00000001, 1), + (0x2b58, 0x3f638e39, 1), + (0x2b5c, 0x3f2aaaab, 1), + (0x2b60, 0x3de38e39, 1), + (0x2b64, 0x3eaaaaab, 1), + (0x2b68, 0xbecccccd, 2), + (0x2b74, 0x47800000, 1), + (0x2b78, 0xc0a00000, 2), + (0x2b84, 0x00000064, 1), + (0x2b88, 0x000003e8, 1), + (0x2b8c, 0x00000064, 1), + (0x2b94, 0x45fa0000, 1), + (0x2b98, 0x00000578, 1), + (0x2ba4, 0x00000048, 1), + (0x2ba8, 0x00000018, 1), + (0x2bac, 0x001a5e00, 1), + (0x2bb4, 0x0008ca00, 1), + (0x2bc0, 0x47800000, 1), + (0x2be8, 0x00001f40, 1), + (0x2bec, 0x00000004, 1), + (0x2bf0, 0x000009c4, 1), + (0x2bf4, 0x00000258, 1), + (0x2bf8, 0x00000002, 1), + (0x2bfc, 0x00000004, 1), + (0x2c00, 0x00000032, 1), + (0x2c30, 0x47800000, 1), + (0x2c40, 0x00000064, 1), + (0x2c44, 0x000003e8, 1), + (0x2e2c, 0x00000004, 1), + (0x2e38, 0x3f780000, 1), + (0x2e40, 0x3d000000, 1), + (0x2e48, 0x40a00000, 1), + (0x2e54, 0x47800000, 1), + (0x2e58, 0x41700000, 1), + (0x2e64, 0x00000384, 1), + (0x2e68, 0x000003e8, 2), + (0x2e78, 0x000003e8, 1), + (0x2eac, 0x000003e8, 1), + (0x2eec, 0x00000064, 1), + (0x2f08, 0x2a222aa2, 1), + (0x2f10, 0x0000007d, 1), + (0x2f14, 0x00000001, 1), + (0x3b58, 0x2a222aa2, 2), + (0x3b60, 0x00000001, 1), + (0x5b64, 0x40a00000, 1), + (0x6320, 0x67305452, 1), + (0x6324, 0x67315452, 1), + (0x6328, 0x67325452, 1), + (0x632c, 0x67335452, 1), + (0x6360, 0x00000005, 1), + (0x6368, 0x72305452, 1), + (0x6370, 0x00000001, 1), + (0x6420, 0x00000001, 1), + (0x642c, 0x44f03000, 1), + (0x6430, 0x44f30000, 1), + (0x6434, 0x44f03000, 1), + (0x6438, 0x44f30000, 1), + (0x64ac, 0x43730000, 4), + (0x64fc, 0x437b0000, 1), + (0x6504, 0x43bdc000, 1), +]; + +/// Region C (`0x1000` bytes). +pub(super) const REGION_C: &[(usize, u32, usize)] = &[ + (0x0024, 0x00000bb8, 1), + (0x0030, 0x00000001, 2), + (0x003c, 0x00000078, 1), + (0x0054, 0x00280fa0, 1), + (0x0058, 0x0000ffff, 1), + (0x005c, 0x00010000, 2), + (0x0064, 0x00030000, 1), + (0x0068, 0x00010000, 1), + (0x0078, 0x00000001, 1), + (0x0084, 0x0001affa, 1), + (0x0088, 0x000003e8, 2), + (0x0098, 0x000003e8, 1), + (0x07c4, 0x000005dc, 1), + (0x07c8, 0x3f800000, 1), + (0x07cc, 0x3f4ccccd, 1), + (0x07d0, 0x3e4ccccd, 1), + (0x07d4, 0x3f666666, 1), + (0x07d8, 0x3dcccccd, 1), + (0x07dc, 0x3e800000, 1), + (0x07e0, 0x3f19999a, 1), + (0x07e4, 0x3f666666, 1), + (0x07e8, 0x00000006, 1), + (0x07ec, 0x00000001, 2), + (0x07f4, 0x00000064, 2), + (0x0998, 0x00000028, 1), + (0x099c, 0x0000000a, 1), + (0x09a0, 0x000000fa, 1), + (0x09a4, 0x00000001, 1), + (0x09b8, 0x00000002, 1), + (0x09bc, 0x00000028, 1), + (0x09c0, 0x00000005, 1), + (0x09c8, 0x00000028, 1), + (0x09cc, 0x00000032, 1), + (0x09d0, 0x00000001, 1), + (0x09e0, 0x00000001, 1), + (0x09e4, 0x01020200, 1), + (0x09e8, 0x010a2801, 1), + (0x09ec, 0x28020101, 1), + (0x09f0, 0x0101010a, 1), + (0x09f4, 0x010a2802, 1), + (0x09f8, 0x28010101, 1), + (0x09fc, 0x0001010a, 1), + (0x0e1c, 0x00000100, 1), + (0x0e38, 0x00000300, 1), + (0x0e3c, 0x00000100, 1), +]; + +/// The AON temperature-source rows (12 bytes each) the power configuration names: GPU +/// clusters 0 to 3 and the SoC. +pub(super) const AON_TABLE: [u32; 15] = [ + 0x67305452, 0x00c20001, 0x00001401, 0x67315452, 0x00970001, 0x00001401, 0x67325452, 0x00920001, + 0x00001401, 0x67335452, 0x00c70001, 0x00001401, 0x72305452, 0x012c0001, 0x00001401, +]; + +/// Hashes of the hardware data, power configuration and region C, without the runtime words. +pub(super) const EXPECTED: [u64; 3] = [0xd781973f99831e35, 0xc36d7dbaa597aac4, 0x91a4c67017e30a24]; diff --git a/drivers/gpu/drm/asahi/hw/t8140.rs b/drivers/gpu/drm/asahi/hw/t8140.rs index 3bbc698889997..a4e7bc5d6e2e3 100644 --- a/drivers/gpu/drm/asahi/hw/t8140.rs +++ b/drivers/gpu/drm/asahi/hw/t8140.rs @@ -52,6 +52,7 @@ pub(crate) const CONFIG: Config = Config { uat_quirks: crate::mmu::UatQuirks::DEFAULT, region_c: &crate::g17::fw::initdata::REGION_C_26A428, chip: None, + captured: None, adopt_root_entries: true, fabric_bridge: true, firmware: crate::g17::fw::variant::Firmware::Fixed(&crate::g17::fw::variant::V26A428), From 87c26f61de93952278e1f1cfa382fe1c64cf79db Mon Sep 17 00:00:00 2001 From: AxxzyWasTaken Date: Tue, 3 Nov 2026 23:00:28 +0000 Subject: [PATCH 16/16] tools/asahi: model the configuration of a firmware build firmware_variant() now also returns the SoC configuration a build was measured with, so the selection test no longer compiled. Give its model of Build the field, and check that the configuration follows the identified image, and that a build without one and a fixed variant keep the probed configuration. --- tools/asahi/g17-firmware-selection-tests.py | 26 ++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py index 771c9c19c6e1e..7bd936c5ce125 100644 --- a/tools/asahi/g17-firmware-selection-tests.py +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -34,6 +34,7 @@ def method(source, signature): image: u32, checked: std::cell::RefCell>, } +pub struct Config(pub u32); mod fw { pub struct Variant(pub u32); pub mod variant { @@ -43,6 +44,7 @@ def method(source, signature): pub name: &'static str, pub image: u32, pub variant: &'static Variant, + pub config: Option<&'static crate::Config>, } pub enum Firmware { Fixed(&'static Variant), Identified(&'static [Build]) } } @@ -60,9 +62,11 @@ def method(source, signature): } static FIRST: fw::Variant = fw::Variant(1); static SECOND: fw::Variant = fw::Variant(2); +static SECOND_CONFIG: Config = Config(2); static BUILDS: [fw::variant::Build; 2] = [ - fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST }, - fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND }, + fw::variant::Build { os_version: [26,6,2], name: "first", image: 1, variant: &FIRST, config: None }, + fw::variant::Build { os_version: [27,0,0], name: "second", image: 2, variant: &SECOND, + config: Some(&SECOND_CONFIG) }, ]; fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } @@ -71,18 +75,28 @@ def method(source, signature): use fw::variant::Firmware; for version in [None, Some([26,6,2]), Some([26,7,0]), Some([99,0,0])] { let dev = device(version, 1); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap(); + assert_eq!(variant.0, 1); + assert!(config.is_none(), "a build without a configuration keeps the probed one"); } + let dev = device(None, 2); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap(); + assert_eq!(variant.0, 2); + assert_eq!(config.unwrap().0, 2, "the configuration follows the identified image"); let dev = device(Some([27,0,0]), 1); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap(); + assert_eq!(variant.0, 1); + assert!(config.is_none(), "the OS hint does not pick a configuration"); assert_eq!(*dev.checked.borrow(), [2,1], "the OS version only orders the candidates"); let dev = device(Some([26,6,2]), 9); assert!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).is_err()); assert_eq!(*dev.checked.borrow(), [1,2]); let dev = device(None, 9); - assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap().0, 2); + let (variant, config) = Gpu::firmware_variant(&dev, &Firmware::Fixed(&SECOND)).unwrap(); + assert_eq!(variant.0, 2); + assert!(config.is_none()); assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); - println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged"); + println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged, configuration follows the image"); } '''