diff --git a/Documentation/devicetree/bindings/gpu/apple,agx.yaml b/Documentation/devicetree/bindings/gpu/apple,agx.yaml index c0a32c3badd07..0cd4dc1d6fd78 100644 --- a/Documentation/devicetree/bindings/gpu/apple,agx.yaml +++ b/Documentation/devicetree/bindings/gpu/apple,agx.yaml @@ -83,9 +83,9 @@ properties: minItems: 3 maxItems: 3 description: - macOS version (major, minor, patch) whose boot firmware loaded the GPU - firmware, which identifies the firmware build. Filled in by the boot - loader. + Reported macOS version (major, minor, patch) whose boot firmware loaded + the GPU firmware. Filled in by the boot loader. This is a selection + hint; the T6040 driver requires the exact loaded image identity. apple,cs-opp: $ref: /schemas/types.yaml#/definitions/phandle @@ -99,6 +99,75 @@ properties: Operating-point table of the GPU's AFR clock domain, one voltage cell per state. + apple,t6040-handoff-version: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 2 + description: + Written last by the boot loader after it has filled the T6040 operating + points, memory regions and per-chip calibration. Absent in the source DT. + + apple,core-mask: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Enabled cores in the two ten-core T6040 clusters, low cluster first + + apple,max-power-mw: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Per-chip maximum GPU power in milliwatts, calculated by the loader + + apple,perf-base-pstate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Logical base state of the frequency-relative performance table. T6040 + supports the first active state, also used when its ADT omits the property. + + apple,perf-map-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 A selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-map-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + All 16 B selector register fields, indexing raw ADT operating points. + Eleven populated entries, including off, followed by five zeros. + + apple,perf-relative-a: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Per-chip relative power of each populated A selector, in percent, + followed by zeros for the five unused selectors. + + apple,perf-relative-b: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Relative frequency of the populated A ladder above the base state, + in percent, followed by zeros for the five unused selectors. + + apple,core-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU core leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,sram-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + GPU SRAM leakage, one coefficient per cluster. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,cs-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + CS leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + + apple,afr-leak-coef: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + AFR leakage. Per-chip efuse values decoded by the boot loader, + stored as IEEE-754 binary32 bit patterns in big-endian cells. + apple,firmware-abi: $ref: /schemas/types.yaml#/definitions/uint32-array minItems: 3 @@ -190,6 +259,38 @@ allOf: const: apple,agx-t6040 then: properties: + apple,core-mask: + minimum: 1 + maximum: 1048575 + apple,max-power-mw: + minimum: 1 + maximum: 200000 + apple,perf-base-pstate: + const: 1 + apple,perf-map-a: + minItems: 16 + maxItems: 16 + apple,perf-map-b: + minItems: 16 + maxItems: 16 + apple,perf-relative-a: + minItems: 16 + maxItems: 16 + apple,perf-relative-b: + minItems: 16 + maxItems: 16 + apple,core-leak-coef: + minItems: 2 + maxItems: 2 + apple,sram-leak-coef: + minItems: 2 + maxItems: 2 + apple,cs-leak-coef: + minItems: 1 + maxItems: 1 + apple,afr-leak-coef: + minItems: 1 + maxItems: 1 reg: items: - description: GFX coprocessor control registers diff --git a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml index 81fd3e37452a6..8f935a2d2b320 100644 --- a/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml +++ b/Documentation/devicetree/bindings/net/wireless/brcm,bcm4329-fmac.yaml @@ -46,6 +46,7 @@ properties: - cypress,cyw4373-fmac - cypress,cyw43012-fmac - infineon,cyw43439-fmac + - pci14e4,4434 # BCM4388 - const: brcm,bcm4329-fmac - enum: - brcm,bcm4329-fmac diff --git a/arch/arm64/boot/dts/apple/t6040-j616s.dts b/arch/arm64/boot/dts/apple/t6040-j616s.dts index a40a3fa1e1dc0..5707ec4a632d2 100644 --- a/arch/arm64/boot/dts/apple/t6040-j616s.dts +++ b/arch/arm64/boot/dts/apple/t6040-j616s.dts @@ -79,8 +79,9 @@ * Text segment of the loaded GPU firmware (ADT gfx-asc * segment-ranges[0]); the driver identifies the build from it. */ - gpu_fw_text: gpu-fw-text@10000be4000 { - reg = <0x100 0x00be4000 0x0 0x60000>; + gpu_fw_text: gpu-fw-text { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -88,8 +89,9 @@ * Data segment of the loaded GPU firmware (ADT sgx gfx-data-*); it * places its crash log here. */ - gpu_fw_data: gpu-fw-data@10001d44000 { - reg = <0x100 0x01d44000 0x0 0x138000>; + gpu_fw_data: gpu-fw-data { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; @@ -98,23 +100,27 @@ * gfx-shared-l2-region-*, gfx-handoff-*). They stay no-map until * the boot loader adds them to /memory. */ - uat_ttbs: uat-ttbs@10bfffb8000 { - reg = <0x10b 0xfffb8000 0x0 0x4000>; + uat_ttbs: uat-ttbs { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_pagetables: uat-pagetables@10bfff78000 { - reg = <0x10b 0xfff78000 0x0 0x40000>; + uat_pagetables: uat-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_l2_pagetables: uat-l2-pagetables@10bfff74000 { - reg = <0x10b 0xfff74000 0x0 0x4000>; + uat_l2_pagetables: uat-l2-pagetables { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; - uat_handoff: uat-handoff@10bfff70000 { - reg = <0x10b 0xfff70000 0x0 0x4000>; + uat_handoff: uat-handoff { + reg = <0 0 0 0>; + status = "disabled"; no-map; }; }; @@ -139,76 +145,76 @@ /* * GPU performance states, one opp-microvolt cell per cluster (ADT * /arm-io/sgx perf-states and perf-states1). The operating points are - * binned per device: these are this unit's values until the boot - * loader fills them in. Entries keep the order of the GPU performance - * states, which is not sorted by frequency. + * binned per device: the loader fills these placeholders. + * The driver requires a completed calibration handoff. Entries keep the + * order of the GPU performance states, which is not sorted by frequency. */ gpu_opp: opp-table-gpu { compatible = "operating-points-v2"; opp00 { opp-hz = /bits/ 64 <0>; - opp-microvolt = <125000 125000>; + opp-microvolt = <0 0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <605000 585000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <655000 640000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <690000 690000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp04 { - opp-hz = /bits/ 64 <924000000>; - opp-microvolt = <715000 725000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp05 { - opp-hz = /bits/ 64 <952000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp06 { - opp-hz = /bits/ 64 <1056000000>; - opp-microvolt = <755000 770000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp07 { - opp-hz = /bits/ 64 <1062000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp08 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <795000 810000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp09 { - opp-hz = /bits/ 64 <1182000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp10 { - opp-hz = /bits/ 64 <1312000000>; - opp-microvolt = <845000 860000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp11 { - opp-hz = /bits/ 64 <1242000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp12 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <900000 915000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp13 { - opp-hz = /bits/ 64 <1326000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp14 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <945000 960000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; opp15 { - opp-hz = /bits/ 64 <1578000000>; - opp-microvolt = <1030000 1045000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0 0>; }; }; @@ -217,36 +223,36 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; }; @@ -255,36 +261,36 @@ compatible = "operating-points-v2"; opp00 { - opp-hz = /bits/ 64 <24000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp01 { - opp-hz = /bits/ 64 <338000000>; - opp-microvolt = <555000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp02 { - opp-hz = /bits/ 64 <618000000>; - opp-microvolt = <620000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp03 { - opp-hz = /bits/ 64 <796000000>; - opp-microvolt = <660000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp04 { - opp-hz = /bits/ 64 <992000000>; - opp-microvolt = <715000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp05 { - opp-hz = /bits/ 64 <1188000000>; - opp-microvolt = <780000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp06 { - opp-hz = /bits/ 64 <1380000000>; - opp-microvolt = <870000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; opp07 { - opp-hz = /bits/ 64 <1470000000>; - opp-microvolt = <910000>; + opp-hz = /bits/ 64 <0>; + opp-microvolt = <0>; }; }; @@ -569,7 +575,6 @@ 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>; @@ -592,7 +597,7 @@ }; wifi@0,0 { - compatible = "brcm,bcm4329-fmac"; + compatible = "pci14e4,4434", "brcm,bcm4329-fmac"; reg = <0 0 0 0 0>; brcm,ccode-map-trivial; brcm,board-type = "apple,hostomel"; @@ -822,12 +827,23 @@ }; /* - * The GPU stays disabled until a boot loader fills in this unit's operating - * points, leakage and regions from the ADT; the values here are one unit's. - * Enabling it also takes gfx_mbox, keeps ps_afr and ps_gfx always on and - * claims pmp_gfx always-on, so that the PMP sees the GPU on exactly while it - * is powered. + * The loaded GPU firmware keeps running across system sleep. Like the Neo's + * GFX domain, these domains must not be switched off while that image lives. + * The PMP report vote is still owned by the GPU consumer. */ +&ps_afr { + apple,always-on; +}; + +&ps_gfx { + apple,always-on; +}; + +/* The loader fills the operating points, calibration and all six memory regions. */ +&gfx_mbox { + status = "okay"; +}; + &gpu { memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>, <&gpu_fw_data>, <&uat_l2_pagetables>, <&gpu_fw_text>; @@ -836,7 +852,7 @@ operating-points-v2 = <&gpu_opp>; apple,cs-opp = <&gpu_cs_opp>; apple,afr-opp = <&gpu_afr_opp>; - status = "disabled"; + status = "okay"; }; /* diff --git a/arch/arm64/boot/dts/apple/t6040.dtsi b/arch/arm64/boot/dts/apple/t6040.dtsi index 213723f3c34b2..ad87eaf38ac31 100644 --- a/arch/arm64/boot/dts/apple/t6040.dtsi +++ b/arch/arm64/boot/dts/apple/t6040.dtsi @@ -616,7 +616,7 @@ <0x2 0x88000000 0x0 0x3758000>; reg-names = "asc", "sgx"; mboxes = <&gfx_mbox>; - power-domains = <&ps_gfx>; + power-domains = <&pmp_gfx>; status = "disabled"; }; @@ -909,8 +909,8 @@ * the GPU as off and holds it at its lowest state, but claiming * it while nothing powers the GPU crashes the PMP firmware * (SError) right after it starts. It sits on the GPU's own - * power state, and a board that powers the GPU (ps_gfx stays on - * while the GPU firmware runs) claims it always-on. + * power state. The GPU consumer takes this domain, so the request + * follows the GFX domain's power lifetime. */ pmp_gfx: report@d { compatible = "apple,t6041-pmp-v2-report-entry", diff --git a/arch/arm64/configs/asahi.config b/arch/arm64/configs/asahi.config index 783e41230238c..b9d021d63bf5d 100644 --- a/arch/arm64/configs/asahi.config +++ b/arch/arm64/configs/asahi.config @@ -11,6 +11,8 @@ CONFIG_ARM64_16K_PAGES=y CONFIG_ARM64_MEMORY_MODEL_CONTROL=y CONFIG_ARM_APPLE_CPUIDLE=y CONFIG_ARM_APPLE_SOC_CPUFREQ=m +CONFIG_ENERGY_MODEL=y +CONFIG_THERMAL_GOV_POWER_ALLOCATOR=y CONFIG_THERMAL_DEFAULT_GOV_POWER_ALLOCATOR=y CONFIG_BT_BRCMEXT=y CONFIG_BT_HCIBCM4377=m diff --git a/drivers/gpu/drm/asahi/float.rs b/drivers/gpu/drm/asahi/float.rs index 46c05699d7722..224e92c6d97e2 100644 --- a/drivers/gpu/drm/asahi/float.rs +++ b/drivers/gpu/drm/asahi/float.rs @@ -44,6 +44,11 @@ struct F32U { } impl F32 { + /// Return the IEEE-754 binary32 representation. + pub(crate) const fn to_bits(self) -> u32 { + self.0 + } + /// Convert a raw 32-bit representation into an F32 pub(crate) const fn from_bits(u: u32) -> F32 { F32(u) diff --git a/drivers/gpu/drm/asahi/g17/calibration.rs b/drivers/gpu/drm/asahi/g17/calibration.rs new file mode 100644 index 0000000000000..d973349d35d40 --- /dev/null +++ b/drivers/gpu/drm/asahi/g17/calibration.rs @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT + +//! Per-chip T6040 power data supplied by the boot loader. + +use super::fw::initdata::{Opp, PerfConfig, PERF_STATES}; +use crate::driver::AsahiDrmDevice; +use kernel::{c_str, of, prelude::*, str::CStr}; + +/// Validated calibration of the running chip. There is no built-in fallback. +pub(super) struct Calibration { + pub(super) perf: PerfConfig, + pub(super) leakage: [u32; 56], + pub(super) max_power_mw: u32, + core_mask: u32, + base: usize, +} + +fn words(node: &of::Node, name: &CStr) -> Result<[u32; N]> { + let values: KVec = node.get_property(name)?; + values.as_slice().try_into().map_err(|_| EINVAL) +} + +// Raw ADT OPP slots and SGX register slots number 16. The supported firmware consumes +// the 11 populated selectors; the remaining handoff entries must all be zero. +const RAW_STATES: usize = 16; + +fn active(values: [u32; RAW_STATES]) -> Result<[u32; PERF_STATES]> { + if values[PERF_STATES..].iter().any(|&v| v != 0) { + return Err(EINVAL); + } + values[..PERF_STATES].try_into().map_err(|_| EINVAL) +} + +fn map(values: [u32; RAW_STATES]) -> Result<[u8; PERF_STATES]> { + let values = active(values)?; + if values[0] != 0 + || values[PERF_STATES - 1] != RAW_STATES as u32 - 1 + || values.iter().any(|&v| v >= RAW_STATES as u32) + || values.windows(2).any(|p| p[0] >= p[1]) + { + return Err(EINVAL); + } + Ok(values.map(|v| v as u8)) +} + +fn relative(values: &[u32; PERF_STATES]) -> bool { + values[0] == 0 + && values[PERF_STATES - 1] == 100 + && values.windows(2).all(|p| p[0] <= p[1] && p[1] <= 100) +} + +impl Calibration { + pub(super) fn read(dev: &AsahiDrmDevice) -> Result { + let node = dev.as_ref().as_ref().of_node().ok_or(EIO)?; + if node.get_property::(c_str!("apple,t6040-handoff-version"))? != 2 { + return Err(EINVAL); + } + let core_mask = node.get_property(c_str!("apple,core-mask"))?; + let max_power_mw = node.get_property(c_str!("apple,max-power-mw"))?; + let base = node.get_property::(c_str!("apple,perf-base-pstate"))? as usize; + if core_mask == 0 + || core_mask & !0xfffff != 0 + || !(1..=200_000).contains(&max_power_mw) + || base != 1 + { + return Err(EINVAL); + } + let perf = PerfConfig { + states_a: map(words(&node, c_str!("apple,perf-map-a"))?)?, + states_b: map(words(&node, c_str!("apple,perf-map-b"))?)?, + relative_a: active(words(&node, c_str!("apple,perf-relative-a"))?)?, + relative_b: active(words(&node, c_str!("apple,perf-relative-b"))?)?, + sram_min_mv: 810, + }; + if !relative(&perf.relative_a) + || !relative(&perf.relative_b) + || perf.relative_b[..=base].iter().any(|&v| v != 0) + { + return Err(EINVAL); + } + let core = words::<2>(&node, c_str!("apple,core-leak-coef"))?; + let sram = words::<2>(&node, c_str!("apple,sram-leak-coef"))?; + let cs = words::<1>(&node, c_str!("apple,cs-leak-coef"))?; + let afr = words::<1>(&node, c_str!("apple,afr-leak-coef"))?; + // Positive finite binary32 values. Integer comparisons preserve their ordering. + if Iterator::chain( + Iterator::chain(Iterator::chain(core.iter(), &sram), &cs), + &afr, + ) + .any(|&v| !(0x3f80_0000..0x4780_0000).contains(&v)) + { + return Err(EINVAL); + } + let mut leakage = [0; 56]; + leakage[..2].copy_from_slice(&core); + leakage[32..34].copy_from_slice(&sram); + leakage[52] = cs[0]; + leakage[54] = afr[0]; + Ok(Self { + perf, + leakage, + max_power_mw, + core_mask, + base, + }) + } + + /// Check the loader's mask against the GPU before starting its firmware. + pub(super) fn check_cores(&self, masks: &[u32]) -> Result { + if masks != [self.core_mask & 0x3ff, self.core_mask >> 10] { + return Err(EINVAL); + } + Ok(()) + } + + /// The performance maps index OPPs in ADT order, rather than sorted frequency order. + pub(super) fn check_opps(&self, opps: &[Opp]) -> Result { + if opps.len() != 16 || opps[0].freq_hz != 0 || opps.iter().any(|opp| opp.cells != 2) { + return Err(EINVAL); + } + for map in [&self.perf.states_a, &self.perf.states_b] { + let mut previous = 0; + for &index in &map[1..] { + let opp = &opps[usize::from(index)]; + let mhz = opp.freq_hz / 1_000_000; + if mhz <= previous + || opp.volt_uv[..2] + .iter() + .any(|v| !(400_000..=1_300_000).contains(v)) + { + return Err(EINVAL); + } + previous = mhz; + } + } + let base = opps[usize::from(self.perf.states_a[self.base])].freq_hz / 1_000_000; + let top = opps[usize::from(self.perf.states_a[PERF_STATES - 1])].freq_hz / 1_000_000; + for (i, &index) in self.perf.states_a.iter().enumerate() { + let freq = opps[usize::from(index)].freq_hz / 1_000_000; + let expected = if i < self.base { + 0 + } else { + 100 * (freq - base) / (top - base) + }; + if u64::from(self.perf.relative_b[i]) != expected { + return Err(EINVAL); + } + } + Ok(()) + } +} + +#[kunit_tests(asahi_t6040_calibration)] +mod tests { + use super::*; + + fn chip() -> Calibration { + Calibration { + perf: PerfConfig { + states_a: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + states_b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + relative_a: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], + relative_b: [0, 0, 11, 22, 33, 44, 55, 66, 77, 88, 100], + sram_min_mv: 810, + }, + leakage: [0; 56], + max_power_mw: 70_000, + core_mask: 0xfffff, + base: 1, + } + } + + #[test] + fn operating_points_and_mask_must_match_the_handoff() { + let chip = chip(); + let mut opps = [Opp { + freq_hz: 0, + volt_uv: [800_000; super::super::fw::initdata::OPP_CELLS], + cells: 2, + }; 16]; + for (i, opp) in opps.iter_mut().enumerate() { + opp.freq_hz = i as u64 * 100_000_000; + } + assert!(chip.check_opps(&opps).is_ok()); + opps[0].freq_hz = 100_000_000; + assert!(chip.check_opps(&opps).is_err()); + opps[0].freq_hz = 0; + assert!(chip.check_cores(&[0x3ff, 0x3ff]).is_ok()); + assert!(chip.check_cores(&[0x3ff, 0x1ff]).is_err()); + assert!(chip.check_cores(&[0xfffff]).is_err()); + opps[5].volt_uv[1] = 0; + assert!(chip.check_opps(&opps).is_err()); + opps[5].volt_uv[1] = 800_000; + opps[5].freq_hz = opps[4].freq_hz; + assert!(chip.check_opps(&opps).is_err()); + opps[5].freq_hz = 501_000_000; + assert!(chip.check_opps(&opps).is_ok()); + opps[5].freq_hz = 511_000_000; + assert!(chip.check_opps(&opps).is_err()); + } + + #[test] + fn maps_refuse_off_repeats_and_out_of_range() { + let valid = [0, 1, 2, 3, 4, 6, 8, 10, 12, 14, 15, 0, 0, 0, 0, 0]; + assert!(map(valid).is_ok()); + for (index, value) in [ + (0, 1), + (1, 0), + (5, 4), + (10, 16), + (10, u32::MAX), + (10, 14), + (11, 1), + (15, 15), + ] { + let mut bad = valid; + bad[index] = value; + assert!(map(bad).is_err()); + } + } + + #[test] + fn relative_tables_are_bounded_and_monotonic() { + let mut values = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; + assert!(relative(&values)); + values[5] = 101; + assert!(!relative(&values)); + values[5] = 39; + assert!(!relative(&values)); + } +} diff --git a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs index c237896a49fb1..f4c3ee5db22e2 100644 --- a/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/fw/initdata/v25g83.rs @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-only OR MIT +// SPDX-License-Identifier: GPL-2.0-only //! Initialization data of the 25G83 firmware build (macOS 26.6, `mBoot-18000.161.10`). //! @@ -17,9 +17,9 @@ //! 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, RegisterMapEntry, - Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, COMPLETION_RING_INDEX, PERF_STATES, - REGISTER_MAP_SLOTS, + fill_words, perf_states, CompletionRingDescriptor, HwDataArgs, Opp, PerfGroup, + RegisterMapEntry, Words, COMPLETION_RING_ENTRIES, COMPLETION_RING_ENTRY_SIZE, + COMPLETION_RING_INDEX, PERF_STATES, REGISTER_MAP_SLOTS, }; use kernel::prelude::*; @@ -381,26 +381,10 @@ impl HwData25G83 { /// Power configuration control period, in 24 MHz clocks (8 ms). const PERIOD_CLOCKS: u32 = 200_000; -/// Highest GPU power, in mW. -pub(crate) const MAX_POWER_MW: u32 = 69_116; -/// [`MAX_POWER_MW`] as an IEEE-754 single. -const MAX_POWER_MW_F32: u32 = 0x4786_fe00; /// Temperature sensors read by the die and MTR temperature limiters. const TEMP_SENSOR_MASK: u32 = 0x2a22_2aa2; -/// Leakage coefficients, IEEE-754 singles: three arrays of sixteen clusters, then four arrays -/// of two dies. Not found in the ADT; constant for the SoC. -const LEAKAGE: [u32; 56] = { - let mut words = [0; 56]; - words[0] = 0x44f0_3000; // 1921.5 - words[1] = 0x44f3_0000; // 1944.0 - words[32] = 0x4373_0000; // 243.0 - words[33] = 0x4373_0000; // 243.0 - words[52] = 0x437b_0000; // 251.0 - words[54] = 0x43bd_c000; // 379.5 - words -}; -/// Offset of [`LEAKAGE`] in the power configuration. +/// Offset of the per-chip leakage coefficients in the power configuration. 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`). @@ -433,12 +417,10 @@ const POWER_CONFIG_WORDS: Words = &[ (0x9c4, 0x3f7f2e9f, 1), (0x9cc, 0x3b516154, 1), (0x9d4, 0x3ca59586, 1), - (0x9e0, MAX_POWER_MW_F32, 1), (0x9e4, 0x40a90fdb, 1), (0x9ec, 0xbc6d0cfe, 1), (0x9f0, 0x64, 1), (0x9f4, 0x3e8, 2), - (0xa04, MAX_POWER_MW, 1), (0xa38, 0x3e8, 1), (0xa70, 0x8, 1), (0xa7c, 0x3f000000, 1), @@ -448,7 +430,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0xa9c, 0x3ecf2e49, 1), (0xaa8, 0x64, 1), (0xaac, 0x3e8, 1), - (0xabc, MAX_POWER_MW, 1), (0xac8, 0x10, 1), (0xad0, 0x5dc00, 1), (0xb28, 0x50, 1), @@ -508,7 +489,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x27b8, 0xc80000, 1), (0x2870, 0x3e8, 1), (0x28c0, 0x1, 1), - (0x28c8, MAX_POWER_MW, 3), (0x28e0, 0x3f800000, 1), (0x28e4, 0x4, 1), (0x28e8, 0x8, 1), @@ -521,8 +501,6 @@ const POWER_CONFIG_WORDS: Words = &[ (0x29e8, 0x3f4f2e49, 1), (0x29f4, 0x28, 1), (0x29f8, 0x3e8, 2), - (0x2a04, MAX_POWER_MW_F32, 1), - (0x2a08, MAX_POWER_MW, 1), (0x2a14, 0xf8, 1), (0x2a1c, 0x5ad200, 1), (0x2a3c, 0x3e8, 1), @@ -591,27 +569,34 @@ unsafe impl Zeroable for PowerConfig25G83 {} static_assert!(LEAKAGE_OFFSET + size_of::<[u32; 56]>() <= size_of::()); impl PowerConfig25G83 { - /// Pointer and random words, which [`Self::hash`] leaves out. - const RUNTIME: [(usize, usize); 3] = - [(CONFIG_STATUS_POINTER, 8), (AON_POINTER, 8), (ENTROPY, 4)]; - /// Initializes the power configuration. - pub(crate) fn init(&mut self, config_va: u64, aon_va: u64, entropy: u32) { + pub(crate) fn init( + &mut self, + config_va: u64, + aon_va: u64, + entropy: u32, + max_power_mw: u32, + leakage: &[u32; 56], + ) { + self.0.fill(0); fill_words(&mut self.0, POWER_CONFIG_WORDS); - for (index, word) in LEAKAGE.iter().enumerate() { + for (index, word) in leakage.iter().enumerate() { let at = LEAKAGE_OFFSET + 4 * index; self.0[at..at + 4].copy_from_slice(&word.to_le_bytes()); } + // All copies of the power ceiling must describe the same chip. + for offset in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + self.0[offset..offset + 4].copy_from_slice(&max_power_mw.to_le_bytes()); + } + let power = crate::float::F32::from(max_power_mw).to_bits(); + for offset in [0x9e0, 0x2a04] { + self.0[offset..offset + 4].copy_from_slice(&power.to_le_bytes()); + } 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()); } - - /// Hash of the contents without the pointers and the random word. - pub(crate) fn hash(&self) -> u64 { - hash_without(&self.0, &Self::RUNTIME) - } } /// The AON temperature-source rows the power configuration names: the GPU cluster 0, GPU @@ -632,50 +617,3 @@ pub(crate) const AON_TABLE: [u32; 9] = [ /// (`+0xc8`, 101.00 °C) and release (`+0xcc`, 96.00 °C) temperatures in 0.01 °C; left at zero /// the limit is 0 °C and the GPU stays at its lowest state whenever the PMP runs. pub(crate) const POWER_STATE_WORDS: Words = &[(0xc8, 10_100, 1), (0xcc, 9_600, 1)]; - -impl HwData25G83 { - /// What [`Self::hash`] leaves out: the runtime words (completion descriptors, QoS pointer) - /// and what comes from the device: the performance tables built from its operating points, - /// which are binned per device, and its enabled core count. - const UNHASHED: [(usize, usize); 10] = [ - (COMPLETION_DESCRIPTORS, 0x80), - (QOS_POINTER, 8), - (core::mem::offset_of!(Self, unk_f88), size_of::()), - (core::mem::offset_of!(Self, unk_fc0), size_of::()), - (core::mem::offset_of!(Self, perf_a), size_of::()), - (core::mem::offset_of!(Self, perf_b_freq_mhz), size_of::<[u32; PERF_STATES]>()), - (core::mem::offset_of!(Self, cs_perf), size_of::()), - (core::mem::offset_of!(Self, afr_perf), size_of::()), - (core::mem::offset_of!(Self, perf_b), size_of::()), - (core::mem::offset_of!(Self, unk_2570), size_of::()), - ]; - - /// Hash of the constant contents (everything but [`Self::UNHASHED`]). - pub(crate) fn hash(&self) -> u64 { - // SAFETY: `HwData25G83` consists of integers only and has no padding. - let bytes = unsafe { - core::slice::from_raw_parts((self as *const Self).cast::(), size_of::()) - }; - hash_without(bytes, &Self::UNHASHED) - } -} - -/// 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 -} - -/// Hashes ([`hash_without`]) of the hardware data, power configuration and region C that the -/// Gravity Linux G16 encoders produce for the J616s, which this build's objects must equal. -pub(crate) mod expected { - pub(crate) const HW_DATA: u64 = 0xfd3b_a26f_570f_57cd; - pub(crate) const POWER_CONFIG: u64 = 0xb669_5b84_2ad2_36fe; - pub(crate) const REGION_C: u64 = 0xd2db_fa6b_8976_47b4; -} diff --git a/drivers/gpu/drm/asahi/g17/fw/variant.rs b/drivers/gpu/drm/asahi/g17/fw/variant.rs index d2d6d2404dfc8..78951f6d10a7e 100644 --- a/drivers/gpu/drm/asahi/g17/fw/variant.rs +++ b/drivers/gpu/drm/asahi/g17/fw/variant.rs @@ -64,10 +64,9 @@ pub(crate) struct Variant { pub(crate) programs: super::render::RegisterPrograms, } -/// A firmware build a SoC supports, selected by the macOS release it ships with and proven by -/// its loaded TEXT. +/// A firmware build a SoC supports, identified by its loaded TEXT. pub(crate) struct Build { - /// macOS version (major, minor, patch). + /// macOS version (major, minor, patch), used to order image candidates. pub(crate) os_version: [u32; 3], /// Name, for the log. pub(crate) name: &'static str, @@ -81,9 +80,8 @@ pub(crate) struct Build { pub(crate) enum Firmware { /// The SoC runs a single build, which is not identified (A18 Pro). Fixed(&'static Variant), - /// One of these builds, selected by the macOS version that the boot loader reports in - /// `apple,firmware-version` (the release whose boot firmware loaded the GPU firmware) and - /// then proven by the loaded TEXT. Any other version or image is refused. + /// One of these exact loaded images. The loader's `apple,firmware-version` is only + /// a candidate-ordering hint because multiple OS releases can share an iBoot string. Identified(&'static [Build]), } diff --git a/drivers/gpu/drm/asahi/g17/initdata.rs b/drivers/gpu/drm/asahi/g17/initdata.rs index 10af261aeb3ac..83a7bbc29dd05 100644 --- a/drivers/gpu/drm/asahi/g17/initdata.rs +++ b/drivers/gpu/drm/asahi/g17/initdata.rs @@ -1223,7 +1223,11 @@ impl InitData { /// The GPU's operating points: `count` of them, each with one voltage cell for the whole /// GPU or one per cluster. - fn opps(dev: &AsahiDrmDevice, count: usize, clusters: u32) -> Result> { + pub(super) fn opps( + dev: &AsahiDrmDevice, + count: usize, + clusters: u32, + ) -> Result> { let opps = Self::opp_table(dev, c_str!("operating-points-v2"))?; if opps.len() != count { return Err(EINVAL); diff --git a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs index 85aa6468fa311..670f4b96dcb76 100644 --- a/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs +++ b/drivers/gpu/drm/asahi/g17/initdata/v25g83.rs @@ -8,7 +8,7 @@ //! ([`abi::v25g83`]); free lists, pools, descriptor tables, rings and regions are the A18 Pro's. use super::{abi, low, BuildEnv, BundleObjects, CpuMap, DescriptorTables, InitData, KernelObject}; -use crate::g17::{Config, Role}; +use crate::g17::{calibration::Calibration, Config, Role}; use crate::mmu; use abi::v25g83 as v; use core::sync::atomic::{fence, Ordering}; @@ -74,7 +74,7 @@ impl InitData { }) } - /// Writes the 25G83 firmware objects, checks them against the expected contents, then + /// Writes the 25G83 firmware objects using the chip calibration, then /// stages the opening control-ring state. fn initialize_25g83( &mut self, @@ -101,12 +101,14 @@ 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 hw_args = abi::HwDataArgs { chip_id: cfg.chip_id, windows: cfg.windows, reserved_slots: cfg.reserved_slots, opps: &opps, - perf: cfg.perf, + perf: &calibration.perf, completion_rings: bundle.completion_rings, qos_va: self.object(bundle.qos)?.gpu_va(), }; @@ -117,41 +119,28 @@ impl InitData { }; // 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(&hw_args, &hw_args_25g83)?; - hashes[0] = hw.hash(); Ok(()) })?; self.object_mut(self.bundle)? .initialize::(v::POWER_CONFIG, |power| { - power.init(config_va, aon_va, entropy); - hashes[1] = power.hash(); + power.init( + config_va, + aon_va, + entropy, + calibration.max_power_mw, + &calibration.leakage, + ); Ok(()) })?; self.object_mut(self.region_c)? .initialize::(0, |region| { region.init(cfg.region_c); + region.max_power_mw = calibration.max_power_mw; Ok(()) })?; - hashes[2] = v::hash_without(self.object_mut(self.region_c)?.bytes_mut(), &[]); - // The constant contents must equal what the Gravity Linux encoders produce on the proven - // setup; the operating points and the core count come from the device and are not hashed. - let expected = [ - v::expected::HW_DATA, - v::expected::POWER_CONFIG, - v::expected::REGION_C, - ]; - if hashes != expected { - dev_err!( - env.dev.as_ref(), - "25G83 initialization data differs: {:#x?}, expected {:#x?}\n", - hashes, - expected - ); - return Err(EINVAL); - } for (index, word) in v::AON_TABLE.iter().enumerate() { self.object_mut(objects.aon)?.write(index * 4, *word)?; } @@ -167,7 +156,9 @@ impl InitData { v::private::STATUS_B + core::mem::offset_of!(abi::PrimaryStatusB, fwctl_state_va); let cluster = self.object(self.cluster)?; cluster.dword(fwctl)?.store(fwctl_va, Ordering::Relaxed); - cluster.dword(fwctl + 8)?.store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); + cluster + .dword(fwctl + 8)? + .store(fwctl_va + abi::FWCTL_RING, Ordering::Relaxed); // One main configuration carrying both roles. let main_offset = abi::bundle::MAIN_CONFIG[Role::Primary as usize]; @@ -218,7 +209,8 @@ impl InitData { )?; for &(offset, value, repeat) in v::POWER_STATE_WORDS { for index in 0..repeat { - self.object_mut(objects.power_state)?.write(offset + 4 * index, value)?; + self.object_mut(objects.power_state)? + .write(offset + 4 * index, value)?; } } diff --git a/drivers/gpu/drm/asahi/g17/mod.rs b/drivers/gpu/drm/asahi/g17/mod.rs index ae78efaad1a95..eab0ca15f21f8 100644 --- a/drivers/gpu/drm/asahi/g17/mod.rs +++ b/drivers/gpu/drm/asahi/g17/mod.rs @@ -18,6 +18,7 @@ //! -> VM exec lock -> GEM reservation. `RoleState::ack` nests inside nothing. mod buffer; +mod calibration; mod channel; mod command; pub(crate) use command::Attachments; @@ -1002,8 +1003,19 @@ impl Gpu { cfg: &'static Config, regs: regs::G17Resources, ) -> Result> { + let calibration = if cfg.chip_id == crate::hw::t6040::CHIP_ID { + Some(calibration::Calibration::read(dev).inspect_err(|_| { + dev_err!(dev.as_ref(), "Missing or invalid T6040 GPU calibration handoff\n"); + })?) + } else { + None + }; let variant = Self::firmware_variant(dev, &cfg.firmware)?; let id = regs.get_g17_id()?; + if let Some(calibration) = calibration { + calibration.check_cores(&id.core_masks)?; + calibration.check_opps(&initdata::InitData::opps(dev, cfg.opps, id.num_clusters)?)?; + } if id.family != cfg.identity.family || id.variant != cfg.identity.variant || id.num_dies != cfg.identity.num_dies @@ -1101,24 +1113,26 @@ impl Gpu { fw::variant::Firmware::Identified(builds) => builds, }; let version = Self::os_firmware_version(dev); - if let Some(build) = builds.iter().find(|build| Some(build.os_version) == version) { - dev_info!(dev.as_ref(), "GPU firmware: {}\n", build.name); - Self::identify_image(dev, build).map_err(|_| ENODEV)?; - return Ok(build.variant); + // iBoot strings can be shared by OS releases. Prefer the reported release's + // candidate, but admit only an exact loaded image, regardless of that hint. + let preferred = builds.iter().filter(|build| Some(build.os_version) == version); + let others = builds.iter().filter(|build| Some(build.os_version) != version); + 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); + } } dev_err!( dev.as_ref(), - "Unsupported GPU firmware: macOS version {:?} (apple,firmware-version)\n", + "Unsupported GPU firmware image (reported macOS version {:?})\n", version ); - for build in builds.iter() { - dev_err!(dev.as_ref(), "Supported GPU firmware: {}\n", build.name); - } Err(ENODEV) } /// Checks that the loaded TEXT (the `fw-text` region) is one of `build`'s images, with the DATA size - /// of the `fw-data` region. The version property only says which build to expect. + /// of the `fw-data` region. The version property is only a selection hint. fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { let node = dev.as_ref().as_ref().of_node().ok_or(EINVAL)?; let text = node.reserved_mem_region_to_resource_byname(c_str!("fw-text"))?; diff --git a/drivers/gpu/drm/asahi/hw/t6040.rs b/drivers/gpu/drm/asahi/hw/t6040.rs index 0007fdf132949..fa3e51efacadc 100644 --- a/drivers/gpu/drm/asahi/hw/t6040.rs +++ b/drivers/gpu/drm/asahi/hw/t6040.rs @@ -7,7 +7,7 @@ use crate::g17::fw::{ self, - initdata::{v25g83, PerfConfig, RegionCConfig, RegisterWindow, PERF_STATES}, + initdata::{PerfConfig, RegionCConfig, RegisterWindow}, }; use crate::g17::{Config, CoreLayout, CrashBuffers, FixedBuffer, Identity}; use crate::hw::{GpuGen, GpuVariant}; @@ -169,33 +169,23 @@ pub(crate) const REGISTER_WINDOWS: [RegisterWindow; 25] = [ window(43, 0x2_88e6_0000, 0x47_4000, 0x58, 0xe6_0000, 2), ]; -/// Performance-table configuration. The device tree lists the sixteen operating points in -/// performance-state order, with one voltage per cluster. +/// Inert defaults. The 25G83 builder requires the loader's per-chip performance maps. 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, 9, 16, 23, 29, 38, 49, 60, 69, 79, 100], - relative_b: [0, 0, 22, 36, 47, 57, 68, 78, 84, 91, 100], + states_a: [0; 11], + states_b: [0; 11], + relative_a: [0; 11], + relative_b: [0; 11], sram_min_mv: 810, }; /// Operating points the performance tables reference. 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; - } -}; - /// Region C. const REGION_C: RegionCConfig = RegionCConfig { unk_54: 0x0fa0, unk_78: [1, 0, 0], - max_power_mw: v25g83::MAX_POWER_MW, + max_power_mw: 0, unk_9c: 0, unk_a0: 0, unk_7c4: 1500, diff --git a/tools/asahi/g17-admission-deadline-tests.py b/tools/asahi/g17-admission-deadline-tests.py index 5002f70997307..b58715f6fcc58 100644 --- a/tools/asahi/g17-admission-deadline-tests.py +++ b/tools/asahi/g17-admission-deadline-tests.py @@ -40,7 +40,7 @@ def fixture(): const QUARANTINE_WAIT_MS:u32=1000; #[derive(Clone,Copy,PartialEq)] enum Plan {Any,Conflict} #[derive(Clone,Copy)] struct Request<'a>(&'a ()); -struct State {owners:[u64;2],quarantined:u64,next_ticket:u64,waiters:usize,conflict:bool,ready:bool} +struct State {owners:[u64;2],quarantined:u64,spared:u64,next_ticket:u64,waiters:usize,conflict:bool,ready:bool} impl State { fn exhausted(&self)->bool {false} fn refusal(&self,_:u64)->Result {Ok(())} @@ -69,7 +69,7 @@ def fixture(): ''' + item(source, 'fn wait_slice_ms(') + '\nimpl Pool {\n' + item(source, 'pub(crate) fn acquire_render(') + r''' } fn new_pool(conflict:bool,quarantined:u64)->Arc { - Arc::new(Pool{state:Lock(Mutex::new(State{owners:[0,1],quarantined,next_ticket:1,waiters:0,conflict,ready:false})),released:CondVar}) + Arc::new(Pool{state:Lock(Mutex::new(State{owners:[0,1],quarantined,spared:0,next_ticket:1,waiters:0,conflict,ready:false})),released:CondVar}) } fn reset(mode:usize){NOW.store(0,Ordering::SeqCst);WAITS.store(0,Ordering::SeqCst);MODE.store(mode,Ordering::SeqCst);} fn main(){ @@ -89,6 +89,9 @@ def fixture(): reset(mode);let pool=new_pool(false,2); assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EIO))); assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"quarantine budget extended"); + reset(mode);let pool=new_pool(false,2);pool.state.lock().spared=2; + assert!(matches!(pool.acquire_render(1,None,2_000_000_000,||Ok(())),Err(EBUSY))); + assert!(NOW.load(Ordering::SeqCst)<=1_010_000_000,"spared quarantine budget extended"); } reset(2);let pool=new_pool(true,0); assert!(matches!(pool.acquire_render(1,Some(Request(&())),2_000_000_000,||Ok(())),Err(ERESTARTSYS))); diff --git a/tools/asahi/g17-firmware-selection-tests.py b/tools/asahi/g17-firmware-selection-tests.py new file mode 100644 index 0000000000000..771c9c19c6e1e --- /dev/null +++ b/tools/asahi/g17-firmware-selection-tests.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Compile the G17 build selector with a modelled image-identity result. + +The shared fwid KUnit tests check the actual SHA-256 normalization. This host +check ensures OS metadata cannot admit a different image or veto a known one. +""" + +from pathlib import Path +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + +def method(source, signature): + start = source.index(signature) + end = source.index("{", start) + 1 + depth = 1 + while depth: + depth += (source[end] == "{") - (source[end] == "}") + end += 1 + return source[start:end] + + +HEADER = r''' +#![allow(dead_code)] +type Result = std::result::Result; +const ENODEV: i32 = -19; +macro_rules! dev_info { ($($t:tt)*) => { () }; } +macro_rules! dev_err { ($($t:tt)*) => { () }; } +struct AsahiDrmDevice { + version: Option<[u32; 3]>, + image: u32, + checked: std::cell::RefCell>, +} +mod fw { + pub struct Variant(pub u32); + pub mod variant { + use super::Variant; + pub struct Build { + pub os_version: [u32; 3], + pub name: &'static str, + pub image: u32, + pub variant: &'static Variant, + } + pub enum Firmware { Fixed(&'static Variant), Identified(&'static [Build]) } + } +} +struct Gpu; +impl Gpu { + fn os_firmware_version(dev: &AsahiDrmDevice) -> Option<[u32; 3]> { dev.version } + fn identify_image(dev: &AsahiDrmDevice, build: &'static fw::variant::Build) -> Result { + dev.checked.borrow_mut().push(build.image); + if dev.image == build.image { Ok(()) } else { Err(ENODEV) } + } +''' + +TESTS = r''' +} +static FIRST: fw::Variant = fw::Variant(1); +static SECOND: fw::Variant = fw::Variant(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 }, +]; +fn device(version: Option<[u32; 3]>, image: u32) -> AsahiDrmDevice { + AsahiDrmDevice { version, image, checked: std::cell::RefCell::new(Vec::new()) } +} +fn main() { + 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 dev = device(Some([27,0,0]), 1); + assert_eq!(Gpu::firmware_variant(&dev, &Firmware::Identified(&BUILDS)).unwrap().0, 1); + 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); + assert!(dev.checked.borrow().is_empty(), "fixed Neo admission is unchanged"); + println!("PASS G17 selection: exact image required, OS hint optional, fixed Neo unchanged"); +} +''' + + +def main(): + source = (DRIVER / "g17/mod.rs").read_text() + selector = method(source, " fn firmware_variant(") + with tempfile.TemporaryDirectory(prefix="asahi-firmware-selection-") as tmp: + root = Path(tmp) + (root / "main.rs").write_text(HEADER + selector + TESTS) + binary = root / "test" + subprocess.run(["rustc", "--edition=2021", "-Dwarnings", str(root / "main.rs"), + "-o", str(binary)], check=True) + subprocess.run([str(binary)], check=True) + + +if __name__ == "__main__": + main() diff --git a/tools/asahi/g17-owner-release-order-tests.py b/tools/asahi/g17-owner-release-order-tests.py index 201f525718950..fd0e2c4a80936 100755 --- a/tools/asahi/g17-owner-release-order-tests.py +++ b/tools/asahi/g17-owner-release-order-tests.py @@ -118,7 +118,7 @@ def fixture(): struct Ticket { ordinal: u64 } struct Active { ticket: Ticket } struct Packet { context: Arc } -struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Option<()>, active: Vec, idle: bool } +struct Pair { executed: u64, switch: Option<(u64, u64)>, replay: Vec<()>, active: Vec, idle: bool } impl Pair { SWITCH /// Host stand-in for a successful publication of `ordinal` (stand-in, not production). @@ -177,7 +177,7 @@ def fixture(): assert!(!table.releasing(&quiet)); // Switch tracking across an owner change of one pair. - let mut pair = Pair { executed: 0, switch: None, replay: None, active: Vec::new(), idle: true }; + let mut pair = Pair { executed: 0, switch: None, replay: Vec::new(), active: Vec::new(), idle: true }; assert_eq!(pair.owner_switch_from(&c), None, "a fresh graph has no previous scheduler"); pair.publish(&Packet { context: d.clone() }, 0); pair.active.clear(); diff --git a/tools/asahi/g17-resource-tests.py b/tools/asahi/g17-resource-tests.py index 35f970799b086..5f87d2ea9e0f5 100644 --- a/tools/asahi/g17-resource-tests.py +++ b/tools/asahi/g17-resource-tests.py @@ -150,14 +150,17 @@ def item(s,start): #[derive(Clone,Copy)] struct Identity {id:Id,published:bool} '''+constants+'\n'+item(s,'pub(crate) struct QueueIds')+'\nimpl QueueIds {\n'+''.join(item(s,'pub(crate) fn '+name+'(')+'\n' for name in ['reserve_initial_render','reserve','publish','can_cancel_unpublished','cancel_unpublished'])+'}\n' test=''' - fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],compute_publication:0,leased:0}} + fn fresh()->QueueIds {QueueIds {entries:[None;QID_COUNT],initial_render:[None;2],publication:0,leased:0,floor:0}} fn main(){ - for render in [0,2,16,32,112,126] { - let mut q=fresh(); + for floor in [0,32] { + for render in [0,2,16,32,80,96,112,126].into_iter().filter(|render|*render<=QID_COUNT-floor) { + let mut q=fresh();q.floor=floor; for i in 0..render {q.reserve(i as u64+1,if i%2==0 {DataMaster::Tiling}else{DataMaster::Fragment},None).unwrap();} let mut compute=0; while q.reserve(1000+compute,DataMaster::Compute,None).is_ok(){compute+=1;} - assert_eq!(compute as usize,QID_COUNT-render.max(COMPUTE_RENDER_RESERVE)); + assert_eq!(compute as usize,QID_COUNT-floor-render.max(COMPUTE_RENDER_RESERVE-floor)); + assert!(q.entries[..floor].iter().all(Option::is_none)); + } } let mut q=fresh();let first=q.reserve(1,DataMaster::Tiling,None).unwrap(); assert!(q.can_cancel_unpublished(first));q.publish(first).unwrap(); @@ -165,7 +168,7 @@ def item(s,start): let unused=q.reserve(2,DataMaster::Fragment,None).unwrap();q.cancel_unpublished(unused).unwrap();assert_eq!(q.leased,1); let next=q.reserve(3,DataMaster::Fragment,None).unwrap();assert_eq!(next.qid,unused.qid); assert!(!q.can_cancel_unpublished(unused));assert_eq!(q.cancel_unpublished(unused),Err(EINVAL)); - println!("queue IDs: six render/compute capacity boundaries; compute-only render reserve retained; published and stale identities protected"); + println!("queue IDs: render/compute capacity boundaries at zero and reserved floors; compute-only render reserve retained; published and stale identities protected"); // Reproduce the hardware snapshot: 124 installed render identities, two // installed compute identities, and two initial reservations fill128 IDs. let mut q=fresh();q.reserve_initial_render().unwrap(); diff --git a/tools/asahi/t6040-calibration-tests.py b/tools/asahi/t6040-calibration-tests.py new file mode 100644 index 0000000000000..d802013175b97 --- /dev/null +++ b/tools/asahi/t6040-calibration-tests.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Compile the T6040 wire builders and calibration checks without a GPU.""" + +from pathlib import Path +import re +import subprocess +import tempfile + +DRIVER = Path(__file__).resolve().parents[2] / "drivers/gpu/drm/asahi" + + + +HEADER = r''' +#![allow(dead_code)] +extern crate self as kernel; +mod prelude { + pub type KVec = Vec; + pub use std::mem::size_of; + pub type Result = std::result::Result; + #[derive(Debug)] + pub struct Error; + impl From for Error { + fn from(_: std::num::TryFromIntError) -> Self { Self } + } + pub const EINVAL: Error = Error; + pub const EIO: Error = Error; + pub const EOVERFLOW: Error = Error; + pub unsafe trait Zeroable {} +} +macro_rules! static_assert { ($e:expr) => { const _: () = assert!($e); } } +fn clear(value: &mut T) { + unsafe { std::ptr::write_bytes(value, 0, 1); } +} +#[macro_export] +macro_rules! c_str { + ($s:literal) => { std::ffi::CStr::from_bytes_with_nul(concat!($s, "\0").as_bytes()).unwrap() }; +} +pub mod str { pub use std::ffi::CStr; } +mod of { + use crate::prelude::*; + #[derive(Clone)] + pub struct Node(pub std::collections::BTreeMap>); + pub trait Property: Sized { fn decode(value: &[u32]) -> Result; } + impl Property for u32 { + fn decode(value: &[u32]) -> Result { + if value.len() == 1 { Ok(value[0]) } else { Err(EINVAL) } + } + } + impl Property for Vec { fn decode(value: &[u32]) -> Result { Ok(value.to_vec()) } } + impl Node { + pub fn get_property(&self, name: &crate::str::CStr) -> Result { + T::decode(self.0.get(name.to_str().unwrap()).ok_or(EINVAL)?) + } + } +} +mod driver { + pub struct AsahiDrmDevice(pub DrmDevice); + pub struct DrmDevice(pub Device); + pub struct Device(pub crate::of::Node); + impl AsRef for AsahiDrmDevice { fn as_ref(&self) -> &DrmDevice { &self.0 } } + impl AsRef for DrmDevice { fn as_ref(&self) -> &Device { &self.0 } } + impl Device { pub fn of_node(&self) -> Option { Some(self.0.clone()) } } +} +mod float; +mod initdata; +mod g17 { + pub mod fw { pub(crate) use crate::initdata; } + mod calibration; + pub fn check_property_adapter() { + use crate::{driver::*, of::Node}; + let fields: &[(&str, &[u32])] = &[ + ("apple,t6040-handoff-version", &[2]), + ("apple,core-mask", &[0xfffff]), + ("apple,max-power-mw", &[70000]), + ("apple,perf-base-pstate", &[1]), + ("apple,perf-map-a", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-map-b", &[0,1,2,3,4,5,6,7,8,9,15,0,0,0,0,0]), + ("apple,perf-relative-a", &[0,10,20,30,40,50,60,70,80,90,100,0,0,0,0,0]), + ("apple,perf-relative-b", &[0,0,11,22,33,44,55,66,77,88,100,0,0,0,0,0]), + ("apple,core-leak-coef", &[0x44480000, 0x44800000]), + ("apple,sram-leak-coef", &[0x43000000, 0x43000000]), + ("apple,cs-leak-coef", &[0x43800000]), + ("apple,afr-leak-coef", &[0x44000000]), + ]; + let node = Node(fields.iter().map(|(k,v)| (k.to_string(), v.to_vec())).collect()); + let read = |n| calibration::Calibration::read(&AsahiDrmDevice(DrmDevice(Device(n)))); + let chip = read(node.clone()).unwrap(); + assert_eq!(chip.max_power_mw, 70000); + assert_eq!(chip.leakage[0], 0x44480000); + assert_eq!(chip.leakage[1], 0x44800000); + assert_eq!(chip.leakage[32..34], [0x43000000; 2]); + assert_eq!(chip.leakage[52], 0x43800000); + assert_eq!(chip.leakage[54], 0x44000000); + for (key, _) in fields { + let mut missing = node.clone(); missing.0.remove(*key); + assert!(read(missing).is_err(), "accepted missing {key}"); + let mut short = node.clone(); short.0.get_mut(*key).unwrap().pop(); + assert!(read(short).is_err(), "accepted short {key}"); + } + for key in ["apple,perf-map-a", "apple,perf-map-b", "apple,perf-relative-a", "apple,perf-relative-b"] { + for index in 11..16 { + let mut bad = node.clone(); bad.0.get_mut(key).unwrap()[index] = 1; + assert!(read(bad).is_err(), "accepted nonzero tail {key}[{index}]"); + } + } + for (key, value) in [ + ("apple,t6040-handoff-version", 1), + ("apple,core-mask", 0x100000), + ("apple,max-power-mw", 0), + ("apple,perf-base-pstate", 0), + ("apple,perf-base-pstate", 2), + ("apple,cs-leak-coef", 0x7fc00000), + ("apple,afr-leak-coef", 0xbf800000), + ] { + let mut bad = node.clone(); bad.0.insert(key.to_string(), vec![value]); + assert!(read(bad).is_err(), "accepted invalid {key}"); + } + } +} +''' + +TEST = r''' +#[test] +fn property_adapter_requires_complete_chip_data() { g17::check_property_adapter(); } + +#[test] +fn power_configuration_uses_only_its_inputs() { + use initdata::v25g83::PowerConfig25G83; + let mut first = PowerConfig25G83([0xcc; 0x6510]); + let mut leakage = [0; 56]; + leakage[0] = 0x44480000; + leakage[1] = 0x44800000; + leakage[32] = 0x43000000; + leakage[33] = 0x43000000; + leakage[52] = 0x43800000; + leakage[54] = 0x44000000; + first.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + let word = |bytes: &[u8], off| u32::from_le_bytes(bytes[off..off + 4].try_into().unwrap()); + for off in [0xa04, 0xabc, 0x28c8, 0x28cc, 0x28d0, 0x2a08] { + assert_eq!(word(&first.0, off), 76543); + } + for off in [0x9e0, 0x2a04] { + assert_eq!(word(&first.0, off), (76543f32).to_bits()); + } + for (i, value) in leakage.iter().enumerate() { + assert_eq!(word(&first.0, 0x642c + 4 * i), *value); + } + assert_eq!(u64::from_le_bytes(first.0[0x3b50..0x3b58].try_into().unwrap()), 0x12340080); + assert_eq!(u64::from_le_bytes(first.0[0x6318..0x6320].try_into().unwrap()), 0x56780000); + assert_eq!(word(&first.0, 0x63d4), 0xabcdef); + let mut second = PowerConfig25G83([0; 0x6510]); + second.init(0x12340000, 0x56780000, 0xabcdef, 76543, &leakage); + assert_eq!(first.0, second.0); + second.init(0x12340000, 0x56780000, 0xabcdef, 80000, &[0; 56]); + assert_eq!(word(&second.0, 0xa04), 80000); + assert_eq!(word(&second.0, 0x9e0), (80000f32).to_bits()); + for i in 0..56 { + assert_eq!(word(&second.0, 0x642c + 4 * i), 0); + } +} +''' + + +def main(): + with tempfile.TemporaryDirectory(prefix="asahi-t6040-") as tmp: + root = Path(tmp) + (root / "initdata").mkdir() + (root / "g17").mkdir() + for source, target in [ + ("g17/fw/initdata.rs", "initdata.rs"), + ("g17/fw/initdata/v25g83.rs", "initdata/v25g83.rs"), + ]: + text = (DRIVER / source).read_text() + (root / target).write_text(text.replace("use kernel::prelude::*;", "use crate::prelude::*;")) + text = (DRIVER / "float.rs").read_text().split("impl<'a> TryFrom', '#include ', '#include ' + json.dumps(str(header)), 'int main(void) {'] + if capacity_tail: + lines[3:3] = [ + '_Static_assert(sizeof(struct drm_asahi_params_global) == 616, "capacity reply size");', + '_Static_assert(offsetof(struct drm_asahi_params_global, memory_limit) == 600, "capacity prefix");', + '_Static_assert(offsetof(struct drm_asahi_params_global, client_memory_limit) == 608, "client capacity offset");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->memory_limit) == 8, "capacity width");', + '_Static_assert(sizeof(((struct drm_asahi_params_global *)0)->client_memory_limit) == 8, "client capacity width");', + ] lines += [f'printf("{key}=%llu\\n", (unsigned long long)({expr}));' for key, expr in keys] source.write_text('\n'.join(lines + ['return 0;', '}']) + '\n') return run_measurements(shlex.split(os.environ.get('CC', 'cc')) + diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs b/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs index 52461a122b89d..925fb72a49931 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/check-channels.rs @@ -4,6 +4,9 @@ mod channels; #[path = "@KERNEL@/drivers/gpu/drm/asahi/g16/fw/graph.rs"] mod graph; +mod fwid { +@FWID_BUILD@ +} mod hw {pub enum GpuVariant {G}} struct Config { chip_id:u32,gpu_variant:hw::GpuVariant,gpu_revision:u32,uat_ias:u32,uat_oas:u32, diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py b/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py index 7d3a06dac82c0..962713941338b 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/run-golden-tests.py @@ -15,10 +15,14 @@ def main(): manifest = unpack_fixtures(golden) (out / 'public-uapi.rs').write_bytes(frozen_bindings().read_bytes()) backend = root / 'drivers/gpu/drm/asahi/g16' + identity = (root / 'drivers/gpu/drm/asahi/fwid.rs').read_text() + first = identity.index('pub(crate) struct FirmwareBuild {') + build_type = identity[first:identity.index('\n}', first) + 2] logs = [] for name in ['initdata', 'compute', 'render', 'channels', 'initdata-bounds']: source = (HERE / f'check-{name}.rs').read_text() source = source.replace('@KERNEL@', str(root)).replace('@OUTPUT@', str(out)) + source = source.replace('@FWID_BUILD@', build_type) test = out / f'check-{name}.rs' test.write_text(source) binary = out / f'check-{name}' diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py index 03184a58211cf..787992e9b4c24 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-mmu-retention.py @@ -19,7 +19,7 @@ def body(source,marker): mm=(ROOT/'drivers/gpu/drm/asahi/mmu.rs').read_text();pt=(ROOT/'drivers/gpu/drm/asahi/pgtable.rs').read_text() pre=r''' #![allow(dead_code,unused_variables)] -use std::{cell::RefCell,rc::Rc,mem::ManuallyDrop,ops::{Deref,DerefMut,Range},num::NonZeroUsize,sync::{Mutex as StdMutex,atomic::{AtomicBool as StdAtomicBool,AtomicU8,AtomicU32,AtomicU64,AtomicUsize,Ordering,fence}}}; +use std::{cell::RefCell,rc::Rc,mem::{ManuallyDrop,size_of},ops::{Deref,DerefMut,Range},num::NonZeroUsize,sync::{Mutex as StdMutex,atomic::{AtomicBool as StdAtomicBool,AtomicU8,AtomicU32,AtomicU64,AtomicUsize,Ordering,fence}}}; type Result = std::result::Result;const EIO:i32=5;const ETIMEDOUT:i32=110;const EOVERFLOW:i32=75; static TRIP_LOAD:AtomicUsize=AtomicUsize::new(0); struct AtomicBool(StdAtomicBool);impl AtomicBool{fn new(v:bool)->Self{Self(StdAtomicBool::new(v))}fn store(&self,v:bool,o:Ordering){self.0.store(v,o);}fn load(&self,o:Ordering)->bool{let n=TRIP_LOAD.load(Ordering::Relaxed);if n!=0&&TRIP_LOAD.fetch_sub(1,Ordering::Relaxed)==1{self.0.store(true,Ordering::Release);}self.0.load(o)}} @@ -29,6 +29,7 @@ def body(source,marker): struct MutexBackend;struct Mutex(StdMutex);implMutex{fn new(v:T)->Self{Self(StdMutex::new(v))}fn lock(&self)->Guard<'_,T,MutexBackend>{Guard(self.0.lock().unwrap(),std::marker::PhantomData)}} struct Guard<'a,T,M>(std::sync::MutexGuard<'a,T>,std::marker::PhantomData);implDeref for Guard<'_,T,M>{type Target=T;fn deref(&self)->&T{&self.0}}implDerefMut for Guard<'_,T,M>{fn deref_mut(&mut self)->&mut T{&mut self.0}} struct Monotonic;struct Delta;impl Delta{fn from_millis(n:u64)->u64{n}}struct Instant{n:std::cell::Cell,p:std::marker::PhantomData}implInstant{fn now()->Self{Self{n:std::cell::Cell::new(0),p:std::marker::PhantomData}}fn elapsed(&self)->u64{let n=self.n.get();self.n.set(n+1);n}} +mod pgtable{pub fn clean_poc(_p:*const u8,_n:usize){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}pub fn poc_barrier(){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}} mod mem{pub fn sync(){super::BARRIERS.fetch_add(1,super::Ordering::Relaxed);}pub fn tlbi_asid(_:u8){}} #[derive(Clone,Copy,PartialEq,Eq)]enum UatFirmware{Handoff,PublishedRoots} struct Handoff{lock_ap:AtomicU8,lock_fw:AtomicU8,turn:AtomicU32,cur_slot:AtomicU32}impl Handoff{fn new()->Self{Self{lock_ap:AtomicU8::new(0),lock_fw:AtomicU8::new(0),turn:AtomicU32::new(0),cur_slot:AtomicU32::new(0xffff)}} @@ -36,17 +37,19 @@ def body(source,marker): handoff='\n'.join(body(mm,m) for m in ['fn lock(&self) {\n self.lock_ap', 'fn unlock(&self) {\n self.turn', 'fn current_slot(']) mid=r''' } -struct SlotTTBS{ttb0:AtomicU64,ttb1:AtomicU64}struct UatShared{handoff:Handoff,ttbs:[SlotTTBS;64],map_kernel_to_user:bool,kernel_ttb1:u64,binding_generations:[u64;64],binding_published:[bool;64],owned_user_roots:[u64;64]} +struct Quirks{handoff_lock:bool,clean_tables:bool} +struct SlotTTBS{ttb0:AtomicU64,ttb1:AtomicU64}struct UatShared{handoff:Handoff,ttbs:[SlotTTBS;64],map_kernel_to_user:bool,kernel_ttb1:u64,binding_generations:[u64;64],binding_published:[bool;64],owned_user_roots:[u64;64],quirks:Quirks} impl UatShared{fn handoff(&self)->&Handoff{&self.handoff}fn ttbs(&self)->&[SlotTTBS;64]{&self.ttbs}} struct UatInner{firmware:UatFirmware,m4_adopted:bool,failed:AtomicBool,shared:Mutex} ''' +mid=mid.replace('impl UatShared{','impl UatShared{'+''.join(body(mm,m) for m in ['fn handoff_lock(', 'fn handoff_unlock(', 'fn publish_slot('])) raii='#[path="'+str(ROOT/'drivers/gpu/drm/asahi/mmu/ppl.rs')+'"]mod ppl;\n'+'\n'.join(body(mm,m) for m in [ 'struct M4Transport<', 'impl ppl::Memory for M4Transport', 'enum HandoffGuard<', 'impl HandoffGuard<', 'impl Drop for HandoffGuard<' ]) inner='impl UatInner{\n'+'\n'.join(body(mm,m) for m in ['fn lock(&self) -> Guard', 'fn check_m4(', 'fn lock_handoff(', 'fn lock_m4_upper('])+'\n}\n' models=r''' -fn uat(m4:bool,firmware:UatFirmware)->Rc{Rc::new(UatInner{firmware,m4_adopted:m4,failed:AtomicBool::new(false),shared:Mutex::new(UatShared{handoff:Handoff::new(),ttbs:std::array::from_fn(|_|SlotTTBS{ttb0:AtomicU64::new(0),ttb1:AtomicU64::new(0)}),map_kernel_to_user:!m4,kernel_ttb1:0x800000000,binding_generations:[0;64],binding_published:[false;64],owned_user_roots:[0;64]})})} +fn uat(m4:bool,firmware:UatFirmware)->Rc{Rc::new(UatInner{firmware,m4_adopted:m4,failed:AtomicBool::new(false),shared:Mutex::new(UatShared{handoff:Handoff::new(),ttbs:std::array::from_fn(|_|SlotTTBS{ttb0:AtomicU64::new(0),ttb1:AtomicU64::new(0)}),map_kernel_to_user:!m4,kernel_ttb1:0x800000000,binding_generations:[0;64],binding_published:[false;64],owned_user_roots:[0;64],quirks:Quirks{handoff_lock:true,clean_tables:false}})})} #[derive(Clone,Copy)]struct Prot{cached:bool}impl Prot{fn is_cached_noncoherent(&self)->bool{self.cached}} struct Mutation;impl Drop for Mutation{fn drop(&mut self){MUTATIONS.fetch_sub(1,Ordering::Relaxed);}} struct MapTable{fail:bool}impl MapTable{fn unmap_pages(&mut self,_r:Range)->Result{LEAF_CLEARS.fetch_add(1,Ordering::Relaxed);if self.fail{Err(EIO)}else{Ok(())}}} @@ -72,7 +75,7 @@ def body(source,marker): #[derive(Clone)]struct Vm{id:u64,binding:Rc>} struct VmBind(Vm,u32); struct Uat{inner:Rc,slots:Allocator} -mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->&u32{&0}}pub static robust_isolation:Param=Param;} +mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->u32{0}}pub static robust_isolation:Param=Param;} ''' helpers='\n'.join(body(mm,m) for m in ['const fn binding_wire_generation(', 'const fn application_ttb1(']) bind='impl Uat{'+body(mm,'pub(crate) fn bind(&self, vm: &Vm)')+'}\n' diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py index 39d2500f60b19..a6c936f3c0b4a 100755 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-uapi.py @@ -27,7 +27,7 @@ def main(): if args.mesa: mesa = args.mesa.resolve() / 'include/drm-uapi/asahi_drm.h' errors += differences(kernel, c_layout(mesa, bindings, out, 'mesa-layout')) - report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; no kernel parser or feature-semantic coverage', + report = {'passed': not errors, 'scope': 'Compiled public UAPI structures, field offsets and constants; frozen params prefix plus exact known capacity-tail assertions; no parser or feature-semantic coverage', 'kernel': str(header), 'bindings': str(bindings), 'comparisons': len(kernel), 'mesa': str(mesa) if mesa else None, 'mesa_comparison': 'performed' if mesa else 'not requested; set ASAHI_M4_MESA or --mesa', diff --git a/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py b/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py index d7750aab9e147..add96b48bb335 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py +++ b/tools/testing/selftests/drivers/gpu/asahi-m4/test-vm-registry.py @@ -20,7 +20,7 @@ def item(marker): implstd::ops::Deref for KVec{type Target=Vec;fn deref(&self)->&Vec{&self.0}} implstd::ops::DerefMut for KVec{fn deref_mut(&mut self)->&mut Vec{&mut self.0}} mod hw{pub mod sizes{pub const MAX_VMS:u32=63;}} -mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->&u32{&0}}pub static robust_isolation:Param=Param;} +mod module_parameters{pub struct Param;impl Param{pub fn value(&self)->u32{0}}pub static robust_isolation:Param=Param;} mod types{pub struct ScopeGuard(Option);implScopeGuard{pub fn new(f:F)->Self{Self(Some(f))}pub fn dismiss(mut self){self.0.take();}}implDrop for ScopeGuard{fn drop(&mut self){if let Some(f)=self.0.take(){f()}}}} struct Handle;impl Handle{fn is_closed(&self)->bool{false}} struct VmState{id:u64,vm:Handle,drops:Arc}impl Drop for VmState{fn drop(&mut self){self.drops.fetch_add(1,Ordering::AcqRel);}}