From 2c916d27ae4eba6d47cb92277a00e9a4ff946f4b Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Sat, 24 Oct 2026 16:34:27 +0200 Subject: [PATCH 1/2] arm64: dts: apple: t6030: guard static PMP nodes The static PMP nodes block the display gate's overlay and leave G15 deferred on M3 Pro. Keeping either the static DART or mailbox would still conflict with the overlay. Guard all four nodes with APPLE_USE_PMP, matching the existing alias and consumer guards. Check the default and static PMP nodes in CI. Assisted-by: Codex:GPT-6 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eryk Wieliczko --- arch/arm64/boot/dts/apple/t6030.dtsi | 4 ++ tools/asahi/t6030-pmp-dt-tests.py | 65 ++++++++++++++++++++++++++++ tools/aurora-ci/host-tests.json | 29 +++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 tools/asahi/t6030-pmp-dt-tests.py diff --git a/arch/arm64/boot/dts/apple/t6030.dtsi b/arch/arm64/boot/dts/apple/t6030.dtsi index cedc08f69e1d3d..52ad06a0668893 100644 --- a/arch/arm64/boot/dts/apple/t6030.dtsi +++ b/arch/arm64/boot/dts/apple/t6030.dtsi @@ -836,6 +836,8 @@ #clock-cells = <1>; }; + /* The display handoff gate installs PMP unless explicitly static. */ + #ifdef APPLE_USE_PMP pmp_dart: iommu@350300000 { compatible = "apple,t6030-dart", "apple,t8110-dart"; reg = <0x3 0x50300000 0x0 0x4000>; @@ -985,6 +987,8 @@ power-domains = <&ps_pmp>, <&ps_pms_sram>; }; + #endif /* APPLE_USE_PMP */ + aic: interrupt-controller@351000000 { compatible = "apple,t6030-aic3", "apple,t8122-aic3"; #interrupt-cells = <3>; diff --git a/tools/asahi/t6030-pmp-dt-tests.py b/tools/asahi/t6030-pmp-dt-tests.py new file mode 100644 index 00000000000000..22826e998173da --- /dev/null +++ b/tools/asahi/t6030-pmp-dt-tests.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only +"""Check static PMP ownership in the compiled T6030 device trees.""" + +import argparse +from pathlib import Path +import subprocess + + +ROOT = Path(__file__).resolve().parents[2] +BOARDS = ("t6030-j514s", "t6030-j516s") +PMP_NODES = { + "iommu@350300000", + "pmp_report@3503c0000", + "pmp@350500000", + "mbox@350c08000", +} + + +def check_profile(tree, output, dtc, board, static): + profile = "static" if static else "default" + dtb = output / f"{board}-{profile}.dtb" + command = [ + "cpp", "-nostdinc", "-undef", "-D__DTS__", + "-I", str(tree / "scripts/dtc/include-prefixes"), + "-x", "assembler-with-cpp", + ] + if static: + command.append("-DAPPLE_USE_PMP") + command.append(str(tree / f"arch/arm64/boot/dts/apple/{board}.dts")) + source = subprocess.check_output(command) + subprocess.run([dtc, "-q", "-I", "dts", "-O", "dtb", "-o", str(dtb)], + input=source, check=True) + + children = subprocess.check_output(["fdtget", "-l", str(dtb), "/soc"], text=True) + present = PMP_NODES.intersection(children.split()) + expected = PMP_NODES if static else set() + if present != expected: + raise AssertionError(f"{board} {profile}: static PMP nodes " + f"{sorted(present)}, expected {sorted(expected)}") + + alias = subprocess.run(["fdtget", "-t", "s", str(dtb), "/aliases", "pmp"], + capture_output=True, text=True) + if static: + if alias.returncode or alias.stdout.strip() != "/soc/pmp@350500000": + raise AssertionError(f"{board} {profile}: missing or incorrect PMP alias") + elif alias.returncode == 0: + raise AssertionError(f"{board} {profile}: unexpected static PMP alias") + print(f"{board} {profile}: PASS") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--tree", type=Path, default=ROOT) + parser.add_argument("--dtc", required=True, help="this kernel's built scripts/dtc/dtc") + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=True) + for board in BOARDS: + for static in (False, True): + check_profile(args.tree.resolve(), args.output, args.dtc, board, static) + + +if __name__ == "__main__": + main() diff --git a/tools/aurora-ci/host-tests.json b/tools/aurora-ci/host-tests.json index d286dde870e537..9e211b3193377d 100644 --- a/tools/aurora-ci/host-tests.json +++ b/tools/aurora-ci/host-tests.json @@ -2166,6 +2166,35 @@ "rust": "1.93.1", "note": "Public synthetic Rust/C host cases; two unavailable private-hardware fixture cases remain explicitly ignored." }, + { + "name": "tools/asahi/t6030-pmp-dt-tests.py", + "group": "tools", + "setup": [ + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "allnoconfig" + ], + [ + "make", + "-s", + "O={output}/kbuild", + "ARCH=arm64", + "scripts_dtc" + ] + ], + "argv": [ + "python3", + "tools/asahi/t6030-pmp-dt-tests.py", + "--dtc", + "{output}/kbuild/scripts/dtc/dtc", + "--output", + "{output}/dtbs" + ], + "note": "Compile J514S/J516S default and static PMP profiles; check supplier nodes and alias in the resulting DTBs." + }, { "name": "tools/asahi/t6030-pmp-graph-tests.py", "group": "tools", From 8b0cbd58bf95692ae58f7fd88e7e2b57e71cadc3 Mon Sep 17 00:00:00 2001 From: Eryk Wieliczko Date: Sat, 24 Oct 2026 16:34:28 +0200 Subject: [PATCH 2/2] drm/asahi: g15: match UAT reservations with unit addresses The DT uses names such as uat-ttbs@10, but of_find_node_by_path() requires an exact match and misses them. Use of_node_name_eq() on /reserved-memory children. Require one matching owner and keep the existing range and property checks. Extend the host test to cover unit addresses and duplicate owners. Assisted-by: Codex:GPT-6 Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Eryk Wieliczko --- drivers/gpu/drm/asahi/g15/glue.c | 29 ++++-- .../drivers/gpu/asahi-g15/uat-owner.c | 95 ++++++++++++++++--- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/drivers/gpu/drm/asahi/g15/glue.c b/drivers/gpu/drm/asahi/g15/glue.c index 5ceb20cd35d4b8..3b4f9fb60c14c0 100644 --- a/drivers/gpu/drm/asahi/g15/glue.c +++ b/drivers/gpu/drm/asahi/g15/glue.c @@ -88,20 +88,31 @@ void asahi_g15_report_loader_gpu(void) */ bool asahi_g15_reserved_uat(u32 index, u64 start, u64 size) { - static const char *const paths[] = { - "/reserved-memory/uat-ttbs", - "/reserved-memory/uat-pagetables", - "/reserved-memory/uat-handoff", - "/reserved-memory/uat-pagetables-l2", + static const char *const names[] = { + "uat-ttbs", + "uat-pagetables", + "uat-handoff", + "uat-pagetables-l2", }; - struct device_node *np; + struct device_node *rmem, *child, *np = NULL; struct resource res; + unsigned int count = 0; bool match; - if (index >= ARRAY_SIZE(paths)) + if (index >= ARRAY_SIZE(names)) return false; - np = of_find_node_by_path(paths[index]); - match = np && of_device_is_available(np) && + + /* Unit addresses belong to the DT layout, not the reservation name. */ + rmem = of_find_node_by_path("/reserved-memory"); + for_each_child_of_node(rmem, child) { + if (!of_node_name_eq(child, names[index])) + continue; + count++; + if (!np) + np = of_node_get(child); + } + of_node_put(rmem); + match = count == 1 && np && of_device_is_available(np) && !of_property_read_bool(np, "reusable") && !of_get_address(np, 1, NULL, NULL) && !of_address_to_resource(np, 0, &res) && diff --git a/tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c b/tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c index 9909ac31e3a417..48e705c60b77fc 100644 --- a/tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c +++ b/tools/testing/selftests/drivers/gpu/asahi-g15/uat-owner.c @@ -8,22 +8,58 @@ typedef uint32_t u32; typedef uint64_t u64; struct device_node { + const char *name; + struct device_node *child, *next; bool available, reusable, extra, bad_reg; + u64 start, size; + int refs; }; struct resource { u64 start, end; }; -static struct device_node node = { .available = true }; +static struct device_node nodes[5]; +static struct device_node rmem; +#define node nodes[0] static bool missing; static u64 start = 0x100000, size = 0x4000; -static int refs; +static inline struct device_node *of_node_get(struct device_node *np) +{ + if (np) + np->refs++; + return np; +} +static void of_node_put(struct device_node *np); static struct device_node *of_find_node_by_path(const char *path) { - assert(!strncmp(path, "/reserved-memory/uat-", 21)); + const char *prefix = "/reserved-memory/"; + if (missing) return NULL; - refs++; - return &node; + if (!strcmp(path, "/reserved-memory")) + return of_node_get(&rmem); + assert(!strncmp(path, prefix, strlen(prefix))); + for (struct device_node *np = rmem.child; np; np = np->next) + if (!strcmp(np->name, path + strlen(prefix))) + return of_node_get(np); + return NULL; +} +static inline struct device_node *of_get_next_child(struct device_node *parent, + struct device_node *prev) +{ + struct device_node *next = NULL; + + if (parent) + next = prev ? prev->next : parent->child; + of_node_put(prev); + return of_node_get(next); +} +#define for_each_child_of_node(parent, child) \ + for ((child) = of_get_next_child((parent), NULL); (child); \ + (child) = of_get_next_child((parent), (child))) +static inline bool of_node_name_eq(struct device_node *np, const char *name) +{ + return np && strlen(name) == strcspn(np->name, "@") && + !strncmp(np->name, name, strlen(name)); } static bool of_device_is_available(struct device_node *np) { @@ -44,8 +80,8 @@ static int of_address_to_resource(struct device_node *np, int index, struct resource *res) { assert(index == 0); - res->start = start; - res->end = start + size - 1; + res->start = np->start; + res->end = np->start + np->size - 1; return np->bad_reg ? -1 : 0; } static u64 resource_size(struct resource *res) @@ -55,14 +91,49 @@ static u64 resource_size(struct resource *res) static void of_node_put(struct device_node *np) { if (np) - refs--; + assert(np->refs-- > 0); } #include "uat-production.h" int main(void) { + static const char *const names[] = { + "uat-ttbs", "uat-pagetables", "uat-handoff", "uat-pagetables-l2" + }; + static const char *const unit_names[] = { + "uat-ttbs@10", "uat-pagetables@20", "uat-handoff@30", "uat-pagetables-l2@40" + }; + rmem.child = nodes; + for (u32 i = 0; i < 4; ++i) { + nodes[i].available = true; + nodes[i].start = start + i * size; + nodes[i].size = size; + nodes[i].name = names[i]; + nodes[i].next = i < 3 ? &nodes[i + 1] : NULL; + } + for (u32 i = 0; i < 4; ++i) + assert(asahi_g15_reserved_uat(i, nodes[i].start, size)); for (u32 i = 0; i < 4; ++i) - assert(asahi_g15_reserved_uat(i, start, size)); + nodes[i].name = unit_names[i]; + for (u32 i = 0; i < 4; ++i) + assert(asahi_g15_reserved_uat(i, nodes[i].start, size)); + nodes[3].next = &nodes[4]; + nodes[4].name = "uat-ttbs@99"; + nodes[4].available = true; + assert(!asahi_g15_reserved_uat(0, start, size)); + nodes[4].available = false; + assert(!asahi_g15_reserved_uat(0, start, size)); + nodes[3].next = NULL; + node.name = "uat-ttbs-other@10"; + assert(!asahi_g15_reserved_uat(0, start, size)); + node.name = unit_names[0]; + for (u32 i = 0; i < 4; ++i) { + const char *saved = nodes[i].name; + + nodes[i].name = "unrelated@10"; + assert(!asahi_g15_reserved_uat(i, nodes[i].start, size)); + nodes[i].name = saved; + } assert(!asahi_g15_reserved_uat(4, start, size)); assert(!asahi_g15_reserved_uat(0, start + 0x4000, size)); assert(!asahi_g15_reserved_uat(0, start, size + 0x4000)); @@ -80,6 +151,8 @@ int main(void) node.extra = false; node.bad_reg = true; assert(!asahi_g15_reserved_uat(0, start, size)); - assert(refs == 0); - puts("production UAT owner: exact ranges, unavailable/reusable/malformed/extra reservations: PASS"); + assert(rmem.refs == 0); + for (u32 i = 0; i < ARRAY_SIZE(nodes); ++i) + assert(nodes[i].refs == 0); + puts("production UAT owner: unit addresses, unique owners and exact ranges: PASS"); }