From 6397fb569e9df4940b3661a15b637b4f4c647a11 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:34:30 -0500 Subject: [PATCH 01/32] soc: apple: rtkit: allow receivers to observe mailbox delivery time Add optional timestamped delivery while preserving the ordinary C and Rust receiver paths. The native display readiness check needs the receipt time rather than deferred callback execution time. Signed-off-by: Satya Benson --- drivers/gpu/drm/asahi/g15/boot.rs | 1 + drivers/soc/apple/rtkit.c | 9 ++++++++- include/linux/soc/apple/rtkit.h | 4 ++++ rust/kernel/soc/apple/rtkit.rs | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/asahi/g15/boot.rs b/drivers/gpu/drm/asahi/g15/boot.rs index 70decd06fca0d..5c188cb745f61 100644 --- a/drivers/gpu/drm/asahi/g15/boot.rs +++ b/drivers/gpu/drm/asahi/g15/boot.rs @@ -318,6 +318,7 @@ static RTKIT_OPS: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { protocol_version: 0, crashed: Some(rtkit_crashed), recv_message: Some(rtkit_recv), + recv_message_timed: None, recv_message_early: Some(rtkit_recv_early), shmem_setup: Some(rtkit_shmem_setup), shmem_setup_ep: None, diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index d352083aaa26c..c53535a08f660 100644 --- a/drivers/soc/apple/rtkit.c +++ b/drivers/soc/apple/rtkit.c @@ -7,6 +7,7 @@ #include "rtkit-internal.h" #include +#include #include #include #include @@ -122,6 +123,7 @@ struct apple_rtkit_rx_work { struct apple_rtkit *rtk; u8 ep; u64 msg; + u64 received_ns; struct work_struct work; }; @@ -1008,7 +1010,10 @@ static void apple_rtkit_rx_work(struct work_struct *work) apple_rtkit_oslog_rx(rtk, rtk_work->msg); break; case APPLE_RTKIT_APP_ENDPOINT_START ... 0xff: - if (rtk->ops->recv_message) + if (rtk->ops->recv_message_timed) + rtk->ops->recv_message_timed(rtk->cookie, rtk_work->ep, + rtk_work->msg, rtk_work->received_ns); + else if (rtk->ops->recv_message) rtk->ops->recv_message(rtk->cookie, rtk_work->ep, rtk_work->msg); else @@ -1065,6 +1070,7 @@ static void apple_rtkit_rx(struct apple_mbox *mbox, struct apple_mbox_msg msg, struct apple_rtkit *rtk = cookie; struct apple_rtkit_rx_work *work; u8 ep = msg.msg1; + u64 received_ns = ktime_get_ns(); if (READ_ONCE(rtk->shutting_down)) return; @@ -1114,6 +1120,7 @@ static void apple_rtkit_rx(struct apple_mbox *mbox, struct apple_mbox_msg msg, work->rtk = rtk; work->ep = ep; work->msg = msg.msg0; + work->received_ns = received_ns; INIT_WORK(&work->work, apple_rtkit_rx_work); queue_work(rtk->wq, &work->work); } diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h index 65bd28a6f0240..18fcdbae2afcf 100644 --- a/include/linux/soc/apple/rtkit.h +++ b/include/linux/soc/apple/rtkit.h @@ -66,6 +66,9 @@ struct apple_rtkit_inherited_buffer { * context. * @recv_message: Function called when a message from RTKit is received * on a non-system endpoint. Called from a worker thread. + * @recv_message_timed: Like recv_message, carrying the monotonic nanosecond + * timestamp captured before deferring mailbox RX. If set, + * this callback is used instead of recv_message. * @recv_message_early: * Like recv_message, but called from atomic context. It * should return true if it handled the message. If it @@ -95,6 +98,7 @@ struct apple_rtkit_ops { unsigned int flags; void (*crashed)(void *cookie, const void *crashlog, size_t crashlog_size); void (*recv_message)(void *cookie, u8 endpoint, u64 message); + void (*recv_message_timed)(void *cookie, u8 endpoint, u64 message, u64 received_ns); bool (*recv_message_early)(void *cookie, u8 endpoint, u64 message); int (*shmem_setup)(void *cookie, struct apple_rtkit_shmem *bfr); int (*shmem_setup_ep)(void *cookie, u8 endpoint, struct apple_rtkit_shmem *bfr); diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs index 325babdecb370..95c39e144186d 100644 --- a/rust/kernel/soc/apple/rtkit.rs +++ b/rust/kernel/soc/apple/rtkit.rs @@ -225,6 +225,7 @@ impl RtKit { syslog_is_debug: None, crashed: Some(crashed_callback::), recv_message: Some(recv_message_callback::), + recv_message_timed: None, recv_message_early: Some(recv_message_early_callback::), shmem_setup: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP { Some(shmem_setup_callback::) From 2962e43d5a44172af21c2a7af41d4adab4434a10 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:34:30 -0500 Subject: [PATCH 02/32] drm/apple: acquire the J613 native26 external power suppliers Gate native26 admission on the live handoff and native PMP graph. Update existing supplier properties transactionally so failure restores the previous device tree. Keep the classic and internal display paths separate. Signed-off-by: Satya Benson --- .../bindings/display/apple,dcp.yaml | 34 +++ .../boot/dts/apple/t8122-j613-dcp-25g83.dtsi | 19 +- drivers/gpu/drm/apple/j613-25g83.h | 7 + drivers/gpu/drm/apple/j613_dcp_activate.c | 196 ++++++++++++++++++ drivers/pmdomain/apple/pmp-report.c | 144 ++++++++++++- 5 files changed, 392 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index dbb2234164d7e..d96feb2768f7a 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -24,6 +24,7 @@ properties: - apple,t6000-dcp - apple,t6020-dcp - apple,t6041-dcp + - apple,t8122-dcp - apple,t8140-dcp - apple,t8142-dcp - const: apple,dcp @@ -34,6 +35,7 @@ properties: - apple,t6000-dcpext - apple,t6020-dcpext - apple,t6041-dcpext + - apple,t8122-dcpext - const: apple,dcpext - items: - const: apple,t8140-dcpext @@ -209,6 +211,38 @@ properties: maxItems: 5 description: Loader-provided firmware ABI version used for IOMFB selection. + apple,j613-25g83-dcpext-handoff: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: | + Loader validation of the J613 native26 external processor. Requires + the separately validated internal mapping handoff, exact 26.6.2 image, + complete stream-5 mappings and retained locked page tables. Does not + enable hardware. Firmware reservations must also be complete. + + apple,j613-25g83-dcpext-boot-state: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 4 + maxItems: 4 + description: | + Loader observations in order of powered ancestry, ASC CPU control, + ASC CPU status and firmware-DART protection. Read only while the + processor's full power ancestry is active. A powered processor must + have protected inherited roots; this is not an RTKit readiness claim. + + apple,t8122-dcpext-firmware-ready: + $ref: /schemas/types.yaml#/definitions/uint32 + const: 1 + description: All external firmware reservations were committed by the loader. + + apple,firmware-uuid: + $ref: /schemas/types.yaml#/definitions/string + description: UUID of the actual loaded display image, read from its iop nub. + + apple,pmp: + $ref: /schemas/types.yaml#/definitions/phandle + description: Native PMP supplier, independent of the report request entry. + apple,connector-type: enum: [HDMI-A, DP, USB-C] diff --git a/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi b/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi index e292dda82f022..57bb0b13bf614 100644 --- a/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi +++ b/arch/arm64/boot/dts/apple/t8122-j613-dcp-25g83.dtsi @@ -20,7 +20,8 @@ }; &dcpext { - /delete-property/ apple,pmp-report; + apple,pmp-report = <&pmp_report_dispext>; + apple,pmp = <&pmp>; }; &disp0_piodma { @@ -37,10 +38,17 @@ /* This profile requests only DISP and ANS until external admission succeeds. */ &pmp_report_dispext { + apple,soc-device-record; status = "disabled"; }; +&pmp_report_disp { + apple,soc-device-record; +}; + &pmp_report_ans { + apple,soc-device-record; + apple,no-ack; apple,always-on; }; @@ -89,3 +97,12 @@ apple,pmp = <&pmp>; status = "disabled"; }; + +/* Native26 firmware and PIODMA use the same 1-TiB alias as the panel. */ +&dcpext_dart { + apple,dma-range = /bits/ 64 <0x10000000000 0x1000000000>; +}; + +&dispext0_dart { + apple,dma-range = /bits/ 64 <0x10000000000 0x1000000000>; +}; diff --git a/drivers/gpu/drm/apple/j613-25g83.h b/drivers/gpu/drm/apple/j613-25g83.h index 2ac282ce95456..ead1ef0f9b410 100644 --- a/drivers/gpu/drm/apple/j613-25g83.h +++ b/drivers/gpu/drm/apple/j613-25g83.h @@ -43,6 +43,13 @@ static inline bool j613_25g83_mappings(const u32 *markers, size_t cells) markers[2] == 1; } +/* This profile adopts a powered RUN session through a locked firmware DART. */ +static inline bool j613_25g83_external_state(const u32 *state, size_t cells) +{ + return state && cells == 4 && state[0] == 1 && + (state[1] & BIT(4)) && (state[3] & BIT(0)); +} + /* * Local copies of the PMP table checks of * drivers/pmdomain/apple/pmp-report-validation.h, which is not in this tree diff --git a/drivers/gpu/drm/apple/j613_dcp_activate.c b/drivers/gpu/drm/apple/j613_dcp_activate.c index 6d0a262069469..a5c668b5ec4b8 100644 --- a/drivers/gpu/drm/apple/j613_dcp_activate.c +++ b/drivers/gpu/drm/apple/j613_dcp_activate.c @@ -294,6 +294,196 @@ static void j613_resolve(struct device_node **nodes) "apple,t8122-asc-mailbox"); } +static struct of_changeset external_gate; +static bool external_gate_applied; + +/* + * UPDATE_PROPERTY also inserts an absent property and records its old value + * for changeset rollback. Firmware DTs may already constrain these suppliers. + */ +static int __init j613_external_update(struct device_node *np, const char *name, + const void *value, unsigned int length) +{ + struct property *prop = kzalloc_obj(*prop); + int ret; + + if (!prop) + return -ENOMEM; + prop->name = kstrdup(name, GFP_KERNEL); + prop->length = length; + if (length) + prop->value = kmemdup(value, length, GFP_KERNEL); + if (!prop->name || (length && !prop->value)) { + ret = -ENOMEM; + goto free; + } + ret = of_changeset_update_property(&external_gate, np, prop); + if (!ret) + return 0; +free: + kfree(prop->value); + kfree(prop->name); + kfree(prop); + return ret; +} + + +/* Optional external transaction; its failure must not unwind the panel. */ +static int __init j613_external_gate(struct device_node *display) +{ + struct device_node *chosen, *ext = NULL, *pio = NULL, *report = NULL; + struct device_node *nodes[9] = {}, *cpu = NULL, *routes = NULL, *route; + struct of_phandle_args args; + const char *opt, *uuid, *status, *loader_result; + const char *reason = "external node or compatible"; + struct property *prop = NULL; + const void *old; + __be32 *cells; + u32 state[4], version[3], ready; + int len, ret = -EINVAL, i, count = 0; + + chosen = of_find_node_by_path("/chosen"); + opt = chosen ? of_get_property(chosen, "asahi,j613-25g83-dcpext", &len) : NULL; + if (!opt || len != 2 || opt[0] != '1' || opt[1]) { + of_node_put(chosen); + return 0; + } + loader_result = of_get_property(chosen, "asahi,j613-25g83-dcpext-result", &len); + if (loader_result && len > 0 && loader_result[len - 1] == 0) + pr_info("J613 native26 external loader result: %s\n", loader_result); + else + pr_info("J613 native26 external loader result: absent or malformed\n"); + of_node_put(chosen); + if (!of_machine_is_compatible("apple,j613")) + return -ENODEV; + ext = of_find_node_by_path("dcpext"); + if (!ext) + ext = of_find_node_by_path("dcpext0"); +#define J613_EXT_REQUIRE(test, why) do { \ + if (!(test)) { \ + reason = why; \ + goto out; \ + } \ +} while (0) + J613_EXT_REQUIRE(ext && of_device_is_compatible(ext, "apple,t8122-dcpext"), + "external node or compatible"); + J613_EXT_REQUIRE(of_property_count_u32_elems(ext, "apple,firmware-compat") == 3 && + !of_property_read_u32_array(ext, "apple,firmware-compat", version, 3), + "missing or malformed apple,firmware-compat"); + J613_EXT_REQUIRE(!of_property_read_string(ext, "apple,firmware-uuid", &uuid), + "missing apple,firmware-uuid"); + J613_EXT_REQUIRE(j613_25g83_identity(true, 1, version, 3, uuid), "firmware identity"); + J613_EXT_REQUIRE(!of_property_read_u32(ext, "apple,j613-25g83-dcpext-handoff", &ready) && ready == 1, + "missing native handoff marker"); + J613_EXT_REQUIRE(!of_property_read_u32(ext, "apple,t8122-dcpext-firmware-ready", &ready) && ready == 1, + "missing firmware-ready marker"); + J613_EXT_REQUIRE(of_property_count_u32_elems(ext, "apple,j613-25g83-dcpext-boot-state") == 4 && + !of_property_read_u32_array(ext, "apple,j613-25g83-dcpext-boot-state", state, 4), + "missing or malformed boot-state"); + J613_EXT_REQUIRE(j613_25g83_external_state(state, 4), "boot-state not powered RUN and locked"); + J613_EXT_REQUIRE(of_property_present(ext, "memory-region"), "missing memory-region"); +#undef J613_EXT_REQUIRE + pr_info("J613 native26 external handoff: identity, markers, boot-state and memory-region accepted\n"); + reason = "PMP/DART/mailbox suppliers"; + nodes[count++] = of_node_get(ext); + nodes[count++] = j613_target(ext, "iommus", "#iommu-cells", 5, "apple,t8122-dart"); + nodes[count++] = j613_target(ext, "mboxes", "#mbox-cells", -1, "apple,t8122-asc-mailbox"); + pio = of_get_child_by_name(ext, "piodma"); + nodes[count++] = pio ? j613_target(pio, "iommus", "#iommu-cells", 4, "apple,t8122-dart") : NULL; + nodes[count++] = j613_target(ext, "apple,pmp-report", NULL, -1, + "apple,t6000-pmp-v2-report-entry"); + if (!nodes[4] || of_property_read_u32(nodes[4], "reg", &ready) || ready != 8 || + of_property_read_bool(nodes[4], "apple,no-ack") || !nodes[1] || + !nodes[3] || nodes[1] == nodes[3]) + goto out; + reason = "PMP report compatible"; + report = of_get_parent(nodes[4]); + if (!report || !of_device_is_compatible(report, "apple,j613-25g83-pmp-report")) + goto out; + reason = "DISPEXT CPU power domain"; + cpu = of_parse_phandle(ext, "power-domains", 0); + if (!cpu || of_property_read_string(cpu, "label", &status) || strcmp(status, "dispext_cpu") || + !of_property_read_bool(cpu, "apple,firmware-owned")) + goto out; + reason = "two crossbar routes"; + if (of_count_phandle_with_args(ext, "mux-controls", "#mux-control-cells") != 2) + goto out; + for (i = 0; i < 2; i++) { + if (of_parse_phandle_with_args(ext, "mux-controls", "#mux-control-cells", i, &args)) + goto out; + nodes[count++] = args.np; + if (args.args_count != 1 || args.args[0] || + !of_device_is_compatible(args.np, "apple,t6020-display-crossbar")) + goto out; + } + reason = "supplier must be disabled"; + for (i = 0; i < count; i++) { + if (!nodes[i] || of_property_read_string(nodes[i], "status", &status) || + strcmp(status, "disabled")) + goto out; + } + reason = "two Type-C route children"; + routes = of_get_child_by_name(ext, "typec-routes"); + if (of_get_child_count(routes) != 2) + goto out; + reason = "internal display IOMMU shape"; + old = of_get_property(display, "iommus", &len); + if (!old || len != 8 || !nodes[3]->phandle) + goto out; + reason = "changeset allocation"; + prop = kzalloc_obj(*prop); + cells = kmalloc(16, GFP_KERNEL); + if (!prop || !cells) { + kfree(prop); + kfree(cells); + prop = NULL; + ret = -ENOMEM; + goto out; + } + memcpy(cells, old, 8); + cells[2] = cpu_to_be32(nodes[3]->phandle); + cells[3] = 0; + prop->name = kstrdup("iommus", GFP_KERNEL); + prop->length = 16; + prop->value = cells; + if (!prop->name) { + kfree(cells); + kfree(prop); + ret = -ENOMEM; + goto out; + } + reason = "resource changeset"; + of_changeset_init(&external_gate); + ret = of_changeset_update_property(&external_gate, display, prop); + if (!ret) + ret = j613_external_update(nodes[4], "apple,always-on", NULL, 0); + for (i = 0; i < count && !ret; i++) + ret = of_changeset_update_prop_string(&external_gate, nodes[i], "status", "okay"); + for_each_child_of_node(routes, route) { + if (!ret) + ret = of_changeset_update_prop_string(&external_gate, route, "status", "okay"); + } + if (!ret) + ret = of_changeset_apply(&external_gate); + if (ret) + of_changeset_destroy(&external_gate); + else { + external_gate_applied = true; + pr_info("J613 native26 external gate accepted: reserved firmware, DISPEXT12 and both Type-C routes\n"); + } +out: + if (ret) + pr_info("J613 native26 external gate refused: %d (%s); internal display retained\n", ret, reason); + for (i = 0; i < count; i++) + of_node_put(nodes[i]); + of_node_put(cpu); + of_node_put(routes); + of_node_put(report); + of_node_put(pio); + of_node_put(ext); + return ret; +} + int __init apple_j613_25g83_coldplug(void) { struct device_node *nodes[J613_NR_NODES] = {}, *chosen; @@ -389,11 +579,17 @@ int __init apple_j613_25g83_coldplug(void) for (i = 0; i < ARRAY_SIZE(nodes) && !ret; i++) ret = of_changeset_update_prop_string(&gate, nodes[i], "status", "okay"); if (!ret) { + j613_external_gate(nodes[J613_DISPLAY]); display_clock_hz = witness[3]; ret = of_changeset_apply(&gate); } if (ret) { display_clock_hz = 0; + if (external_gate_applied) { + of_changeset_revert(&external_gate); + of_changeset_destroy(&external_gate); + external_gate_applied = false; + } of_changeset_destroy(&gate); } else { display_clock_hz = witness[3]; diff --git a/drivers/pmdomain/apple/pmp-report.c b/drivers/pmdomain/apple/pmp-report.c index 2429a5637c2a9..33b956898da0d 100644 --- a/drivers/pmdomain/apple/pmp-report.c +++ b/drivers/pmdomain/apple/pmp-report.c @@ -104,6 +104,8 @@ struct apple_pmp_report { bool defer; unsigned int polls; struct delayed_work ready_work; + struct device_node *native_pmp; + u64 native_claimed; }; static bool apple_pmp_report_running(const struct apple_pmp_report *rep) @@ -382,6 +384,7 @@ static void apple_pmp_report_release(void *data) synchronize_rcu(); /* The debugfs proxy drains active reads and rejects subsequent accesses. */ debugfs_remove(rep->debugfs); + of_node_put(rep->native_pmp); } /* @@ -657,7 +660,7 @@ static const struct { * records are not in ID order, so the bit is the index of the record that * holds that ID. Only a report that starts its PMP has that table. */ -static int apple_pmp_report_bit(const struct apple_pmp_start *st, +static int apple_pmp_report_record(struct device_node *pmp, struct device_node *entry, u32 *bit, bool *record) { const u8 *table; @@ -671,9 +674,9 @@ static int apple_pmp_report_bit(const struct apple_pmp_start *st, *bit = reg; return 0; } - if (!st) + if (!pmp) return -EINVAL; - table = of_get_property(st->pmp, "apple,tunable-soc-device", &len); + table = of_get_property(pmp, "apple,tunable-soc-device", &len); index = apple_pmp_record_of_id(table, len > 0 ? len : 0, reg + 1); if (index < 0 || index > 63) return -ENOENT; @@ -681,6 +684,73 @@ static int apple_pmp_report_bit(const struct apple_pmp_start *st, return 0; } +static int apple_pmp_report_bit(const struct apple_pmp_start *st, + struct device_node *entry, u32 *bit, bool *record) +{ + return apple_pmp_report_record(st ? st->pmp : NULL, entry, bit, record); +} + +/* The native PMP owns startup; this report still exclusively owns PTD votes. */ +static int apple_pmp_native_records(struct apple_pmp_report *rep) +{ + struct device_node *pmp, *link; + const u8 *table, *group, *ranges; + const char *uuid; + int ntable, ngroup, nranges; + + { + struct device_node *chosen = of_find_node_by_path("/chosen"); + const char *opt = NULL; + int len = 0; + bool selected; + + if (chosen) + opt = of_get_property(chosen, "asahi,j613-25g83-dcpext", &len); + selected = opt && len == 2 && opt[0] == '1' && !opt[1] && + of_device_is_compatible(rep->dev->of_node, "apple,j613-25g83-pmp-report"); + of_node_put(chosen); + if (!selected) + return 0; + } + if (!of_machine_is_compatible("apple,j613") || + !of_device_is_compatible(rep->dev->of_node, "apple,j613-25g83-pmp-report")) + return -ENODEV; + for_each_compatible_node(pmp, NULL, "apple,t8122-pmp-v2") { + link = of_parse_phandle(pmp, "apple,pmp-report", 0); + if (link != rep->dev->of_node || !of_device_is_available(pmp)) { + of_node_put(link); + continue; + } + of_node_put(link); + if (rep->native_pmp) { + of_node_put(pmp); + return -EINVAL; + } + rep->native_pmp = of_node_get(pmp); + } + pmp = rep->native_pmp; + if (!pmp || of_property_read_string(pmp, "apple,tunable-uuid", &uuid) || + strcmp(uuid, "EFD60284-7C58-33A9-8522-CB3366AF6040")) + return -EINVAL; + table = of_get_property(pmp, "apple,tunable-soc-device", &ntable); + group = of_get_property(pmp, "apple,tunable-soc-device-ps-group", &ngroup); + ranges = of_get_property(pmp, "apple,tunable-ptd-range", &nranges); + if (!table || ntable != 20 * PMP_SOC_DEVICE_SIZE || !group || ngroup != 8 || + get_unaligned_le64(group) != 0x3fff0 || + !apple_pmp_ranges_valid(ranges, nranges, 0x1000, 0x10800, 0x1080) || + apple_pmp_record_of_id(table, ntable, 8) != 11 || + apple_pmp_record_of_id(table, ntable, 9) != 12 || + apple_pmp_record_of_id(table, ntable, 18) != 19 || + memcmp(table + 11 * PMP_SOC_DEVICE_SIZE + 0x74, "DISP\0\0\0\0", 8) || + memcmp(table + 12 * PMP_SOC_DEVICE_SIZE + 0x74, "DISPEXT\0", 8) || + memcmp(table + 19 * PMP_SOC_DEVICE_SIZE + 0x74, "ANS\0\0\0\0\0", 8) || + !apple_pmp_records_valid(table, ntable, BIT_ULL(11) | BIT_ULL(12) | BIT_ULL(19), + BIT_ULL(11) | BIT_ULL(12))) + return -EINVAL; + dev_dbg(rep->dev, "native26 PTD admitted: DISP11 DISPEXT12 ANS19 (no ANS ack)\n"); + return 0; +} + /* The single PMGR power state of this SoC with @label. */ static struct device_node *apple_pmp_start_domain(struct apple_pmp_report *rep, const char *label) @@ -1291,6 +1361,17 @@ int apple_pmp_report_wait_ready(struct device_node *entry, unsigned long timeout list_for_each_entry_rcu(rep, &apple_pmp_reports, list) { if (rep->dev->of_node == parent) { bound = true; + if (rep->native_pmp) { + ret = apple_pmp_report_record(rep->native_pmp, entry, &id, &record); + if (!ret && (!record || id != 12)) + ret = -EINVAL; + if (!ret && (!(readq(rep->base + rep->offsets->status) & 1) || + !(readq(rep->base + rep->offsets->actual) & BIT_ULL(id)) || + !(readq(rep->base + rep->offsets->tgt_read) & BIT_ULL(id)))) + ret = -EIO; + rcu_read_unlock(); + goto out; + } break; } } @@ -1358,6 +1439,10 @@ static int apple_pmp_report_probe(struct platform_device *pdev) if (ret) return ret; + ret = apple_pmp_native_records(rep); + if (ret) + return dev_err_probe(dev, ret, "native26 PTD table refused\n"); + /* Before the entries exist: they request by soc-device record through it. */ if (rep->offsets->starts_pmp) { ret = apple_pmp_start_init(rep); @@ -1543,6 +1628,7 @@ struct apple_pmp_report_entry { u32 dvfs_entry; u64 dvfs_on; bool removed; + struct apple_pmp_report *native_owner; }; #define genpd_to_apple_pmp_report_entry(_genpd) \ @@ -1553,6 +1639,7 @@ static int apple_pmp_report_set_state(struct apple_pmp_report *rep, u32 id, { u64 bit_val = BIT_ULL(id); u64 val; + bool latched = true; unsigned long flags; bool ready; @@ -1573,8 +1660,12 @@ static int apple_pmp_report_set_state(struct apple_pmp_report *rep, u32 id, if (enable) val |= bit_val; writeq(val, rep->base + rep->offsets->tgt_write); + if (rep->native_pmp) + latched = !!(readq(rep->base + rep->offsets->tgt_read) & bit_val) == enable; } spin_unlock_irqrestore(&rep->lock, flags); + if (!latched) + return -EIO; if (!ready || !apple_pmp_report_running(rep) || no_ack) return 0; return readq_poll_timeout_atomic( @@ -1764,6 +1855,14 @@ static int apple_pmp_report_entry_power_off(struct generic_pm_domain *genpd) static void apple_pmp_report_entry_free(struct apple_pmp_report_entry *ent) { + if (ent->native_owner) { + unsigned long flags; + + spin_lock_irqsave(&ent->native_owner->lock, flags); + ent->native_owner->native_claimed &= ~BIT_ULL(ent->id); + spin_unlock_irqrestore(&ent->native_owner->lock, flags); + } + of_node_put(ent->report_node); of_node_put(ent->node); kfree(ent->genpd.name); @@ -1789,7 +1888,7 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *node = dev->of_node; struct apple_pmp_report_entry *ent; - const struct apple_pmp_report *rep; + struct apple_pmp_report *rep; struct apple_pmp_start *start; int ret; const char *name; @@ -1814,7 +1913,30 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) /* Only a report that starts its PMP requests by record, or without ack. */ rep = dev->parent ? dev_get_drvdata(dev->parent) : NULL; start = rep ? rep->start : NULL; - if (start) { + if (rep && rep->native_pmp) { + unsigned long flags; + bool record; + + ret = apple_pmp_report_record(rep->native_pmp, node, &ent->id, &record); + if (ret || !record || (ent->id != 11 && ent->id != 12 && ent->id != 19)) { + ret = -EINVAL; + goto err_free; + } + ent->no_ack = of_property_read_bool(node, "apple,no-ack"); + if (ent->no_ack != (ent->id == 19)) { + ret = -EINVAL; + goto err_free; + } + spin_lock_irqsave(&rep->lock, flags); + ret = rep->native_claimed & BIT_ULL(ent->id) ? -EBUSY : 0; + if (!ret) { + rep->native_claimed |= BIT_ULL(ent->id); + ent->native_owner = rep; + } + spin_unlock_irqrestore(&rep->lock, flags); + if (ret) + goto err_free; + } else if (start) { bool record; ret = apple_pmp_report_bit(start, node, &ent->id, &record); @@ -1853,7 +1975,14 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) if (of_property_read_bool(node, "apple,always-on")) { ent->genpd.flags |= GENPD_FLAG_ACTIVE_WAKEUP; - apple_pmp_report_entry_set_state(&ent->genpd, true); + ret = apple_pmp_report_entry_set_state(&ent->genpd, true); + if (ret && ent->native_owner) + goto err_free; + /* Native DCPEXT keeps its resident producer and request through s2idle. */ + if (ent->native_owner && ent->id == 12) { + ent->genpd.flags |= GENPD_FLAG_ALWAYS_ON; + ent->sleep_release = false; + } } ent->genpd.power_on = apple_pmp_report_entry_power_on; @@ -1866,7 +1995,8 @@ static int apple_pmp_report_entry_probe(struct platform_device *pdev) if (of_property_read_bool(node, "apple,irq-safe")) ent->genpd.flags |= GENPD_FLAG_IRQ_SAFE; - ret = pm_genpd_init(&ent->genpd, NULL, true); + ret = pm_genpd_init(&ent->genpd, NULL, + !(ent->genpd.flags & GENPD_FLAG_ALWAYS_ON)); if (ret) { ret = dev_err_probe(dev, ret, "pm_genpd_init failed\n"); goto err_free; From c88b6c207ed4d9456c6636f2d3cc5cdf4dc1b500 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:34:31 -0500 Subject: [PATCH 03/32] drm/apple: encode J613 native26 external primary surfaces Validate backing size, dimensions and address arithmetic before constructing the separate 26.6.2 wire record. Initialize nullable layers and decode each supported reply layout explicitly. Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcpext26-wire.h | 122 ++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 drivers/gpu/drm/apple/dcpext26-wire.h diff --git a/drivers/gpu/drm/apple/dcpext26-wire.h b/drivers/gpu/drm/apple/dcpext26-wire.h new file mode 100644 index 0000000000000..2fa54054260d3 --- /dev/null +++ b/drivers/gpu/drm/apple/dcpext26-wire.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCPEXT26_WIRE_H +#define APPLE_DCPEXT26_WIRE_H + +#include +#include +#include +#include + +/* J613 26.6.2 external personality; neither classic 14.7 nor H17P. */ +#define DCPEXT26_SUBMIT_BYTES 0x1bd8 +#define DCPEXT26_SURFACE_BYTES 0x22c + +struct dcpext26_primary { + u64 address; + u64 backing_bytes; + u32 width; + u32 height; + u32 pitch; +}; + +/* Validate all arithmetic before changing a caller-owned command buffer. */ +static inline int dcpext26_primary_check(const struct dcpext26_primary *p) +{ + u64 bytes = (u64)p->pitch * p->height; + + if (!p->address || !p->width || !p->height || + (p->pitch & 3) || (u64)p->width * 4 > p->pitch) + return -EINVAL; + if (bytes > (u32)~0U || bytes > p->backing_bytes || + p->address > (u64)~0ULL - bytes) + return -ERANGE; + return 0; +} + +/* Full-frame linear opaque BGRA. All unused layers stay explicitly null. */ +static inline int dcpext26_primary_encode(void *buffer, size_t capacity, + u32 id, unsigned int layer, + const struct dcpext26_primary *p) +{ + u8 *wire = buffer, *surface; + u32 bytes; + unsigned int i; + int ret; + + if (!buffer || capacity != DCPEXT26_SUBMIT_BYTES || !id || layer >= 4) + return -EINVAL; + if (p) { + ret = dcpext26_primary_check(p); + if (ret) + return ret; + } + memset(wire, 0, capacity); + put_unaligned_le32(id, wire + 0x98); + put_unaligned_le32(0x80000007, wire + 0x14c); + put_unaligned_le32(0x80000007, wire + 0x150); + put_unaligned_le32(0xff000000, wire + 0x154); + /* Ten nullable surfaces, followed by the two output-pointer flags. */ + memset(wire + 0x1bcb, 1, 10); + wire[0x1bd6] = 1; + wire[0x1bd7] = 1; + if (!p) + return 0; + + surface = wire + 0x588 + layer * DCPEXT26_SURFACE_BYTES; + bytes = p->pitch * p->height; + { + const struct { unsigned int offset; u32 value; } words[] = { + {0x21, p->width}, {0x59, p->width}, + {0x25, p->height}, {0x5d, p->height}, + {0x15, p->pitch}, {0x69, p->pitch}, + {0x29, bytes}, {0x6d, bytes}, + {3, 1}, {7, 1}, {0xb, 0x42475241}, {0x35, 1}, + }; + const struct { unsigned int offset; u8 value; } octets[] = { + {2, 1}, {0x13, 13}, {0x14, 12}, {0x19, 1}, + {0x1b, 1}, {0x1c, 1}, {0x71, 4}, {0x73, 1}, {0x74, 1}, + }; + + for (i = 0; i < sizeof(words) / sizeof(words[0]); i++) + put_unaligned_le32(words[i].value, surface + words[i].offset); + for (i = 0; i < sizeof(octets) / sizeof(octets[0]); i++) + surface[octets[i].offset] = octets[i].value; + put_unaligned_le64(1, surface + 0x149); + put_unaligned_le64(1, surface + 0x51); + } + put_unaligned_le64(p->address, wire + 0xe38 + layer * 8); + put_unaligned_le32(1, wire + 0x9c + layer * 4); + for (i = 0; i < 2; i++) { + u8 *rect = wire + (i ? 0x10c : 0xac) + layer * 16; + + put_unaligned_le32(p->width, rect + 8); + put_unaligned_le32(p->height, rect + 12); + } + wire[0x1bcb + layer] = 0; + return 0; +} + +static inline int dcpext26_start_reply(const void *reply, size_t length, u32 *id) +{ + const u8 *wire = reply; + u32 value; + + if (!reply || !id || length != 8) + return -EINVAL; + value = get_unaligned_le32(wire); + if (!value || get_unaligned_le32(wire + 4)) + return -EPROTO; + *id = value; + return 0; +} + +static inline int dcpext26_submit_reply(const void *reply, size_t length) +{ + const u8 *wire = reply; + + if (!reply || length != 12) + return -EINVAL; + return get_unaligned_le32(wire + 5) ? -EPROTO : 0; +} + +#endif From 9649e5974600bcb794f4d35b21741dc008fc767f Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:34:31 -0500 Subject: [PATCH 04/32] drm/apple: support retained J613 native26 external displays Separate carrier startup, EPIC service identity and controller OPEN from display-port admission. Validate attachment generations, EDID and primary modesets; preserve same-mode readiness across DPMS and repeated commits. Retain the firmware session through s2idle and restart discovery after DRM resume. Release pending flips on a failed modeset without dereferencing an absent backlink. Keep native26 explicitly selected and leave classic peers on their existing protocol. Fold callback, declaration, reply padding, admission, transactional supplier and atomic-primary fixes from the attended C13h series into their owning paths. Omit diagnostic catalogue and lifecycle tracing; keep remaining informational native messages at debug level. Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/afk.c | 106 +++- drivers/gpu/drm/apple/afk.h | 4 + drivers/gpu/drm/apple/apple_drv.c | 2 +- drivers/gpu/drm/apple/dcp-fabric.c | 18 +- drivers/gpu/drm/apple/dcp-fabric.h | 1 + drivers/gpu/drm/apple/dcp-internal.h | 46 +- drivers/gpu/drm/apple/dcp.c | 381 +++++++++++-- drivers/gpu/drm/apple/dcp.h | 3 + drivers/gpu/drm/apple/dcpext26-lifecycle.h | 42 ++ drivers/gpu/drm/apple/dptxep.c | 208 +++++++- drivers/gpu/drm/apple/dptxep.h | 2 + drivers/gpu/drm/apple/epic/dpavservep.c | 15 +- drivers/gpu/drm/apple/iomfb.c | 163 +++++- drivers/gpu/drm/apple/iomfb.h | 5 + drivers/gpu/drm/apple/iomfb_h17p.c | 591 ++++++++++++++++++++- drivers/gpu/drm/apple/iomfb_internal.h | 4 + drivers/gpu/drm/apple/iomfb_template.c | 171 +++++- drivers/gpu/drm/apple/iomfb_template.h | 7 +- drivers/gpu/drm/apple/plane.c | 30 +- drivers/gpu/drm/apple/plane.h | 2 +- drivers/gpu/drm/apple/systemep.c | 6 +- 21 files changed, 1694 insertions(+), 113 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcpext26-lifecycle.h diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c index 77a7d06fe45b7..fe34f7cbd1259 100644 --- a/drivers/gpu/drm/apple/afk.c +++ b/drivers/gpu/drm/apple/afk.c @@ -69,7 +69,8 @@ void afk_service_retry(struct apple_dcp_afkep *ep) { unsigned long flags; - if (!ep || !ep->wire_v2 || ep != ep->dcp->dcpavservep) + if (!ep || !ep->wire_v2 || + (!dcp_is_ext26(ep->dcp) && ep != ep->dcp->dcpavservep)) return; spin_lock_irqsave(&ep->lock, flags); if (!ep->stopping) @@ -107,6 +108,7 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint, afkep->ops = ops; afkep->dcp = dcp; afkep->endpoint = endpoint; + afkep->wire_v2 = dcp_is_ext26(dcp); afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x", WQ_MEM_RECLAIM, endpoint); if (!afkep->wq) { @@ -1282,8 +1284,12 @@ static void afk_retry_service_init(struct apple_dcp_afkep *ep) if (!service->pending_open) continue; - ret = ops->revalidate(service, service->pending_name, - service->pending_class, service->pending_unit); + if (dcp_is_ext26(ep->dcp) && + (!READ_ONCE(ep->dcp->ext26_services_open) || + (ep == ep->dcp->dptxep && !dptx_native_controllers_ready(ep->dcp)))) + continue; + ret = ops->revalidate ? ops->revalidate(service, service->pending_name, + service->pending_class, service->pending_unit) : 0; if (ret) { afk_discard_pending_open(service); continue; @@ -1301,8 +1307,9 @@ static void afk_retry_service_init(struct apple_dcp_afkep *ep) } service->pending_open = false; afk_service_publish(service); - ops->init(service, service->pending_name, service->pending_class, - service->pending_unit); + if (ops->init) + ops->init(service, service->pending_name, service->pending_class, + service->pending_unit); if (service->enabled) afk_populate_service_debugfs(service); kfree(service->pending_name); @@ -1335,8 +1342,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, const char *epic_name = NULL, *epic_class = NULL; const struct apple_epic_service_ops *ops; struct dcp_parse_ctx ctx; - u8 *props = payload + sizeof(name); - size_t props_size = payload_size - sizeof(name); + u8 *props; + size_t props_size; previous = afk_epic_find_service(ep, channel); /* @@ -1361,7 +1368,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, if (!pending->pending_open || pending->channel != channel) continue; - if (!pending->ops->revalidate(pending, pending->pending_name, + if (!pending->ops->revalidate || + !pending->ops->revalidate(pending, pending->pending_name, pending->pending_class, pending->pending_unit)) return; afk_discard_pending_open(pending); @@ -1374,6 +1382,8 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, } strscpy(name, payload, sizeof(name)); + props = payload + sizeof(name); + props_size = payload_size - sizeof(name); /* * in DCP firmware 13.2 DCP reports interface-name as name which starts @@ -1390,8 +1400,18 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, return; } ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit, - ep->dcp->fw_compat == DCP_FIRMWARE_H17P); + ep->dcp->fw_compat == DCP_FIRMWARE_H17P && + !dcp_is_ext26(ep->dcp)); if (ret) { + /* These native infrastructure reports have no EPIC identity. */ + if (dcp_is_ext26(ep->dcp) && ret == -ENOENT && + (ep->endpoint == SYSTEM_ENDPOINT || + ep->endpoint == DISP0_ENDPOINT)) { + dev_dbg(ep->dcp->dev, + "native26 AFK ep=%#x interface=%u report=%s has no EPIC identity; ignored\n", + ep->endpoint, channel, name); + return; + } dev_err(ep->dcp->dev, "AFK[ep:%02x]: failed to extract init props: %d\n", ep->endpoint, ret); @@ -1406,6 +1426,10 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ops = afk_match_service(ep, epic_name); else ops = afk_match_service(ep, service_name); + if (dcp_is_ext26(ep->dcp)) + dev_dbg(ep->dcp->dev, + "native26 AFK identity ep=%#x interface=%u name=%s class=%s unit=%lld\n", + ep->endpoint, channel, epic_name ?: name, epic_class ?: "", epic_unit); /* T6030 14.7: the DPAV controller endpoint binds whatever it announces. */ if (!ops && ep->dcp->fw_compat == DCP_FIRMWARE_V_14_7 && ep->ops && @@ -1416,6 +1440,9 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, ops = ep->ops; } + if (!ops && dcp_is_ext26(ep->dcp) && ep->ops && + !strcmp(ep->ops->name, "native26-observer")) + ops = ep->ops; if (!ops) { dev_err(ep->dcp->dev, "AFK[ep:%02x]: unable to match service %s on channel %d\n", @@ -1446,13 +1473,19 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel, "AFK optional %s (%s) is unsupported on channel %u\n", ops->name, service_name, channel); else - dev_err(ep->dcp->dev, "AFK service %s admission failed: %d\n", - service_name, ret); + dev_err(ep->dcp->dev, + "AFK service %s admission failed: %d (ep=%#x interface=%u name=%s unit=%lld)\n", + service_name, ret, ep->endpoint, channel, epic_name ?: name, epic_unit); afk_service_discard(service); goto free; } } - if (previous && retired && ops->revalidate) { + if ((previous && retired && ops->revalidate) || + (dcp_is_ext26(ep->dcp) && + (!READ_ONCE(ep->dcp->ext26_services_open) || + (ep == ep->dcp->dptxep && !dptx_native_controllers_ready(ep->dcp))))) { + /* No reader may use a native interface before its deferred OPEN. */ + WRITE_ONCE(service->enabled, false); /* Preserve the admitted identity and its original provider ticket. */ service->pending_open = true; service->pending_name = epic_name; @@ -2726,6 +2759,22 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, goto err_put_service; } + if (dcp_is_ext26(ep->dcp)) { + size_t charge = ALIGN(payload_len, SZ_16K) + ALIGN(output_len, SZ_16K); + + spin_lock_irqsave(&ep->lock, flags); + ret = READ_ONCE(ep->dcp->crashed) ? -EIO : + (ep->ext26_command_count >= 255 || + charge > SZ_16M - ep->ext26_command_bytes) ? -ENOSPC : 0; + if (!ret) { + ep->ext26_command_bytes += charge; + ep->ext26_command_count++; + } + spin_unlock_irqrestore(&ep->lock, flags); + if (ret) + goto err_put_service; + } + rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma, GFP_KERNEL); if (!rxbuf) { @@ -2819,7 +2868,9 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, */ if (!service->cmds[idx].done) { service->cmds[idx].completion = NULL; - service->cmds[idx].free_on_ack = true; + service->cmds[idx].free_on_ack = !dcp_is_ext26(ep->dcp); + if (dcp_is_ext26(ep->dcp)) + WRITE_ONCE(ep->dcp->crashed, true); spin_unlock_irqrestore(&service->lock, flags); dev_warn(ep->dcp->dev, "AFK[ep:%02x]: %s (chan:%u) command type 0x%x tag 0x%04x timed out after %u ms\n", @@ -2883,9 +2934,11 @@ static int afk_send_command_common(struct apple_epic_service *service, u8 type, spin_unlock_irqrestore(&service->lock, flags); err_free_txbuf: kfree(inline_command); - dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma); + if (!dcp_is_ext26(ep->dcp)) + dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma); err_free_rxbuf: - dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma); + if (!dcp_is_ext26(ep->dcp)) + dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma); err_put_service: afk_service_put(service); return ret; @@ -3244,3 +3297,26 @@ static void afk_remove_service_debugfs(struct apple_epic_service *srv) #if IS_ENABLED(CONFIG_DRM_APPLE_PARSER_KUNIT_TEST) #include "afk-parser-test.c" #endif + +/* Called after RX and the ordered endpoint worker have been joined. */ +bool afk_host_idle(struct apple_dcp_afkep *ep) +{ + unsigned long flags; + unsigned int i; + bool idle = true; + + spin_lock_irqsave(&ep->lock, flags); + if (ep->rxbfr.ready) + idle = READ_ONCE(*ep->rxbfr.rptr) == READ_ONCE(*ep->rxbfr.wptr); + if (ep->txbfr.ready) + idle &= READ_ONCE(*ep->txbfr.rptr) == READ_ONCE(*ep->txbfr.wptr); + spin_unlock_irqrestore(&ep->lock, flags); + for (i = 0; i < AFK_MAX_CHANNEL && idle; i++) { + struct apple_epic_service *service = &ep->services[i]; + + spin_lock_irqsave(&service->lock, flags); + idle = bitmap_empty(service->cmd_map, MAX_PENDING_CMDS); + spin_unlock_irqrestore(&service->lock, flags); + } + return idle; +} diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h index a4f2afb38588f..f6b32eb853322 100644 --- a/drivers/gpu/drm/apple/afk.h +++ b/drivers/gpu/drm/apple/afk.h @@ -487,6 +487,8 @@ struct apple_dcp_afkep { void *bfr; u16 bfr_tag; size_t bfr_size; + size_t ext26_command_bytes; + unsigned int ext26_command_count; dma_addr_t bfr_dma; struct afk_ringbuffer txbfr; @@ -526,6 +528,8 @@ int afk_v2_copy_owned(struct apple_epic_service *service, u64 dva, int afk_start(struct apple_dcp_afkep *ep); int afk_start_service(struct apple_epic_service *service); void afk_cancel_commands(struct apple_dcp_afkep *ep); +/* A sampled host boundary, never a remote-producer or DMA fence. */ +bool afk_host_idle(struct apple_dcp_afkep *ep); void afk_shutdown(struct apple_dcp_afkep *ep); void afk_quiesce(struct apple_dcp_afkep *ep); int afk_receive_message(struct apple_dcp_afkep *ep, u64 message); diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index a1680f12c2d2f..e6c1fd5e45381 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -901,7 +901,7 @@ static int apple_probe_per_dcp(struct device *dev, supports_l10r, supports_xrgb2101010, plane_type, interchange, - supports_premultiplied); + supports_premultiplied, dcp_is_ext26(dcp_data)); } if (IS_ERR(planes[zpos])) return PTR_ERR(planes[zpos]); diff --git a/drivers/gpu/drm/apple/dcp-fabric.c b/drivers/gpu/drm/apple/dcp-fabric.c index b3bb5982524cc..ed5bfd8926b83 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.c +++ b/drivers/gpu/drm/apple/dcp-fabric.c @@ -588,7 +588,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) dcp_peer_release_26_6(dcp); guard(mutex)(&dcp->external_lock); - if (dcp_is_external_h17p(dcp) && + if (dcp_is_retained_external(dcp) && (dcp->external_error || (dcp->external_power_attempted && !dcp->external_stopped))) return dcp->external_error ?: -EAGAIN; @@ -641,7 +641,7 @@ static int dcp_typec_route_deactivate(struct apple_dcp_typec_route *route) if (sel && sel != -EOPNOTSUPP) dev_warn(dcp->dev, "DP tunnel source reset failed: %d\n", sel); } - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) dpavservep_detach(dcp); route->selected = false; ended_attachment = dcp_modes_end_typec(dcp, route); @@ -1490,7 +1490,7 @@ static bool dcp_rebalance_deactivate(void *data, void *entry) (dcp->typec_connector && dcp->typec_connector->connected)) { ret = dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); /* H17P external pipes keep their own stop recovery, as before. */ - if (ret && !dcp_is_external_h17p(dcp)) + if (ret && !dcp_is_retained_external(dcp)) return true; } port->hpd = false; @@ -1622,7 +1622,7 @@ static int dcp_typec_route_prepare(struct typec_mux_dev *mux, port->dp_wanted = false; port->dp_hpd = false; /* HPD absence alone is not proof of a completed firmware shutdown. */ - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { scoped_guard(mutex, &dcp->external_lock) { if (dcp->external_error) return dcp->external_error; @@ -1764,7 +1764,7 @@ static int dcp_typec_route_set(struct typec_mux_dev *mux, (dcp->typec_connector && dcp->typec_connector->connected)) { ret = dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); /* H17P external pipes keep their own stop recovery, as before. */ - if (ret && !dcp_is_external_h17p(dcp)) + if (ret && !dcp_is_retained_external(dcp)) return ret; } /* @@ -2144,7 +2144,7 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) struct mux_control *xbar = NULL; int ret; - if (!of_machine_is_compatible("apple,t6030")) + if (!of_machine_is_compatible("apple,t6030") && !dcp_is_ext26(dcp)) return 0; guard(mutex)(&dcp->tb_lock); route = dcp->active_typec_route; @@ -2155,7 +2155,7 @@ int dcp_direct_crossbar_link(struct apple_dcp *dcp, bool up) /* A deselected output went idle, and its clocks with it. */ if (dcp->direct_xbar_up && dcp->direct_xbar_up != xbar) dcp->direct_xbar_up = NULL; - if (!xbar || !!dcp->direct_xbar_up == up) + if (!xbar || (!!dcp->direct_xbar_up == up && (up || !dcp_is_ext26(dcp)))) return 0; ret = dcp_dpxbar_link(xbar, up); if (ret) { @@ -3751,7 +3751,7 @@ static void dcp_typec_route_unregister(void *data) if (port->hpd || dcp->typec_cable_connected) dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); port->hpd = false; - if (dcp_typec_route_deactivate(route) && dcp_is_external_h17p(dcp)) + if (dcp_typec_route_deactivate(route) && dcp_is_retained_external(dcp)) return; port->owner = NULL; } @@ -3762,7 +3762,7 @@ static void dcp_typec_route_unregister(void *data) dcp_tb_binding_retire(dcp, route); if (port->hpd || dcp->typec_cable_connected) dcp_dptx_disconnect_oob(to_platform_device(dcp->dev), 0); - if (dcp_typec_route_deactivate(route) && dcp_is_external_h17p(dcp)) + if (dcp_typec_route_deactivate(route) && dcp_is_retained_external(dcp)) return; port->secondary_owner = NULL; } diff --git a/drivers/gpu/drm/apple/dcp-fabric.h b/drivers/gpu/drm/apple/dcp-fabric.h index 57bffb02d715f..bf0233db4c287 100644 --- a/drivers/gpu/drm/apple/dcp-fabric.h +++ b/drivers/gpu/drm/apple/dcp-fabric.h @@ -122,5 +122,6 @@ void dcp_fabric_shutdown_dptx(struct apple_dcp *dcp); int dcp_dptx_connect(struct apple_dcp *dcp, u32 port); int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port); bool dcp_is_external_h17p(struct apple_dcp *dcp); +bool dcp_is_retained_external(struct apple_dcp *dcp); #endif /* __APPLE_DCP_FABRIC_H__ */ diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 65daae9a3f5dc..ac5f77c2112e9 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -44,6 +44,7 @@ struct apple_dcp; struct iomfb_m4; +struct ext26_control; struct apple_dcp_afkep; struct dcp_method_entry; @@ -197,6 +198,7 @@ enum dcp_iomfb_method_profile { DCP_IOMFB_METHODS_DEFAULT, DCP_IOMFB_METHODS_H17G, DCP_IOMFB_METHODS_H17P_EXTERNAL, + DCP_IOMFB_METHODS_J613_EXT26, }; struct apple_dcp_hw_data { @@ -473,6 +475,41 @@ struct apple_dcp { /* DCP has crashed */ bool crashed; + /* Native26 control/property epochs, serialized by the IOMFB owner. */ + struct completion ext26_link_ready; + bool ext26_services_open; + bool ext26_client_open; + struct apple_dcp_afkep *ext26_aux[3]; + u32 ext26_stride; + u64 ext26_edid_generation; + bool ext26_edid_tried; + u64 ext26_rx_ns; + u64 ext26_power_ns; + u64 ext26_generation; + u64 ext26_buffer_bytes; + bool ext26_blocked; + bool ext26_pm_work_off; + bool ext26_stopping; + bool ext26_blanked; + bool ext26_pm_preparing; + bool ext26_control_pending; + bool ext26_early_complete; + u32 ext26_early_id; + u64 ext26_early_ns; + u32 ext26_control_id; + struct ext26_control *ext26_control; + struct work_struct ext26_blank_work; + struct list_head ext26_retained; + u64 ext26_retained_bytes; + unsigned int ext26_epochs; + struct drm_display_mode ext26_mode; + DECLARE_BITMAP(ext26_released, DCP_MAX_MAPPINGS); + bool ext26_power_sent; + bool ext26_normal; + struct completion ext26_ready; + iomfb_cb_handler *ext26_handlers; + u8 ext26_present[0x1bd8]; + DECLARE_BITMAP(iomfb_surfaces, DCP_MAX_PLANES); @@ -955,6 +992,11 @@ static inline bool dcp_fw_is_14_7(const struct apple_dcp *dcp) dcp->hw.fw_14_7_endpoints); } +static inline bool dcp_is_ext26(const struct apple_dcp *dcp) +{ + return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_J613_EXT26; +} + /* * The T6030 external processor (the J514S/J516S built-in HDMI port and * USB-C displays) driven natively through Aurora's DCPLink session: DCP @@ -987,8 +1029,8 @@ void dcp_external_audio_start(struct apple_dcp *dcp); static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) { /* Not the generic M1/M2 DCPs this tree also runs on the template. */ - return dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && - dcp->hw.fw_14_7_endpoints; + return (dcp->fw_compat == DCP_FIRMWARE_V_14_7_TEMPLATE && + dcp->hw.fw_14_7_endpoints); } #define DCP_AUDIO_MAX_CHANS 15 diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index be07aff95d776..f7d4f36a0cec4 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -2,6 +2,7 @@ /* Copyright 2021 Alyssa Rosenzweig */ #include +#include #include #include #include @@ -61,6 +62,7 @@ #include "dcp-syslog.h" #include "dcpext_scanout.h" #include "dcp-internal.h" +#include "j613-25g83.h" #include "dcp-external-bw.h" #include "dptx-service.h" #include "iomfb.h" @@ -406,7 +408,49 @@ bool dcp_is_external_h17p(struct apple_dcp *dcp) return dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; } +bool dcp_is_retained_external(struct apple_dcp *dcp) +{ + return dcp_is_external_h17p(dcp) || dcp_is_ext26(dcp); +} + +static void dcp_ext26_blank_work(struct work_struct *work) +{ + struct apple_dcp *dcp = container_of(work, struct apple_dcp, ext26_blank_work); + int ret; + + guard(mutex)(&dcp->external_lock); + if (dcp->ext26_pm_preparing || dcp->external_stopped || + !READ_ONCE(dcp->pipe_enabled_h17p)) + return; + iomfb_pause_presentation(dcp, true); + ret = iomfb_ext26_blank(dcp); + if (ret) { + WRITE_ONCE(dcp->ext26_blocked, true); + dcp->external_error = ret; + dev_err(dcp->dev, "native26 null control failed: %d; uncertain backing retained\n", ret); + } else { + WRITE_ONCE(dcp->ext26_blanked, true); + dev_dbg(dcp->dev, "native26 DPMS blank complete; link, mode and backing retained\n"); + } +} + static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation); +int dcp_ext26_layout_stop(struct apple_dcp *dcp) +{ + int ret; + + /* Atomic modeset owns external_lock; poweron joined the blank worker. */ + lockdep_assert_held(&dcp->external_lock); + ret = dcp_external_stop_locked(dcp, dcp->external_generation); + if (!ret) { + WRITE_ONCE(dcp->ext26_blanked, false); + dcp->external_generation++; + dcp_queue_typec_reconnect(dcp, 0); + return -EAGAIN; /* A fresh owner/catalogue/backing is required. */ + } + return ret; +} + static int dcp_external_disconnect(struct apple_dcp *dcp, bool physical); /* copied and simplified from drm_vblank.c */ @@ -1150,6 +1194,14 @@ static struct apple_dcp_afkep *dcp_afkep(struct apple_dcp *dcp, u8 endpoint) if (ep) return ep; } + if (dcp_is_ext26(dcp)) { + if (endpoint == 0x27) + return dcp->ext26_aux[0]; + if (endpoint == 0x29) + return dcp->ext26_aux[1]; + if (endpoint == 0x2c) + return dcp->ext26_aux[2]; + } /* DPAV 0x2a on H17P overlaps the legacy DPTX endpoint number. */ if (dcp->dcpavservep && endpoint == dcp->dcpavservep->endpoint) return dcp->dcpavservep; @@ -1251,7 +1303,7 @@ void dcp_swap_watchdog_complete(struct apple_dcp *dcp) void dcp_external_link_fault(struct apple_dcp *dcp) { dcp_180hz_invalidate(dcp); - if (dcp_is_external_h17p(dcp) && dcp_mode_invalidate(&dcp->mode_state)) + if (dcp_is_retained_external(dcp) && dcp_mode_invalidate(&dcp->mode_state)) dcp_handle_hotplug_actions(dcp, DCP_HOTPLUG_NOTIFY); } @@ -1296,6 +1348,15 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) afk_receive_message(ep, message); } +static void dcp_recv_msg_timed(void *cookie, u8 endpoint, u64 message, u64 received_ns) +{ + struct apple_dcp *dcp = cookie; + + if (dcp_is_ext26(dcp) && endpoint == IOMFB_ENDPOINT) + dcp->ext26_rx_ns = received_ns; + dcp_recv_msg(cookie, endpoint, message); +} + static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) { struct apple_dcp *dcp = cookie; @@ -1424,6 +1485,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr) static struct apple_rtkit_ops rtkit_ops = { .crashed = dcp_rtk_crashed, .recv_message = dcp_recv_msg, + .recv_message_timed = dcp_recv_msg_timed, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1438,6 +1500,7 @@ static const struct apple_rtkit_ops external_h17p_rtkit_ops = { .syslog_is_debug = dcp_rtk_syslog_is_debug, .crashed = dcp_rtk_crashed, .recv_message = dcp_recv_msg, + .recv_message_timed = dcp_recv_msg_timed, .shmem_setup = dcp_rtk_shmem_setup, .shmem_destroy = dcp_rtk_shmem_destroy, }; @@ -1447,7 +1510,8 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) bool native_14_7 = dcp->fw_compat == DCP_FIRMWARE_V_14_7; int ret; - if (READ_ONCE(dcp->quiescing)) + if (READ_ONCE(dcp->quiescing) || + (dcp_is_ext26(dcp) && READ_ONCE(dcp->ext26_blocked))) return; trace_dcp_send_msg(dcp, endpoint, message); /* @@ -1456,7 +1520,11 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message) * DisplayPort handshake then waits for a reply that was never sent. */ ret = apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL, - native_14_7 ? in_atomic() : true); + (native_14_7 || dcp_is_ext26(dcp)) ? in_atomic() : true); + if (ret && dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->crashed, true); + dev_err(dcp->dev, "native26 mailbox send failed ep=%#x: %d\n", endpoint, ret); + } if (ret && native_14_7) dev_warn_ratelimited(dcp->dev, "DCP send ep %02x failed: %d\n", endpoint, ret); @@ -1626,7 +1694,7 @@ static bool dcp_connect_session_valid(struct apple_dcp *dcp, /* H17P (J700, T8140, T8142) connects and reconnects as before, without one. */ static bool dcp_typec_sessionless(struct apple_dcp *dcp) { - return dcp->fw_compat == DCP_FIRMWARE_H17P || dcp_is_external_h17p(dcp); + return dcp->fw_compat == DCP_FIRMWARE_H17P || dcp_is_retained_external(dcp); } /* Capture the session at enqueue, not when a stale worker eventually runs. */ @@ -1727,6 +1795,12 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable dfp_port = port && READ_ONCE(dcp->split.active) ? 2 : dcp->dptx_dfp_port; /* Compact DPTX (desktop 26.x) has no connection validation call. */ if (!dcp_ext_dptx_607(dcp) && !compact) { + if (dcp_is_ext26(dcp)) { + ret = dcp_direct_crossbar_link(dcp, false); + if (ret) + goto out_unlock; + } + if (!dcp_ext_dptx_607(dcp)) { ret = dptxport_validate_connection(dcp->dptxport[port].service, dfp_port, dcp->dptx_phy, dcp->dptx_die); @@ -1859,7 +1933,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable /* Do not timestamp a new attachment after an older link wait. */ if (!port && dcp_is_typec_output(dcp) && !dcp_is_usb4_output(dcp) && dcp->fw_compat != DCP_FIRMWARE_H17P && !dcp->external_native && - !dcp_is_external_h17p(dcp)) { + !dcp_is_retained_external(dcp)) { guard(mutex)(&dcp->hpd_mutex); if (dcp->typec_generation == generation && dcp->typec_cable_connected) { scoped_guard(spinlock_irqsave, &dcp->relink_lock) @@ -1871,7 +1945,7 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable out_withdraw: mutex_lock(&dcp->hpd_mutex); - if (!dcp_is_external_h17p(dcp)) { + if (!dcp_is_retained_external(dcp)) { dcp->dptxport[port].connected = false; goto out_release; } @@ -1941,7 +2015,9 @@ static int dcp_external_link_locked(struct apple_dcp *dcp) int ret; lockdep_assert_held(&dcp->external_lock); - if (!dcp->external_prepared) + if (!dcp->external_prepared || + (dcp_is_ext26(dcp) && (!READ_ONCE(dcp->ext26_services_open) || + !dptx_native_controllers_ready(dcp)))) return -EAGAIN; if (dcp->external_error) return dcp->external_error; @@ -1959,8 +2035,18 @@ static int dcp_external_start_locked(struct apple_dcp *dcp) lockdep_assert_held(&dcp->external_lock); ret = dcp_external_link_locked(dcp); - if (ret || dcp->external_running) + if (ret) return ret; + /* Gentle DPMS kept the link running but paused presentation. */ + if (dcp_is_ext26(dcp)) { + dcp->external_running = true; + dcp->external_stopped = false; + WRITE_ONCE(dcp->typec_crtc_off, false); + iomfb_pause_presentation(dcp, false); + return 0; + } + if (dcp->external_running) + return 0; /* A deferred stop sent nothing: the pipe is still powered. */ if (dcp->external_stop_deferred) { dcp->external_stop_deferred = false; @@ -2398,6 +2484,7 @@ int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) return dcp_desktop_dptx_connect(dcp, port); if (!dcp_is_external_h17p(dcp)) { + if (!dcp_is_retained_external(dcp)) { if (dcp_typec_sessionless(dcp)) { ret = dcp_dptx_connect_raw(dcp, port, NULL, NULL, false, false); } else { @@ -2431,6 +2518,10 @@ static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation) if (dcp->external_stopped && !READ_ONCE(dcp->dptxport[0].connected)) return 0; iomfb_pause_presentation(dcp, true); + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_stopping, true); + dpavservep_detach(dcp); + } dcp->external_running = false; /* Route setup can call this before IOMFB or its shared memory exists. */ if (dcp->external_stopped || !dcp->external_power_attempted) @@ -2480,7 +2571,33 @@ static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation) dcp->dptxport[0].connected = false; } } + if (dcp_is_ext26(dcp)) { + struct apple_dcp_afkep *endpoints[] = {dcp->systemep, dcp->ibootep, + dcp->dptx_controller_ep, dcp->dptxep, dcp->dcpavservep, + dcp->ext26_aux[0], dcp->ext26_aux[1], dcp->ext26_aux[2]}; + unsigned int i; + + step = "draining the retained epoch"; + apple_rtkit_flush_rx(dcp->rtk); + for (i = 0; i < ARRAY_SIZE(endpoints); i++) + if (!IS_ERR_OR_NULL(endpoints[i])) + flush_workqueue(endpoints[i]->wq); + apple_rtkit_flush_rx(dcp->rtk); + ret = iomfb_queue_settle(dcp, msecs_to_jiffies(5000)); + for (i = 0; !ret && i < ARRAY_SIZE(endpoints); i++) + if (!IS_ERR_OR_NULL(endpoints[i]) && !afk_host_idle(endpoints[i])) + ret = -EBUSY; + if (!ret) + ret = iomfb_ext26_retain_epoch(dcp); + if (ret) + goto failed; + dcp_modes_begin_attachment(dcp); + WRITE_ONCE(dcp->pipe_enabled_h17p, false); + WRITE_ONCE(dcp->ext26_normal, false); + } dcp->external_stopped = true; + if (dcp_is_ext26(dcp)) + WRITE_ONCE(dcp->ext26_stopping, false); WRITE_ONCE(dcp->typec_crtc_off, true); if (dcp->avep) av_service_disconnect(dcp); @@ -2498,10 +2615,14 @@ static int dcp_external_disconnect(struct apple_dcp *dcp, bool physical) cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); + if (dcp_is_ext26(dcp)) + flush_work(&dcp->ext26_blank_work); guard(mutex)(&dcp->external_lock); /* Invalidate physical EDID before waiting for firmware power/link teardown. */ if (physical) { WRITE_ONCE(dcp->external_physically_absent, true); + if (dcp_is_ext26(dcp)) + dcp_modes_begin_attachment(dcp); scoped_guard(mutex, &dcp->hpd_mutex) { WRITE_ONCE(dcp->typec_cable_connected, false); dcp->typec_generation++; @@ -2554,10 +2675,24 @@ static void dcp_external_stop_work(struct work_struct *work) bool dcp_external_hotplug(struct apple_dcp *dcp, bool connected) { - if (!dcp_is_external_h17p(dcp)) + if (!dcp_is_retained_external(dcp)) return false; /* No lifecycle or HPD mutex may be acquired from the RX callback. */ lockdep_assert_held(&dcp->iomfb.lock); + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->ext26_pm_preparing) || READ_ONCE(dcp->ext26_stopping) || + READ_ONCE(dcp->quiescing) || + READ_ONCE(dcp->external_physically_absent)) + return true; + if (connected) + return false; + if (READ_ONCE(dcp->external_stopped)) + return true; + dcp->external_stop_generation = dcp->external_rx_generation; + WRITE_ONCE(dcp->ext26_normal, false); + schedule_work(&dcp->external_stop_work); + return true; + } if (READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->external_physically_absent)) return true; if (dcp->iomfb.presentation_paused) { @@ -2783,7 +2918,7 @@ bool dcp_hold_resume_event(struct platform_device *pdev) struct apple_dcp *dcp = platform_get_drvdata(pdev); struct apple_connector *con = READ_ONCE(dcp->connector); - if (!dcp_is_external_h17p(dcp) || !con || READ_ONCE(con->dcp) != pdev) + if (!dcp_is_retained_external(dcp) || !con || READ_ONCE(con->dcp) != pdev) return false; guard(mutex)(&dcp->hpd_mutex); @@ -2843,7 +2978,7 @@ static bool dcp_typec_relink_eligible(struct apple_dcp *dcp) /* H17P and native external pipes recover through their own lifecycle */ if (dcp->fw_compat == DCP_FIRMWARE_H17P || dcp->external_native || !dcp_is_typec_output(dcp) || dcp_is_usb4_output(dcp) || - dcp_is_external_h17p(dcp) || !dcp->typec_connector) + dcp_is_retained_external(dcp) || !dcp->typec_connector) return false; /* * Only a sink that is attached, shown and still connected for DRM: @@ -3003,7 +3138,7 @@ int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port) { int ret; - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) return dcp_external_disconnect(dcp, false); /* Release the caller's RemotePort service, not the downstream DFP port. */ dev_info(dcp->dev, "%s(port=%d)\n", __func__, port); @@ -3026,7 +3161,8 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_retained_external(dcp)) { bool new_cable; cancel_delayed_work_sync(&dcp->typec_reconnect_wq); @@ -3071,7 +3207,7 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_disconnect_oob(dcp, port); - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) return dcp_external_disconnect(dcp, true); if (dcp_is_typec_output(dcp)) { @@ -3122,7 +3258,7 @@ int dcp_dptx_recover_irq(struct apple_dcp *dcp) int ret; mutex_lock(&dcp->hpd_mutex); - if (dcp->external_native || dcp_is_external_h17p(dcp) || + if (dcp->external_native || dcp_is_retained_external(dcp) || !dcp_is_typec_output(dcp) || dcp_is_usb4_output(dcp) || !dcp->typec_connector || READ_ONCE(dcp->typec_connector->connected) || @@ -3650,6 +3786,77 @@ static int dcp_start_desktop(struct apple_dcp *dcp) if (ret) dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret); return ret; + } +static int ext26_aux_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t size, void *reply, size_t room) +{ + memset(reply, 0, room); + memcpy(reply, data, min(size, room)); + if (room >= sizeof(u32)) + put_unaligned_le32(0, reply); + return 0; +} + +static const struct apple_epic_service_ops ext26_aux_ops[] = { + { .name = "native26-observer", .call_v2 = ext26_aux_call }, {} +}; + +void dcp_ext26_open_services(struct apple_dcp *dcp) +{ + struct apple_dcp_afkep *eps[] = { dcp->systemep, dcp->ibootep, + dcp->dptx_controller_ep, dcp->ext26_aux[0], dcp->dcpavservep, + dcp->ext26_aux[1], dcp->dptxep, dcp->ext26_aux[2] }; + unsigned int i; + + WRITE_ONCE(dcp->ext26_services_open, true); + for (i = 0; i < ARRAY_SIZE(eps); i++) + if (!IS_ERR_OR_NULL(eps[i])) + afk_service_retry(eps[i]); +} + +static int dcp_ext26_start_services(struct apple_dcp *dcp) +{ + static const u8 auxiliary[] = { 0x27, 0x29, 0x2c }; + unsigned int i; + int ret; + + ret = iomfb_start_rtkit(dcp); + if (ret) + return ret; + if (!wait_for_completion_timeout(&dcp->ext26_link_ready, msecs_to_jiffies(5000))) + return dev_err_probe(dcp->dev, -ETIMEDOUT, "native26 DCPLink readiness timed out\n"); + if (READ_ONCE(dcp->crashed)) + return -EIO; + ret = systemep_init(dcp); + if (!ret) + ret = ibootep_init(dcp); + if (!ret) + ret = dptx_controller_init(dcp); + for (i = 0; !ret && i < ARRAY_SIZE(auxiliary); i++) { + struct apple_dcp_afkep *ep; + + if (i == 1) { + ret = dpavservep_init(dcp); + if (ret) + break; + } + if (i == 2) { + ret = dptxep_init(dcp); + if (ret) + break; + } + ep = afk_init(dcp, auxiliary[i], ext26_aux_ops); + if (IS_ERR(ep)) + return PTR_ERR(ep); + ep->match_epic_name = false; + dcp->ext26_aux[i] = ep; + ret = afk_start(ep); + } + if (ret) + return dev_err_probe(dcp->dev, ret, "native26 AFK startup failed\n"); + dev_dbg(dcp->dev, "native26 AFK ready: 20 23 24 27 28 29 2a 2c; starting A401\n"); + iomfb_ext26_start(dcp); + return 0; } int dcp_start(struct platform_device *pdev) @@ -3690,6 +3897,8 @@ int dcp_start(struct platform_device *pdev) if (IS_ERR_OR_NULL(dcp->rtk)) return -ENODEV; + if (dcp_is_ext26(dcp)) + return dcp_ext26_start_services(dcp); /* Make the H17P allocator available before external service discovery. */ if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { @@ -3710,14 +3919,14 @@ int dcp_start(struct platform_device *pdev) ret); } - if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { + if ((dcp->phy || dcp_is_ext26(dcp)) && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) { ret = ibootep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n", ret); if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) { + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(dcp)) { ret = dptx_controller_init(dcp); if (ret) return dev_err_probe(dcp->dev, ret, @@ -3762,7 +3971,7 @@ int dcp_start(struct platform_device *pdev) } #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) - if (hdmi_audio) { + if (hdmi_audio && !dcp_is_ext26(dcp)) { ret = avep_init(dcp); if (ret) dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret); @@ -4010,7 +4219,10 @@ void dcp_poweron(struct platform_device *pdev) return; } - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_ext26(dcp)) + flush_work(&dcp->ext26_blank_work); + if (dcp_is_retained_external(dcp)) { cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); cancel_work_sync(&dcp->external_stop_work); @@ -4133,7 +4345,7 @@ void dcp_external_recover(struct platform_device *pdev) { struct apple_dcp *dcp = platform_get_drvdata(pdev); - if (dcp_is_external_h17p(dcp)) + if (dcp_is_retained_external(dcp)) dcp_poweron(pdev); } @@ -4152,7 +4364,13 @@ void dcp_poweroff(struct platform_device *pdev) return; } - if (dcp_is_external_h17p(dcp)) { + + if (dcp_is_ext26(dcp)) { + if (!READ_ONCE(dcp->ext26_pm_preparing) && !READ_ONCE(dcp->external_stopped)) + queue_work(system_unbound_wq, &dcp->ext26_blank_work); + return; + } + if (dcp_is_retained_external(dcp)) { cancel_delayed_work_sync(&dcp->typec_reconnect_wq); cancel_delayed_work_sync(&dcp->placeholder_edid_wq); guard(mutex)(&dcp->external_lock); @@ -4701,6 +4919,27 @@ static int dcp_read_fw_version(struct device *dev, const char *name, return len; } +static bool dcp_ext26_admitted(struct device_node *np) +{ + struct device_node *chosen = of_find_node_by_path("/chosen"); + const char *opt, *uuid; + int length = 0; + u32 version[3], marker; + bool ok; + + opt = chosen ? of_get_property(chosen, "asahi,j613-25g83-dcpext", &length) : NULL; + ok = opt && length == 2 && opt[0] == '1' && !opt[1] && + of_machine_is_compatible("apple,j613") && + !of_property_read_u32(np, "apple,j613-25g83-dcpext-handoff", &marker) && marker == 1 && + !of_property_read_u32(np, "apple,t8122-dcpext-firmware-ready", &marker) && marker == 1 && + of_property_count_u32_elems(np, "apple,firmware-compat") == 3 && + !of_property_read_u32_array(np, "apple,firmware-compat", version, 3) && + !of_property_read_string(np, "apple,firmware-uuid", &uuid) && + j613_25g83_identity(true, 1, version, 3, uuid); + of_node_put(chosen); + return ok; +} + static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) { const struct apple_dcp_hw_data *hw = of_device_get_match_data(dev); @@ -4778,6 +5017,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) * firmware through the classic IOMFB template, as on M1/M2. */ if (of_device_is_compatible(dev->of_node, "apple,t8122-dcpext")) { + if (dcp_ext26_admitted(dev->of_node)) + return DCP_FIRMWARE_H17P; if (!strcmp(compat_str, "14.7.0")) return DCP_FIRMWARE_V_14_7_TEMPLATE; dev_err(dev, "T8122 external display not started: DCP firmware-compat %s is not 14.7.0\n", @@ -4861,7 +5102,7 @@ VISIBLE_IF_KUNIT void dcp_disable_typec_work(struct apple_dcp *dcp, bool release /* Nothing arms the timeout work past its drain below. */ dcp->announce.stopped = true; } - if (release_cable && dcp_is_external_h17p(dcp)) + if (release_cable && dcp_is_retained_external(dcp)) dpavservep_detach(dcp); /* Block new enqueues as well as draining users of the AFK endpoints. */ disable_delayed_work_sync(&dcp->typec_reconnect_wq); @@ -4897,7 +5138,7 @@ VISIBLE_IF_KUNIT void dcp_enable_typec_work(struct apple_dcp *dcp) /* A cable can be routed before the DRM component binds. */ if (READ_ONCE(dcp->typec_cable_connected)) dcp_queue_typec_reconnect(dcp, 0); - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { struct apple_connector *con = READ_ONCE(dcp->connector); guard(mutex)(&dcp->hpd_mutex); @@ -4973,7 +5214,7 @@ static int dcp_ext_wait_pmp(struct apple_dcp *dcp) unsigned long deadline; int ret; - if (!dcp_ext_dptx_607(dcp)) + if (!dcp_ext_dptx_607(dcp) && !dcp_is_ext26(dcp)) return 0; entry = of_parse_phandle(dcp->dev->of_node, "apple,pmp-report", 0); if (!entry) { @@ -5044,7 +5285,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat return ret; /* A native external processor mapped these at probe, for its start work. */ - if (!dcp->external_native) { + if (!dcp->external_native && !dcp_is_ext26(dcp)) { dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc"); if (IS_ERR(dcp->coproc_reg)) @@ -5066,7 +5307,11 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (dcp->hw.adopt_live_session) { cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); - if (!(cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) + if (dcp_is_ext26(dcp)) + dev_dbg(dev, "native26 initial carrier: control=%#x status=%#x; RTKit %s\n", + cpu_ctrl, readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS), + cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? "warm INIT" : "resident first boot"); + if (!(cpu_ctrl & APPLE_DCP_COPROC_CPU_CONTROL_RUN) && !dcp_is_ext26(dcp)) return dev_err_probe(dev, -EOPNOTSUPP, "DCP was not left running by the bootloader\n"); } @@ -5228,6 +5473,13 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat if (ret) goto err_piodma; + if (dcp_is_ext26(dcp)) { + ret = dcp_ext26_start_resident(dcp); + if (ret) + goto err_piodma; + goto session_ready; + } + if (!dcp->hw.adopt_live_session) { cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); @@ -5270,6 +5522,7 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat ret = dev_err_probe(dev, ret, "Failed to boot RTKit: %d\n", ret); goto err_piodma; } +session_ready: enable_work(&dcp->dimensions_wq); schedule_work(&dcp->dimensions_wq); dcp_enable_typec_work(dcp); @@ -5360,6 +5613,8 @@ VISIBLE_IF_KUNIT void dcp_comp_unbind(struct device *dev, struct device *main, v return; } + if (dcp_is_ext26(dcp)) + cancel_work_sync(&dcp->ext26_blank_work); if (dcp->retain_dma) { /* No firmware stop is acknowledged: detach host users, never DMA. */ scoped_guard(mutex, &dcp->iomfb.lock) @@ -5693,6 +5948,23 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) /* The display such a processor finds at boot is its first sink. */ if (dcp_sink_metadata(dcp)) dcp_desktop_metadata_new_sink(dcp); + if (of_device_is_compatible(dev->of_node, "apple,t8122-dcpext") && + fw_compat == DCP_FIRMWARE_H17P) { + dcp->hw.iomfb_method_profile = DCP_IOMFB_METHODS_J613_EXT26; + dcp->hw.adopt_live_session = true; + dcp->hw.fw_14_7_endpoints = false; + init_completion(&dcp->ext26_link_ready); + init_completion(&dcp->ext26_ready); + INIT_WORK(&dcp->ext26_blank_work, dcp_ext26_blank_work); + INIT_LIST_HEAD(&dcp->ext26_retained); + dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc"); + if (IS_ERR(dcp->coproc_reg)) + return PTR_ERR(dcp->coproc_reg); + dev_dbg(dev, "native26 probe carrier: control=%#x status=%#x; firmware CPU lease supplied by loader\n", + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL), + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS)); + } + if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) { if (!dcp->hw.firmware_clock || @@ -5822,6 +6094,11 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) set_bit(surf, dcp->iomfb_surfaces); surf++; } + if (dcp_is_ext26(dcp)) { + bitmap_zero(dcp->iomfb_surfaces, DCP_MAX_PLANES); + set_bit(0, dcp->iomfb_surfaces); + } + /* The J700 loader names surface zero rather than enabling it. */ if (of_machine_is_compatible("apple,j700") && of_device_is_compatible(dev->of_node, "apple,t8140-dcp") && @@ -5988,8 +6265,7 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) return iomfb_v14_7_external_start(dcp); } - if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) { + if (dcp_is_retained_external(dcp)) { /* Complete optional startup before the DRM master can depend on it. */ ret = dcp_comp_bind(dev, NULL, NULL); if (ret) { @@ -6350,6 +6626,15 @@ static int dcp_platform_prepare(struct device *dev) struct apple_dcp *dcp = dev_get_drvdata(dev); const char *why; + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->crashed) || READ_ONCE(dcp->ext26_blocked)) + return -EIO; + WRITE_ONCE(dcp->ext26_pm_preparing, true); + cancel_work_sync(&dcp->ext26_blank_work); + dcp_disable_typec_work(dcp, false); + dcp->ext26_pm_work_off = true; + return 0; + } if (!dcp->external) return 0; mutex_lock(&dcp->hpd_mutex); @@ -6371,6 +6656,16 @@ static void dcp_platform_complete(struct device *dev) { struct apple_dcp *dcp = dev_get_drvdata(dev); + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_pm_preparing, false); + if (dcp->ext26_pm_work_off && !READ_ONCE(dcp->ext26_blocked) && + !READ_ONCE(dcp->crashed)) { + dcp_enable_typec_work(dcp); + dcp->ext26_pm_work_off = false; + } + return; + } + if (!dcp->external) return; mutex_lock(&dcp->hpd_mutex); @@ -6397,6 +6692,10 @@ static int dcp_platform_suspend(struct device *dev) device_set_wakeup_path(dev); return ret; } + if (dcp_is_ext26(dcp) && pm_suspend_target_state != PM_SUSPEND_TO_IDLE) { + dev_warn(dev, "native26 refuses non-s2idle target %d\n", pm_suspend_target_state); + return -EBUSY; + } if (READ_ONCE(dcp->ext_dead)) return 0; iomfb_v14_7_backlight_suspend(dcp); @@ -6422,16 +6721,23 @@ static int dcp_platform_suspend(struct device *dev) mutex_unlock(&dcp->hpd_mutex); } - if (dcp_is_external_h17p(dcp)) { + if (dcp_is_retained_external(dcp)) { int ret; - dcp_disable_typec_work(dcp, false); + if (!dcp_is_ext26(dcp)) + dcp_disable_typec_work(dcp, false); ret = dcp_external_disconnect(dcp, false); if (ret) { - dcp_enable_typec_work(dcp); + if (!dcp_is_ext26(dcp)) + dcp_enable_typec_work(dcp); return ret; } + if (dcp_is_ext26(dcp) && + !(readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL) & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) + return -EHOSTDOWN; device_set_wakeup_path(dev); + if (dcp_is_ext26(dcp)) + dev_dbg(dev, "native26 retained s2idle stop; firmware and supplier leases held\n"); return 0; } @@ -6482,6 +6788,19 @@ static int dcp_platform_resume(struct device *dev) return 0; dcp->link_recovery_tries = 0; + if (dcp_is_ext26(dcp)) { + if (!(readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL) & APPLE_DCP_COPROC_CPU_CONTROL_RUN) || + READ_ONCE(dcp->crashed)) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->external_error, -EHOSTDOWN); + dev_err(dev, "native26 firmware did not survive s2idle; retained session requires reboot\n"); + return -EHOSTDOWN; + } + WRITE_ONCE(dcp->external_suspended, false); + /* Complete restarts discovery after DRM has dropped the stale CRTC. */ + dev_dbg(dev, "native26 resumed retained firmware; discovery waits for PM complete\n"); + return 0; + } dcp_release_after_sleep(dcp); iomfb_v14_7_backlight_resume(dcp); dcp_enable_typec_work(dcp); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index d65367c8af13f..f049885abce98 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -74,6 +74,8 @@ bool dcp_ext_unusable(struct platform_device *pdev); unsigned long* dcp_get_iomfb_surfaces(struct platform_device *pdev); int dcp_start(struct platform_device *pdev); int dcp_activate(struct platform_device *pdev); +void dcp_ext26_open_services(struct apple_dcp *dcp); +int dcp_ext26_layout_stop(struct apple_dcp *dcp); int dcp_wait_ready(struct platform_device *pdev, u64 timeout); void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_commit *commit); bool dcp_hdr_metadata_live(struct platform_device *pdev); @@ -121,6 +123,7 @@ int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port); bool dcp_hold_resume_event(struct platform_device *pdev); int iomfb_start_rtkit(struct apple_dcp *dcp); +void iomfb_ext26_start(struct apple_dcp *dcp); void iomfb_shutdown(struct apple_dcp *dcp); /* rtkit message handler for IOMFB messages */ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message); diff --git a/drivers/gpu/drm/apple/dcpext26-lifecycle.h b/drivers/gpu/drm/apple/dcpext26-lifecycle.h new file mode 100644 index 0000000000000..4eb85f49e22e9 --- /dev/null +++ b/drivers/gpu/drm/apple/dcpext26-lifecycle.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCPEXT26_LIFECYCLE_H +#define APPLE_DCPEXT26_LIFECYCLE_H + +#include + +/* A protocol control completion grants no permission to retire DMA backing. */ +struct dcpext26_exchange { + u32 id; + bool replied; + bool completed; + bool failed; +}; + +static inline bool dcpext26_exchange_begin(struct dcpext26_exchange *x, u32 id) +{ + if (!id || x->id) + return false; + x->id = id; + return true; +} + +static inline bool dcpext26_exchange_complete(struct dcpext26_exchange *x, u32 id) +{ + if (!x->id || id != x->id) + return false; + x->completed = true; + return true; +} + +static inline bool dcpext26_exchange_finished(const struct dcpext26_exchange *x) +{ + return x->failed || (x->id && x->replied && x->completed); +} +/* Reserve headroom for an in-flight full-frame primary before readmission. */ +static inline bool dcpext26_epoch_fits(unsigned int epochs, u64 retained, u64 incoming) +{ + const u64 limit = (1ULL << 30) - (64ULL << 20); + + return !incoming || (epochs < 8 && retained <= limit && incoming <= limit - retained); +} +#endif diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 66196c8c5cc61..cf06543e51dab 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -598,7 +598,8 @@ static int dptxport_call_set_link_rate(struct apple_epic_service *service, } } else if (dptx->atcphy) { /* No clocked crossbar output across a PHY rate change. */ - dcp_direct_crossbar_link(service->ep->dcp, false); + if (!dcp_is_ext26(service->ep->dcp)) + dcp_direct_crossbar_link(service->ep->dcp, false); dptx->phy_ops.dp.link_rate = phy_link_rate; dptx->phy_ops.dp.set_rate = 1; ret = phy_configure(dptx->atcphy, &dptx->phy_ops); @@ -983,7 +984,7 @@ static int dptxport_prepare(struct apple_epic_service *service, const char *name return dptx_service_bind(service, &dcp->dptxport[unit], unit); } - if (dcp_fw_is_14_7(dcp) && name && + if ((dcp_fw_is_14_7(dcp) || dcp_is_ext26(dcp)) && name && (!strcmp(name, "dcpdptx-port-epic") || !strcmp(name, "dcp-lpdptx-port-epic")) && (!class || !class[0] || !strcmp(class, "AppleDCPDPTXRemotePort"))) @@ -1009,8 +1010,8 @@ static void dptxport_init(struct apple_epic_service *service, const char *name, if (service->ep->dcp->external_native) schedule_work(&service->ep->dcp->external_ready_work); /* Publication may follow the startup wait or the bounded retry window. */ - if (service->ep->dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL && + if ((service->ep->dcp->hw.iomfb_method_profile == + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(service->ep->dcp)) && READ_ONCE(service->ep->dcp->typec_cable_connected) && !READ_ONCE(service->ep->dcp->quiescing) && !READ_ONCE(service->ep->dcp->external_error)) @@ -1018,9 +1019,140 @@ static void dptxport_init(struct apple_epic_service *service, const char *name, &service->ep->dcp->typec_reconnect_wq, 0); } +/* Native26 USB-C callbacks use the current Type-C PHY without changing its mode. */ +static int dptxport_native26_call(struct apple_epic_service *service, u16 group, u32 command, + const void *data, size_t size, void *reply, size_t room) +{ + struct dptx_port *port = service->cookie; + struct apple_dcp *dcp = service->ep->dcp; + union phy_configure_opts capacity = {}; + int ret; + + if (group == 1 && (command == 3 || command == 4)) + return dptx_j700_training(service, command == 3, data, size, reply, room); + if (group || (size && !data)) + return -EOPNOTSUPP; + memset(reply, 0, room); + memcpy(reply, data, min(size, room)); + if (room >= 4) + put_unaligned_le32(0, reply); + switch (command) { + case DPTX_APCALL_ACTIVATE: + if (!port->atcphy) + return -ENODEV; + ret = phy_validate(port->atcphy, PHY_MODE_DP, 0, &capacity); + if (ret || capacity.dp.lanes < 2) + return ret ?: -EINVAL; + return dcp_direct_crossbar_link(dcp, true); + case DPTX_APCALL_DEACTIVATE: + return dcp_direct_crossbar_link(dcp, false); + case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG: + memset(reply, 0, room); + return dcp_direct_crossbar_link(dcp, false); + case DPTX_APCALL_DID_CHANGE_LINK_CONFIG: + memset(reply, 0, room); + mdelay(10); + return port->link_rate ? dcp_direct_crossbar_link(dcp, true) : 0; + case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS: + if (room < 28) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(3, reply + 20); + put_unaligned_le32(3, reply + 24); + return 0; + case DPTX_APCALL_SET_DRIVE_SETTINGS: + return -EOPNOTSUPP; + case DPTX_APCALL_GET_DRIVE_SETTINGS: + return dptx_j700_training(service, false, data, size, reply, room); + case DPTX_APCALL_GET_MAX_LINK_RATE: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(LINK_RATE_HBR2, reply + 16); + return 0; + case DPTX_APCALL_GET_LINK_RATE: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(port->link_rate, reply + 16); + return 0; + case DPTX_APCALL_SET_LINK_RATE: + if (size < 32 || room < 32) + return -EINVAL; + switch (get_unaligned_le32(data + 16)) { + case 0: + case LINK_RATE_RBR: + case LINK_RATE_HBR: + case LINK_RATE_HBR2: + case LINK_RATE_HBR3: + break; + default: + return -EINVAL; + } + ret = dcp_direct_crossbar_link(dcp, false); + if (ret) + return ret; + return dptxport_call_set_link_rate(service, data, size, reply, room); + case DPTX_APCALL_GET_MAX_LANE_COUNT: + if (room < 32 || !port->atcphy) + return -EINVAL; + ret = phy_validate(port->atcphy, PHY_MODE_DP, 0, &capacity); + if (ret || capacity.dp.lanes < 2 || capacity.dp.lanes > 4) + return ret ?: -EINVAL; + port->native26_max_lanes = capacity.dp.lanes; + memset(reply, 0, room); + put_unaligned_le64(capacity.dp.lanes, reply + 16); + return 0; + case DPTX_APCALL_SET_ACTIVE_LANE_COUNT: { + u64 lanes; + + if (size < 32 || room < 32) + return -EINVAL; + lanes = get_unaligned_le64(data + 16); + ret = dptx_j700_validate_lanes(port, lanes); + if (ret) + return ret; + port->lane_count = lanes; + put_unaligned_le64(lanes, reply + 16); + if (lanes) + complete(&port->linkcfg_completion); + return 0; + } + case DPTX_APCALL_GET_ACTIVE_LANE_COUNT: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le64(port->lane_count, reply + 16); + return 0; + case DPTX_APCALL_GET_SUPPORTS_HPD: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + put_unaligned_le32(1, reply + 16); + return 0; + case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD: + if (room < 32) + return -EINVAL; + memset(reply, 0, room); + return 0; + case DPTX_APCALL_SET_TILED_DISPLAY_HINTS: + if (room < 4) + return -EINVAL; + put_unaligned_le32(1, reply); + return 0; + case DPTX_APCALL_GET_VIRTUAL_DEVICE_MODE: + memset(reply, 0, room); + return 0; + default: + return dptxport_call(service, command, data, size, reply, room); + } +} + static int dptxport_v2_group_call(struct apple_epic_service *service, u16 group, u32 command, const void *data, size_t size, void *reply, size_t room) { + if (dcp_is_ext26(service->ep->dcp)) + return dptxport_native26_call(service, group, command, data, size, reply, room); if (group == 1 && command == DPTX_APCALL_SET_DRIVE_SETTINGS) return dptx_j700_training(service, true, data, size, reply, room); if (group == 1 && command == DPTX_APCALL_GET_DRIVE_SETTINGS) @@ -1238,8 +1370,61 @@ static void dptx_controller_service_init(struct apple_epic_service *service, const char *name, const char *class, s64 unit) { complete(&service->ep->dcp->dptx_controller_ready); + if (dcp_is_ext26(service->ep->dcp) && + dptx_native_controllers_ready(service->ep->dcp)) { + dev_dbg(service->ep->dcp->dev, + "native26 all four controller interfaces OPEN; port OPEN enabled\n"); + afk_service_retry(service->ep->dcp->dptxep); + } +} + +bool dptx_native_controllers_ready(struct apple_dcp *dcp) +{ + struct apple_dcp_afkep *ep = READ_ONCE(dcp->dptx_controller_ep); + unsigned long flags, mask = 0; + u32 i; + + if (IS_ERR_OR_NULL(ep)) + return false; + for (i = 0; i < min_t(u32, READ_ONCE(ep->num_channels), AFK_MAX_CHANNEL); i++) { + struct apple_epic_service *service = &ep->services[i]; + + spin_lock_irqsave(&service->lock, flags); + if (service->enabled && !service->torndown) + mask |= (unsigned long)service->cookie; + spin_unlock_irqrestore(&service->lock, flags); + } + return mask == 0xf; +} + +static int dptx_native_controller_prepare(struct apple_epic_service *service, + const char *name, const char *class, s64 unit) +{ + unsigned int kind; + + if (!name || !class || strcmp(class, "AppleDCPDPTXController") || + unit < 0 || unit > 1) + return -EINVAL; + if (!strcmp(name, "dcpav-controller-epic")) + kind = 0; + else if (!strcmp(name, "dcpdp-controller-epic")) + kind = 1; + else + return -EINVAL; + /* A bit identifies each unit's AV and DP interface, independently of channel. */ + service->cookie = (void *)(unsigned long)BIT(unit * 2 + kind); + return 0; } +static const struct apple_epic_service_ops dptx_native_controller_ops[] = { + { + .name = "AppleDCPDPTXController", + .prepare = dptx_native_controller_prepare, + .init = dptx_controller_service_init, + }, + {} +}; + static const struct apple_epic_service_ops dptx_controller_ops[] = { { .name = "AppleDCPDPTXController", @@ -1254,17 +1439,20 @@ int dptx_controller_init(struct apple_dcp *dcp) int ret; init_completion(&dcp->dptx_controller_ready); - ep = afk_init(dcp, DPTX_CONTROLLER_ENDPOINT, dptx_controller_ops); + ep = afk_init(dcp, DPTX_CONTROLLER_ENDPOINT, + dcp_is_ext26(dcp) ? dptx_native_controller_ops : dptx_controller_ops); if (IS_ERR(ep)) return PTR_ERR(ep); /* Controller interfaces require the same OPEN handshake as the port. */ - ep->wire_v2 = dcp->hw.iomfb_method_profile == + ep->wire_v2 = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; dcp->dptx_controller_ep = ep; ret = afk_start(ep); if (ret) return ret; + if (dcp_is_ext26(dcp)) + return 0; /* Native OPEN follows A401. */ if (!wait_for_completion_timeout(&dcp->dptx_controller_ready, msecs_to_jiffies(1000))) return -ETIMEDOUT; @@ -1275,7 +1463,7 @@ int dptx_controller_init(struct apple_dcp *dcp) int dptxep_init(struct apple_dcp *dcp) { struct apple_dcp_afkep *ep; - bool wire_v2 = dcp->hw.iomfb_method_profile == + bool wire_v2 = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; int ret; u32 port; @@ -1286,7 +1474,7 @@ int dptxep_init(struct apple_dcp *dcp) init_completion(&dcp->dptxport[0].linkcfg_completion); init_completion(&dcp->dptxport[1].linkcfg_completion); - ep = afk_init(dcp, wire_v2 ? DPTX_ENDPOINT_V2 : DPTX_ENDPOINT, dptxep_ops); + ep = afk_init(dcp, wire_v2 && !dcp_is_ext26(dcp) ? DPTX_ENDPOINT_V2 : DPTX_ENDPOINT, dptxep_ops); if (IS_ERR(ep)) return PTR_ERR(ep); ep->wire_v2 = wire_v2; @@ -1296,6 +1484,8 @@ int dptxep_init(struct apple_dcp *dcp) if (ret) return ret; + if (dcp_is_ext26(dcp)) + return 0; /* Native OPEN follows A401; publication retries discovery. */ for (port = 0; port < dcp->hw.num_dptx_ports; port++) { ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion, timeout); @@ -1306,7 +1496,7 @@ int dptxep_init(struct apple_dcp *dcp) * kIOReturnNoDevice (0xe00002c0). Leave the endpoint * up so a later announce can still bind. */ - if (dcp_fw_is_14_7(dcp)) { + if (dcp_fw_is_14_7(dcp) || dcp_is_ext26(dcp)) { dev_info(dcp->dev, "DPTX port %u not announced yet\n", port); return 0; diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h index 211e72d9855ea..b577d2ec8ba1a 100644 --- a/drivers/gpu/drm/apple/dptxep.h +++ b/drivers/gpu/drm/apple/dptxep.h @@ -60,6 +60,7 @@ struct dptx_port { struct phy *atcphy; struct mux_control *mux; u32 lane_count; + u32 native26_max_lanes; u32 link_rate, pending_link_rate; u32 drive_settings[2]; u32 trained_voltage[4], trained_pre[4]; @@ -84,5 +85,6 @@ int dptxport_set_hpd_timeout(struct apple_epic_service *service, bool hpd, int dptxport_sink_irq(struct apple_epic_service *service); struct apple_dcp; +bool dptx_native_controllers_ready(struct apple_dcp *dcp); int dpav_ctrl_init(struct apple_dcp *dcp); #endif diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c index a92af3295fcf8..d4f8ba9458630 100644 --- a/drivers/gpu/drm/apple/epic/dpavservep.c +++ b/drivers/gpu/drm/apple/epic/dpavservep.c @@ -39,6 +39,9 @@ static bool dcpavserv_identity(struct apple_epic_service *service, const char *n { if (!name || strcmp(name, "dcpav-service-epic")) return false; + if (dcp_is_ext26(service->ep->dcp)) + return service->ep->endpoint == DPAVSERV_ENDPOINT && unit == 0 && + class && !strcmp(class, "DCPDPService") && service->channel; if (!service->ep->wire_v2) return unit == 0; /* H17P's lower-case interface-id is dynamic, not a physical-port unit. */ @@ -134,6 +137,8 @@ static void dcpavserv_init(struct apple_epic_service *service, const char *name, mutex_unlock(&dcp->dcpavserv.lock); if (v2) mutex_unlock(&dcp->hpd_mutex); + if (published && dcp_is_ext26(dcp) && !dcp->ext26_client_open) + WRITE_ONCE(dcp->ext26_edid_tried, false); if (!published) { /* A generation/registry change during OPEN cannot enable stale work. */ afk_service_retire(service); @@ -579,8 +584,8 @@ static const struct drm_edid *dcpavserv_read_edid_inline(struct apple_epic_servi bool dcpavserv_edid_current(struct apple_dcp *dcp, u64 generation) { lockdep_assert_held(&dcp->hpd_mutex); - return dcp->hw.iomfb_method_profile != - DCP_IOMFB_METHODS_H17P_EXTERNAL || + return (!dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != + DCP_IOMFB_METHODS_H17P_EXTERNAL) || (dcp->typec_cable_connected && generation == dcp->typec_generation && !READ_ONCE(dcp->quiescing) && !READ_ONCE(dcp->external_physically_absent)); } @@ -590,7 +595,7 @@ bool dcpavserv_edid_service_current(struct apple_dcp *dcp, u64 epoch) lockdep_assert_held(&dcp->dcpavserv.lock); return dcp->dcpavserv.enabled && dcp->dcpavserv.service && epoch == dcp->dcpavserv.epoch && - (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL || + ((!dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) || !READ_ONCE(dcp->external_physically_absent)); } @@ -653,7 +658,7 @@ const struct drm_edid *dcpavserv_copy_edid(struct apple_dcp *dcp, u64 generation edid = service->ep->wire_v2 ? dcpavserv_read_edid_inline(service) : dcpavserv_read_edid_legacy(service); /* The 14.7 external role: every block's header and checksum. */ - if (v14_7_ext && !IS_ERR(edid) && !drm_edid_valid(edid)) { + if ((v14_7_ext || dcp_is_ext26(dcp)) && !IS_ERR(edid) && !drm_edid_valid(edid)) { dev_warn(dcp->dev, "DCPAVSERV: the sink's EDID is not valid\n"); drm_edid_free(edid); edid = ERR_PTR(-EPROTO); @@ -712,7 +717,7 @@ int dpavservep_init(struct apple_dcp *dcp) if (IS_ERR(ep)) return PTR_ERR(ep); /* External H17P uses AFKV2 service calls with host-owned OOB buffers. */ - ep->wire_v2 = dcp->hw.iomfb_method_profile == + ep->wire_v2 = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; ep->inline_v2 = false; dcp->dcpavservep = ep; diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index f6a2df3ec55a4..90686dd41a509 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -39,6 +39,7 @@ #include "iomfb.h" #include "iomfb_internal.h" #include "iomfb_m4.h" +#include "dcpext26-lifecycle.h" #include "parser.h" #include "trace.h" @@ -276,6 +277,8 @@ static u8 dcp_pop_depth(u8 *depth) } /* Older firmware and the H17G method profile retain their existing transport. */ +static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout); + static bool iomfb_uses_queue(struct apple_dcp *dcp) { return dcp->fw_compat == DCP_FIRMWARE_H17P && @@ -311,7 +314,7 @@ static int iomfb_enqueue_opaque_x(struct apple_dcp *dcp) lockdep_assert_held(&dcp->iomfb.lock); /* DCPEXT rejects enableGPAlphaDiv; retain the panel-only workaround. */ if (dcp->hw.iomfb_method_profile == - DCP_IOMFB_METHODS_H17P_EXTERNAL) + DCP_IOMFB_METHODS_H17P_EXTERNAL || dcp_is_ext26(dcp)) return 0; if (atomic_read(&dcp->iomfb.opaque_x_state) != IOMFB_OPAQUE_X_NEEDED) return 0; @@ -357,7 +360,8 @@ static void iomfb_queue_advance(struct apple_dcp *dcp) /* The active operation may still be visible to firmware. */ return; } - if (!iomfb_channels_idle(dcp) || dcp->present_state_h17p.pending) + if (!iomfb_channels_idle(dcp) || dcp->present_state_h17p.pending || + dcp->ext26_control_pending) return; if (transaction) { @@ -762,6 +766,8 @@ void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *ca if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17G && call->tag_h17g[0]) memcpy(resolved.tag, call->tag_h17g, sizeof(resolved.tag)); + if (dcp_is_ext26(dcp) && call->tag_ext26[0]) + memcpy(resolved.tag, call->tag_ext26, sizeof(resolved.tag)); call = &resolved; enum dcp_context_id context = dcp_call_context(dcp, oob); @@ -802,9 +808,9 @@ void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *ca if (in_len > 0) memcpy(out_data, data, in_len); - /* An unwritten status must not look like a successful response. */ + /* Native26 Boolean replies may leave their padding unwritten. */ if (out_len) - memset(out_data + in_len, 0xff, out_len); + memset(out_data + in_len, dcp_is_ext26(dcp) ? 0 : 0xff, out_len); trace_iomfb_push(dcp, call, context, offset, depth); @@ -814,8 +820,19 @@ void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *ca ch->header[depth] = header; ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT); + if (dcp_is_ext26(dcp)) + dma_wmb(); dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_msg(context, data_len, offset)); + /* Only a successful transport send can establish the readiness boundary. */ + if (dcp_is_ext26(dcp) && !memcmp(call->tag, "A473", 4) && in_len == 16 && + get_unaligned_le64(data) == 1) { + bool sent = !READ_ONCE(dcp->crashed) && !READ_ONCE(dcp->quiescing) && + !READ_ONCE(dcp->ext26_blocked); + + WRITE_ONCE(dcp->ext26_power_ns, sent ? ktime_get_ns() : 0); + WRITE_ONCE(dcp->ext26_power_sent, sent); + } } /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */ @@ -843,6 +860,8 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context) struct dcp_channel *ch = dcp_get_channel(dcp, context); dcp_pop_depth(&ch->depth); + if (dcp_is_ext26(dcp)) + dma_wmb(); dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_ack(context)); } @@ -1463,6 +1482,13 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context, dcp_trace_panel_setter(dcp, tag, in, hdr->in_len); if (!handled) { + if (dcp_is_ext26(dcp)) { + dev_err(dev, "native26 unsupported callback D%03d (%u/%u); session retained\n", + tag, hdr->in_len, hdr->out_len); + WRITE_ONCE(dcp->crashed, true); + complete(&dcp->start_done); + return; + } zero_reply = dcp_external_zero_reply(dcp, tag, context, hdr->in_len, hdr->out_len); if (!zero_reply && @@ -1548,15 +1574,16 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context) * acked on the callback context, whose receive area holds the callback * record rather than the reply, so the ack cannot be used to find it. * - * On older firmware the lengths in the record header must still be - * the ones that were sent; otherwise the record was overwritten and - * its output cannot be trusted. The tag is left out of the check: it - * has only been seen preserved on 13.5 firmware. + * Native26 preserves the command header, including its tag. Validate it + * before using the reply. Older firmware keeps its length-only check; + * other H17 profiles retain their existing admission policy. */ - if (dcp->fw_compat != DCP_FIRMWARE_H17P) { + if (dcp->fw_compat != DCP_FIRMWARE_H17P || dcp_is_ext26(dcp)) { record = out - sent->in_len - sizeof(*record); if (record->in_len != sent->in_len || - record->out_len != sent->out_len) { + record->out_len != sent->out_len || + (dcp_is_ext26(dcp) && + memcmp(record->tag, sent->tag, sizeof(record->tag)))) { dev_err(dcp->dev, "corrupt %c%c%c%c command record on context %X\n", sent->tag[3], sent->tag[2], sent->tag[1], @@ -1595,6 +1622,8 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message) } data = dcp->shmem + channel_offset + offset; + if (dcp_is_ext26(dcp)) + dma_rmb(); if (FIELD_GET(IOMFB_MSG_ACK, message)) dcpep_handle_ack(dcp, ctx_id); @@ -1990,7 +2019,7 @@ int dcp_get_modes(struct drm_connector *connector) } modes = dcp_modes_snapshot(dcp, apple_connector, &nr_modes); - physical_edid = dcp->hw.iomfb_method_profile == + physical_edid = dcp_is_ext26(dcp) || dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL; /* get_modes holds mode_config.mutex; HPD paths never acquire it. */ if (physical_edid) { @@ -2013,6 +2042,8 @@ int dcp_get_modes(struct drm_connector *connector) /* Internal H17P still has no supported EDID command transport. */ if (nr_modes && !apple_connector->drm_edid && + (!dcp_is_ext26(dcp) || !dcp->ext26_edid_tried || + dcp->ext26_edid_generation != edid_generation) && (dcp->fw_compat != DCP_FIRMWARE_H17P || (dcp->dcpavservep && dcp->dcpavservep->wire_v2))) { const struct drm_edid *edid; @@ -2020,6 +2051,10 @@ int dcp_get_modes(struct drm_connector *connector) mutex_unlock(&dcp->dcpavserv.lock); if (physical_edid) mutex_unlock(&dcp->hpd_mutex); + if (dcp_is_ext26(dcp)) { + dcp->ext26_edid_tried = true; + dcp->ext26_edid_generation = edid_generation; + } edid = dcpavserv_copy_edid(dcp, edid_generation, &service_epoch); if (physical_edid) mutex_lock(&dcp->hpd_mutex); @@ -2042,6 +2077,15 @@ int dcp_get_modes(struct drm_connector *connector) /* Retirement may have queued cleanup while this request waited. */ dcp_consume_edid_invalidation(apple_connector); if (IS_ERR_OR_NULL(edid)) { + if (dcp_is_ext26(dcp) && IS_ERR(edid) && + PTR_ERR(edid) != -ENODEV && PTR_ERR(edid) != -ESTALE) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + apple_connector->connected = false; + nr_modes = 0; + dev_err(dcp->dev, "native26 initial EDID discovery failed: %pe; session retained\n", edid); + goto edid_done; + } /* * There is no AV service for an internal panel, nor * for an external display while its link is down; @@ -2105,6 +2149,13 @@ int dcp_get_modes(struct drm_connector *connector) } edid_done: + /* Discovery can replace the catalogue while the OOB read waits. */ + if (dcp_is_ext26(dcp)) { + if (READ_ONCE(dcp->crashed) || READ_ONCE(dcp->ext26_blocked)) + nr_modes = 0; + else + modes = dcp_modes_snapshot(dcp, apple_connector, &nr_modes); + } /* Publish after a possibly delayed raw read, under its physical generation. */ for (i = 0; i < nr_modes; ++i) { mode = drm_mode_duplicate(dev, &modes[i].mode); @@ -2601,7 +2652,7 @@ static bool dcp_mode_settable(struct apple_dcp *dcp, if (!lookup_mode(dcp, mode, &dcp_mode)) return false; - if (dcp->fw_compat == DCP_FIRMWARE_H17P && + if (dcp->fw_compat == DCP_FIRMWARE_H17P && !dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) return dcp_mode.mode.type & DRM_MODE_TYPE_PREFERRED; @@ -2654,7 +2705,7 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, /* Disconnect cannot pass the admission check before these RPCs finish. */ guard(mutex)(&dcp->external_lock); - if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL && + if (dcp_is_retained_external(dcp) && (!dcp->external_running || dcp->external_error || READ_ONCE(dcp->quiescing) || READ_ONCE(dcp->crashed) || READ_ONCE(dcp->iomfb.presentation_paused))) { /* @@ -2712,7 +2763,8 @@ int dcp_crtc_atomic_modeset(struct drm_crtc *crtc, ret = iomfb_v14_7_modeset(dcp, crtc_state); break; case DCP_FIRMWARE_H17P: - ret = iomfb_modeset_h17p(dcp, crtc_state); + ret = iomfb_modeset_h17p(dcp, crtc_state, + drm_atomic_get_new_plane_state(commit, crtc->primary)); break; default: WARN_ONCE(true, "Unexpected firmware version: %u\n", @@ -3060,6 +3112,12 @@ VISIBLE_IF_KUNIT void iomfb_scanout_put_h17p(struct apple_dcp *dcp, return; if (dcp->iomfb.next_scanout == scanout) dcp->iomfb.next_scanout = NULL; + if (dcp_is_ext26(dcp)) { + /* An unsuccessful present can still have exposed its IOVAs. */ + list_add_tail(&scanout->link, &dcp->ext26_retained); + dcp->ext26_retained_bytes += ext26_scanout_bytes(scanout); + return; + } iomfb_scanout_release_h17p(scanout); } @@ -3350,7 +3408,12 @@ static void iomfb_recv_message(struct apple_dcp *dcp, u64 message) "IOMFB: init complete %#llx (version %llu)\n", message, FIELD_GET(GENMASK_ULL(63, 48), message)); - iomfb_start(dcp); + if (dcp_is_ext26(dcp)) { + dev_dbg(dcp->dev, "native26 DCPLink ready before AFK startup\n"); + complete_all(&dcp->ext26_link_ready); + } else { + iomfb_start(dcp); + } } else if (type == IOMFB_MESSAGE_TYPE_MSG) dcpep_got_msg(dcp, message); else @@ -3373,6 +3436,15 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message) mutex_unlock(&dcp->iomfb.lock); } +void iomfb_ext26_start(struct apple_dcp *dcp) +{ + guard(mutex)(&dcp->iomfb.lock); + WRITE_ONCE(dcp->iomfb.owner, current); + iomfb_start(dcp); + WRITE_ONCE(dcp->iomfb.owner, NULL); + iomfb_queue_advance(dcp); +} + int iomfb_start_rtkit(struct apple_dcp *dcp) { dma_addr_t shmem_iova; @@ -3395,7 +3467,8 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) * display DART faults. */ if (dcp->fw_compat == DCP_FIRMWARE_H17P && - dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) { + dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL && + !dcp_is_ext26(dcp)) { ret = apple_rtkit_start_ep(dcp->rtk, REMOTE_ALLOC_ENDPOINT); if (ret) return ret; @@ -3448,3 +3521,61 @@ void iomfb_shutdown(struct apple_dcp *dcp) #if IS_ENABLED(CONFIG_DRM_APPLE_PARSER_KUNIT_TEST) #include "iomfb-parser-test.c" #endif + +static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout) +{ + u64 bytes = 0; + unsigned int i; + + for (i = 0; i < SWAP_SURFACES; i++) + if (scanout->fb[i] && scanout->fb[i]->obj[0]) + bytes += scanout->fb[i]->obj[0]->size; + return bytes; +} + +/* A release/transport boundary is not a backing-retirement witness. */ +int iomfb_ext26_retain_epoch(struct apple_dcp *dcp) +{ + struct iomfb_scanout_h17p *scanout; + u64 bytes = 0; + + guard(mutex)(&dcp->iomfb.lock); + if (!iomfb_channels_idle(dcp) || dcp->present_state_h17p.pending || + dcp->iomfb.active || dcp->iomfb.next_scanout || dcp->chunks.data || + dcp->ext26_control_pending) + return -EBUSY; + if (dcp->iomfb.scanout) + bytes += ext26_scanout_bytes(dcp->iomfb.scanout); + list_for_each_entry(scanout, &dcp->iomfb.dropped, link) + bytes += ext26_scanout_bytes(scanout); + if (!dcpext26_epoch_fits(dcp->ext26_epochs, dcp->ext26_retained_bytes, bytes)) + return -ENOSPC; + if (dcp->iomfb.scanout) { + list_add_tail(&dcp->iomfb.scanout->link, &dcp->ext26_retained); + dcp->iomfb.scanout = NULL; + } + list_splice_tail_init(&dcp->iomfb.dropped, &dcp->ext26_retained); + dcp->ext26_retained_bytes += bytes; + if (bytes) + dcp->ext26_epochs++; + dcp->ext26_blanked = false; + dcp->ext26_normal = false; + dcp->ext26_early_complete = false; + dev_dbg(dcp->dev, "native26 retained epoch %u: %llu backing bytes; remote/DMA fence unproved\n", + dcp->ext26_epochs, dcp->ext26_retained_bytes); + return 0; +} + +/* A retained old attachment may still address this GEM object. */ +bool iomfb_ext26_backing_fresh(struct apple_dcp *dcp, struct drm_framebuffer *fb) +{ + struct iomfb_scanout_h17p *scanout; + unsigned int i; + + guard(mutex)(&dcp->iomfb.lock); + list_for_each_entry(scanout, &dcp->ext26_retained, link) + for (i = 0; i < SWAP_SURFACES; i++) + if (scanout->fb[i] && scanout->fb[i]->obj[0] == fb->obj[0]) + return false; + return true; +} diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 7fb06f8d5eea2..775387b918edf 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -204,8 +204,13 @@ struct dcp_method_entry { char tag[4]; /* H17G shares the H17P transport but changes selected method IDs. */ char tag_h17g[4]; + char tag_ext26[4]; }; +#define IOMFB_METHOD_EXT26(tag, native, name) [name] = { #name, \ + { tag[0], tag[1], tag[2], tag[3] }, {0}, \ + { native[0], native[1], native[2], native[3] } } + #define IOMFB_MAX_CB (1000) bool iomfb_validate_callback_h17p(int tag, u32 in_len, u32 out_len); diff --git a/drivers/gpu/drm/apple/iomfb_h17p.c b/drivers/gpu/drm/apple/iomfb_h17p.c index 4f13cee71c872..b963642fec05a 100644 --- a/drivers/gpu/drm/apple/iomfb_h17p.c +++ b/drivers/gpu/drm/apple/iomfb_h17p.c @@ -2,9 +2,13 @@ /* Copyright The Asahi Linux Contributors */ #include +#include +#include #include #include "dcp-link.h" +#include "dcpext26-wire.h" +#include "dcpext26-lifecycle.h" #include "iomfb_h17p.h" #include "iomfb_v12_3.h" #include "iomfb_v13_3.h" @@ -196,11 +200,12 @@ void iomfb_encode_backlight_h17p(struct dcp_present_h17p *wire, u32 nits, static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A000", dcpep_late_init_signal), - IOMFB_METHOD_H17("A025", "A029", dcpep_setup_video_limits), /* (0,0) */ + [dcpep_setup_video_limits] = { "dcpep_setup_video_limits", + {'A', '0', '2', '5'}, {'A', '0', '2', '9'}, {'A', '0', '2', '9'} }, /* (0,0) */ IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched), /* nested in D206 */ IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched), /* nested in D207 */ - IOMFB_METHOD("A385", dcpep_set_create_dfb), /* first call inside D121; (0,0) */ - IOMFB_METHOD("A386", iomfbep_a358_vi_set_temperature_hint), /* nested in D100 */ + IOMFB_METHOD_EXT26("A385", "A389", dcpep_set_create_dfb), /* first call inside D121; (0,0) */ + IOMFB_METHOD_EXT26("A386", "A390", iomfbep_a358_vi_set_temperature_hint), /* nested in D100 */ IOMFB_METHOD("A401", dcpep_start_signal), IOMFB_METHOD("A406", dcpep_swap_start), /* @@ -208,7 +213,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { * with it: every swap is A406 followed by A408, A407 is unused, and the * A408 response is 0x0c bytes (dcp_swap_submit_resp_h17p). */ - IOMFB_METHOD("A408", dcpep_swap_submit), + IOMFB_METHOD_EXT26("A408", "A407", dcpep_swap_submit), IOMFB_METHOD("A411", dcpep_set_display_device), IOMFB_METHOD("A412", dcpep_is_main_display), IOMFB_METHOD("A413", dcpep_set_digital_out_mode), @@ -253,6 +258,13 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { #define DCP_FW h17p #define DCP_FW_VER DCP_FW_VERSION(26, 6, 0) +static int ext26_encode(struct apple_dcp *dcp, struct dcp_swap_submit_req_h17p *request); +static int ext26_activate(struct apple_dcp *dcp); +static int ext26_open_client(struct apple_dcp *dcp); +static u8 ext26_prop_start(struct apple_dcp *dcp, u32 advertised); +static void ext26_start(struct apple_dcp *dcp); +static bool ext26_abort(struct apple_dcp *dcp, int tag, void *out, void *input); + #include "iomfb_template.c" void iomfb_apply_opaque_x_h17p(struct apple_dcp *dcp, dcp_callback_t callback, @@ -699,10 +711,465 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [599] = trampoline_nop, /* find_swap_function_gated */ }; +/* Included with the public H17P template helpers in scope. */ + +static bool ext26_callback_size(int tag, u32 input, u32 output) +{ + switch (tag) { + case 3: return input == 4 && output == 60; + case 6: return input == 84 && output == 80; + case 107: return input == 64 && !output; + case 115: return input == 16 && output == 8; + case 116: return input == 16 && output == 4; + case 134: return !input && output == 4; + case 408: return input == 8 && output == 8; + case 454: return input == 4 && output == 4; + case 567: return input == 128 && output == 4; + case 572: return input == 4 && output == 4; + case 576: return input == 4 && !output; + case 588: return !input && !output; + default: return iomfb_validate_callback_h17p(tag, input, output); + } +} + +static u8 ext26_prop_start(struct apple_dcp *dcp, u32 advertised) +{ + struct dcp_chunks *chunks = &dcp->chunks; + + if (!advertised || advertised > SZ_1M + 1) + return false; + kfree(chunks->data); + memset(chunks, 0, sizeof(*chunks)); + chunks->length = advertised - 1; + chunks->contiguous = true; + chunks->modes_generation = dcp_modes_transfer_begin(dcp); + dcp_firmware_metadata_begin(dcp); + chunks->data = kzalloc(max_t(size_t, 1, chunks->length), GFP_KERNEL); + return !!chunks->data; +} + +struct ext26_boot { + unsigned int completed; + u32 one; +}; + +static void ext26_boot_next(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct ext26_boot *boot = cookie; + const struct dcp_method_entry *method; + u32 in = 0, reply = 0; + + if (READ_ONCE(dcp->crashed) || (boot->completed && !out) || + ((boot->completed == 5 || boot->completed == 6) && + get_unaligned_le32(out) != 1)) { + WRITE_ONCE(dcp->crashed, true); + dev_err(dcp->dev, + "native26 D121 startup refused: completed=%u last=%s reply=%#x present=%u; session retained\n", + boot->completed, boot->completed == 5 ? "A000" : + boot->completed == 6 ? "A465" : "earlier call", + out && (boot->completed == 2 || boot->completed == 5 || + boot->completed == 6) ? get_unaligned_le32(out) : 0, !!out); + kfree(boot); + return; + } + if (boot->completed == 5 || boot->completed == 6) + dev_dbg(dcp->dev, "native26 D121 %s reply=%#x accepted\n", + boot->completed == 5 ? "A000" : "A465", get_unaligned_le32(out)); + switch (boot->completed++) { + case 0: + method = &dcp_methods[dcpep_set_create_dfb]; + break; + case 1: + method = &dcp_methods[dcpep_create_default_fb]; + reply = 4; + break; + case 2: + method = &dcp_methods[dcpep_setup_video_limits]; + break; + case 3: + method = &dcp_methods[dcpep_flush_supports_power]; + in = 4; + break; + case 4: + method = &dcp_methods[dcpep_late_init_signal]; + in = reply = 4; + break; + case 5: + method = &dcp_methods[dcpep_set_display_refresh_properties]; + reply = 4; + break; + default: + kfree(boot); + boot_done(dcp, NULL, NULL); + dev_dbg(dcp->dev, "native26 D121 startup complete\n"); + return; + } + dcp_push(dcp, false, method, in, reply, &boot->one, ext26_boot_next, boot); +} + +static bool ext26_boot_callback(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + struct ext26_boot *boot = kzalloc_obj(*boot); + + if (!boot) { + WRITE_ONCE(dcp->crashed, true); + return false; + } + boot->one = 1; + ext26_boot_next(dcp, NULL, boot); + return false; +} + +static bool ext26_property(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + if (tag == 107 && (!strncmp((const char *)in, "TimingElements", 64) || + !strncmp((const char *)in, "DisplayAttributes", 64))) { + dcp_modes_begin_attachment(dcp); + if (dcp->connector) { + dcp->connector->connected = false; + apple_connector_invalidate_edid(dcp->connector); + dcp_queue_hotplug(dcp->connector); + } + } + if (memcmp(in, "NormalModeActive", sizeof("NormalModeActive"))) + return true; + if (tag == 107) { + WRITE_ONCE(dcp->ext26_normal, false); + return true; + } + bool on = tag == 104 ? !!in[64] : !!get_unaligned_le32(in + 64); + + if (!on) { + WRITE_ONCE(dcp->ext26_normal, false); + return true; + } + if (READ_ONCE(dcp->ext26_power_sent) && + dcp->ext26_rx_ns > READ_ONCE(dcp->ext26_power_ns) && + dcp->ext26_generation == READ_ONCE(dcp->typec_generation)) { + WRITE_ONCE(dcp->ext26_normal, true); + complete_all(&dcp->ext26_ready); + dev_dbg(dcp->dev, "native26 NormalModeActive after power publication\n"); + } + return true; +} + +static bool ext26_bandwidth(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + memset(out, 0, 60); + put_unaligned_le32(1, (u8 *)out + 56); + return true; +} + +static bool ext26_frame_sync(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + memset(out, 0, 80); + if (!(in[80] & 1)) + memcpy(out, in, 80); + return true; +} + +static bool ext26_defaults(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u32 count = get_unaligned_le32(in + 64); + + memset(out, 0, 36); + if (count <= 8) + memcpy(out, in + 68, count * 4); + return true; +} + +static bool ext26_clock(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u32 index = get_unaligned_le32(in + 4); + u64 rate = 0; + + if (!memcmp(in, "VORP", 4) && index < 2) + rate = index ? 24000000 : 48000000; + put_unaligned_le64(rate, out); + return true; +} + +static bool ext26_analytics(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + + memset(out, 0, 4100); + if (memchr(in, 0, 64) && !in[4160]) + memcpy(out, in + 64, 4096); + return true; +} + +static bool ext26_tiling(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + memset(out, 0, 8); + if (((u8 *)input)[12] <= 1) + ((u8 *)out)[4] = 1; + return true; +} + +static bool ext26_release(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + u32 id = get_unaligned_le32(in); + + memset(out, 0, 4); + /* Logical descriptor release never proves DMA retirement. */ + if (id && id < DCP_MAX_MAPPINGS && test_bit(id, dcp->memdesc_map) && + !test_and_set_bit(id, dcp->ext26_released)) + *(u8 *)out = 1; + return true; +} + +static bool ext26_allocate(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 bytes = get_unaligned_le64(in + 4); + u32 alignment = get_unaligned_le32(in + 12); + + memset(out, 0, 28); + if (get_unaligned_le32(in) != 0x703 || !is_power_of_2(alignment) || + alignment > SZ_16K || in[16] || in[17] || in[18] || !bytes || + bytes > SZ_64M || ALIGN(bytes, SZ_16K) > SZ_256M - dcp->ext26_buffer_bytes) + return true; + trampoline_allocate_buffer_h17p(dcp, tag, out, input); + if (get_unaligned_le32((u8 *)out + 24)) + dcp->ext26_buffer_bytes += ALIGN(bytes, SZ_16K); + return true; +} + +static bool ext26_hotplug(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 connected = get_unaligned_le64(in); + + memset(out, 0, 76); + if (!(in[84] & 1)) + memcpy(out, in + 8, 76); + + dcpep_cb_hotplug(dcp, &connected); + return true; +} + +static void ext26_started(struct apple_dcp *dcp, void *out, void *cookie) +{ + if (!out || READ_ONCE(dcp->crashed) || get_unaligned_le32(out) != 1) { + WRITE_ONCE(dcp->crashed, true); + complete_all(&dcp->start_done); + return; + } + dcp_ext26_open_services(dcp); + dcp->main_display = false; + dcp->active = true; + dev_dbg(dcp->dev, "native26 A401 complete; service OPEN and route admission enabled\n"); + complete_all(&dcp->start_done); +} + +static void ext26_client_reply(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + WRITE_ONCE(wait->replied, !!out); + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); +} + +static int ext26_open_client(struct apple_dcp *dcp) +{ + struct dcp_wait_cookie *wait; + int ret; + + if (dcp->ext26_client_open) + return 0; + wait = kzalloc_obj(*wait); + if (!wait) + return -ENOMEM; + init_completion(&wait->done); + kref_init(&wait->refcount); + kref_get(&wait->refcount); + dcp_first_client_open(dcp, false, ext26_client_reply, wait); + ret = wait_for_completion_timeout(&wait->done, msecs_to_jiffies(5000)) && + READ_ONCE(wait->replied) && !READ_ONCE(dcp->crashed) ? 0 : -ETIMEDOUT; + kref_put(&wait->refcount, release_wait_cookie); + if (!ret) { + dcp->ext26_client_open = true; + dev_dbg(dcp->dev, "native26 A457 complete before first mode\n"); + } else { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + } + return ret; +} + +static void ext26_main_done(struct apple_dcp *dcp, void *out, void *cookie) +{ + if (!out) { + WRITE_ONCE(dcp->crashed, true); + return; + } + dcp_ack(dcp, DCP_CONTEXT_CB); +} + +static bool ext26_main_query(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + dcp_is_main_display(dcp, false, ext26_main_done, NULL); + return false; +} + +static bool ext26_stride(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + put_unaligned_le32(dcp->ext26_stride, out); + return true; +} + +static bool ext26_match_backlight(struct apple_dcp *dcp, int tag, void *out, void *in) +{ + iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out); + return false; +} + +static bool ext26_map_piodma(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + const u8 *in = input; + u64 id = get_unaligned_le64(in); + + memset(out, 0, 16); + if (!id || id >= DCP_MAX_MAPPINGS || get_unaligned_le32(in + 8) != 1 || + !test_bit(id, dcp->memdesc_map) || test_bit(id, dcp->ext26_released)) + return true; + if (dcp->memdesc[id].piodma_mapped) { + put_unaligned_le32(id, out); + put_unaligned_le64(dcp->memdesc[id].dva, (u8 *)out + 4); + return true; + } + return trampoline_map_piodma_h17p(dcp, tag, out, input); +} + +static void ext26_start(struct apple_dcp *dcp) +{ + iomfb_cb_handler *handlers = kcalloc(IOMFB_MAX_CB, sizeof(*handlers), GFP_KERNEL); + + if (!handlers) { + WRITE_ONCE(dcp->crashed, true); + complete(&dcp->start_done); + return; + } + memcpy(handlers, cb_handlers, sizeof(cb_handlers)); + handlers[0] = handlers[112] = trampoline_true; + handlers[3] = ext26_bandwidth; + handlers[6] = ext26_frame_sync; + handlers[102] = handlers[104] = handlers[107] = ext26_property; + handlers[114] = ext26_analytics; + handlers[115] = ext26_tiling; + handlers[116] = handlers[134] = trampoline_zero; + handlers[588] = trampoline_nop; + handlers[121] = ext26_boot_callback; + handlers[101] = ext26_stride; + handlers[125] = ext26_defaults; + handlers[201] = ext26_map_piodma; + handlers[207] = ext26_match_backlight; + handlers[130] = handlers[411] = handlers[452] = trampoline_zero; + handlers[202] = handlers[592] = handlers[594] = trampoline_nop; + handlers[400] = handlers[401] = handlers[574] = trampoline_zero; + handlers[408] = ext26_clock; + handlers[451] = ext26_allocate; + handlers[454] = ext26_release; + handlers[575] = ext26_hotplug; + handlers[582] = trampoline_zero; + handlers[593] = ext26_abort; + handlers[599] = ext26_main_query; + dcp->ext26_handlers = handlers; + dcp->cb_handlers = handlers; + dcp_start_signal(dcp, false, ext26_started, NULL); +} + +static int ext26_encode(struct apple_dcp *dcp, struct dcp_swap_submit_req_h17p *request) +{ + struct dcpext26_primary primary, *selected = NULL; + const struct dcp_swap_submit_req_h17p *effective = request; + const struct dcp_surface *surface; + struct iomfb_scanout_h17p *scanout = dcp->iomfb.next_scanout; + struct drm_framebuffer *fb = scanout ? scanout->fb[0] : NULL; + + if (READ_ONCE(dcp->ext26_blocked) || !READ_ONCE(dcp->ext26_normal)) + return -EPIPE; + if (!(request->swap.swap_enabled & BIT(0)) && dcp->iomfb.scanout) + effective = &dcp->iomfb.scanout->request; + surface = &effective->surf[0].base; + if (!effective->surf_null[0]) { + if (!fb || !fb->obj[0]) + return -EINVAL; + primary = (struct dcpext26_primary) { + .address = effective->surf_iova[0], .backing_bytes = fb->obj[0]->size, + .width = surface->width, .height = surface->height, .pitch = surface->stride, + }; + selected = &primary; + } + return dcpext26_primary_encode(dcp->ext26_present, sizeof(dcp->ext26_present), + request->swap.swap_id, 0, selected); +} + +static void ext26_power_reply(struct apple_dcp *dcp, void *out, void *cookie) +{ + struct dcp_wait_cookie *wait = cookie; + + wait->status = out && get_unaligned_le64(out) == 1 ? 0 : ~0U; + complete(&wait->done); + kref_put(&wait->refcount, release_wait_cookie); +} + +static bool ext26_abort(struct apple_dcp *dcp, int tag, void *out, void *input) +{ + u32 id = get_unaligned_le32(input); + + if (iomfb_ext26_control_complete(dcp, id, true)) + return true; + bool ack = trampoline_abort_swap_ap_gated(dcp, tag, out, input); + + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + return ack; +} + +static int ext26_activate(struct apple_dcp *dcp) +{ + struct dcp_set_power_state_req_h17p request = { .unklong = 1 }; + struct dcp_wait_cookie *wait; + int ret; + + wait = kzalloc_obj(*wait); + if (!wait) + return -ENOMEM; + wait->status = ~0U; + init_completion(&wait->done); + kref_init(&wait->refcount); + kref_get(&wait->refcount); + WRITE_ONCE(dcp->external_power_attempted, true); + dcp_set_power_state(dcp, false, &request, ext26_power_reply, wait); + ret = wait_for_completion_timeout(&wait->done, msecs_to_jiffies(5000)); + ret = ret && !READ_ONCE(wait->status) ? 0 : -EIO; + kref_put(&wait->refcount, release_wait_cookie); + if (!ret && !wait_for_completion_timeout(&dcp->ext26_ready, msecs_to_jiffies(5000))) + ret = -ETIMEDOUT; + if (!ret && (!READ_ONCE(dcp->ext26_normal) || READ_ONCE(dcp->crashed) || + READ_ONCE(dcp->ext26_blocked))) + ret = -EIO; + if (!ret) { + WRITE_ONCE(dcp->pipe_enabled_h17p, true); + dev_dbg(dcp->dev, "native26 mode and power ready; primary scanout admitted\n"); + } + return ret; +} + bool iomfb_check_callback_h17p(struct apple_dcp *dcp, int tag, u32 in_len, u32 out_len) { - return iomfb_validate_callback_h17p(tag, in_len, out_len); + return dcp_is_ext26(dcp) ? ext26_callback_size(tag, in_len, out_len) : + iomfb_validate_callback_h17p(tag, in_len, out_len); } static_assert(sizeof(struct dcp_set_power_state_resp) == 8); @@ -785,7 +1252,9 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) init_completion(&cookie->wait.done); kref_init(&cookie->wait.refcount); kref_get(&cookie->wait.refcount); - if (on) { + if (dcp_is_ext26(dcp)) { + dcp_set_power_state(dcp, false, &request, iomfb_external_power_complete, cookie); + } else if (on) { u32 handle = 2; iomfb_opaque_x_reset_h17p(dcp); @@ -793,7 +1262,7 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) } else { dcp_set_power_state(dcp, true, &request, iomfb_external_power_complete, cookie); } - ret = wait_for_completion_timeout(&cookie->wait.done, msecs_to_jiffies(500)); + ret = wait_for_completion_timeout(&cookie->wait.done, msecs_to_jiffies(dcp_is_ext26(dcp) ? 5000 : 500)); ret = ret ? (int)READ_ONCE(cookie->wait.status) : -ETIMEDOUT; kref_put(&cookie->wait.refcount, release_wait_cookie); return ret; @@ -801,6 +1270,10 @@ int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on) void iomfb_start_h17p(struct apple_dcp *dcp) { + if (dcp_is_ext26(dcp)) { + ext26_start(dcp); + return; + } WRITE_ONCE(dcp->pipe_enabled_h17p, false); dcp->cb_handlers = cb_handlers; dcp_start_signal(dcp, false, dcp_started, NULL); @@ -839,3 +1312,107 @@ int iomfb_h17p_deliver_swap_complete(struct apple_dcp *dcp, u32 swap_id) #undef DCP_FW_VER #undef DCP_FW + +struct ext26_control { + struct dcpext26_exchange exchange; + struct completion done; + u8 wire[DCPEXT26_SUBMIT_BYTES]; +}; + +static void ext26_control_signal(struct apple_dcp *dcp, struct ext26_control *control) +{ + if (dcpext26_exchange_finished(&control->exchange)) { + if (control->exchange.failed) { + WRITE_ONCE(dcp->crashed, true); + WRITE_ONCE(dcp->ext26_blocked, true); + } + dcp->ext26_control_pending = false; + complete_all(&control->done); + } +} + +bool iomfb_ext26_control_complete(struct apple_dcp *dcp, u32 id, bool aborted) +{ + struct ext26_control *control = dcp->ext26_control; + + if (!dcp_is_ext26(dcp)) + return false; + if (id && id == dcp->ext26_control_id) + return true; + if (!control || !dcpext26_exchange_complete(&control->exchange, id)) + return false; + if (aborted) + control->exchange.failed = true; + ext26_control_signal(dcp, control); + return true; +} + +static void ext26_null_submitted(struct apple_dcp *dcp, void *reply, void *cookie) +{ + struct ext26_control *control = cookie; + + control->exchange.replied = true; + if (dcpext26_submit_reply(reply, 12)) + control->exchange.failed = true; + ext26_control_signal(dcp, control); +} + +static void ext26_null_started(struct apple_dcp *dcp, void *reply, void *cookie) +{ + struct ext26_control *control = cookie; + u32 id; + + if (dcpext26_start_reply(reply, 8, &id) || + !dcpext26_exchange_begin(&control->exchange, id) || + dcpext26_primary_encode(control->wire, sizeof(control->wire), id, 0, NULL)) { + control->exchange.failed = true; + ext26_control_signal(dcp, control); + return; + } + dcp_push(dcp, false, &dcp_methods[dcpep_swap_submit], sizeof(control->wire), 12, + control->wire, ext26_null_submitted, control); +} + +/* Called by the lifecycle worker, never from RX or the shared DRM tail. */ +int iomfb_ext26_blank(struct apple_dcp *dcp) +{ + struct ext26_control *control; + u8 start[16] = {}; + int ret; + + ret = iomfb_queue_settle(dcp, msecs_to_jiffies(5000)); + if (ret) + return ret; + control = kzalloc_obj(*control); + if (!control) + return -ENOMEM; + init_completion(&control->done); + mutex_lock(&dcp->iomfb.lock); + if (dcp->ext26_control || dcp->present_state_h17p.pending || dcp->crashed) { + mutex_unlock(&dcp->iomfb.lock); + kfree(control); + return -EBUSY; + } + dcp->ext26_control = control; + dcp->ext26_control_pending = true; + mutex_unlock(&dcp->iomfb.lock); + dcp_push(dcp, false, &dcp_methods[dcpep_swap_start], sizeof(start), 8, + start, ext26_null_started, control); + ret = wait_for_completion_timeout(&control->done, msecs_to_jiffies(5000)); + /* RX no longer owns a successful control before its storage is reclaimed. */ + if (ret) + apple_rtkit_flush_rx(dcp->rtk); + mutex_lock(&dcp->iomfb.lock); + if (!ret || control->exchange.failed || dcp->crashed) { + WRITE_ONCE(dcp->crashed, true); + ret = -EIO; + /* Possibly published control storage remains owned until reset. */ + } else { + dcp->ext26_control_id = control->exchange.id; + dcp->ext26_control = NULL; + kfree(control); + ret = 0; + } + mutex_unlock(&dcp->iomfb.lock); + return ret; +} diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h index b34a1171123e3..09e67dd85a967 100644 --- a/drivers/gpu/drm/apple/iomfb_internal.h +++ b/drivers/gpu/drm/apple/iomfb_internal.h @@ -118,3 +118,7 @@ u8 dcp_crtc_eotf(struct apple_dcp *dcp, struct drm_crtc *crtc, int dcp_crtc_check_hdr(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *commit); void dcp_hdr_metadata_forget(struct apple_dcp *dcp); +int iomfb_ext26_blank(struct apple_dcp *dcp); +bool iomfb_ext26_backing_fresh(struct apple_dcp *dcp, struct drm_framebuffer *fb); +int iomfb_ext26_retain_epoch(struct apple_dcp *dcp); +bool iomfb_ext26_control_complete(struct apple_dcp *dcp, u32 id, bool aborted); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 2d7268f84e290..afaffd1c4dd25 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -359,6 +359,18 @@ static void dcp_swap_submit(struct apple_dcp *dcp, bool oob, { const void *data = request; + if (dcp_is_ext26(dcp)) { + if (ext26_encode(dcp, request)) { + WRITE_ONCE(dcp->crashed, true); + if (cb) + cb(dcp, NULL, cookie); + return; + } + dcp_push(dcp, oob, &dcp_methods[dcpep_swap_submit], 0x1bd8, 12, + dcp->ext26_present, cb, cookie); + return; + } + if (dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17G) { iomfb_serialize_present_h17p(&dcp->present_h17p, request); if (!iomfb_apply_backlight_h17p(dcp, request, &dcp->present_h17p)) { @@ -600,8 +612,22 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp) static void dcpep_cb_swap_complete(struct apple_dcp *dcp, struct DCP_FW_NAME(dc_swap_complete_resp) *resp) { - ktime_t now = ktime_get(); + ktime_t now = dcp_is_ext26(dcp) ? ns_to_ktime(dcp->ext26_rx_ns) : ktime_get(); bool retire = true; + if (dcp_is_ext26(dcp)) { + if (iomfb_ext26_control_complete(dcp, resp->swap_id, false)) + return; + if (resp->swap_id == dcp->last_swap_id) + return; + if (dcp->present_state_h17p.pending && !dcp->present_state_h17p.accepted && + resp->swap_id == dcp->present_state_h17p.swap_id) { + dcp->ext26_early_complete = true; + dcp->ext26_early_id = resp->swap_id; + dcp->ext26_early_ns = dcp->ext26_rx_ns; + return; + } + } + #if DCP_FW_DESKTOP_26(DCP_FW_VER) if (dcp_peer_swap_complete_26_6(dcp, resp->swap_id)) @@ -1071,15 +1097,27 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, return resp; } - allocated.buf = dma_alloc_coherent(dcp->dev, size, &allocated.dva, - GFP_KERNEL); + if (dcp_is_ext26(dcp)) { + allocated.size = size; + allocated.buf = dma_alloc_attrs(dcp->dev, size, &allocated.dva, + GFP_KERNEL, DMA_ATTR_FORCE_CONTIGUOUS); + } else { + allocated.buf = dma_alloc_coherent(dcp->dev, size, &allocated.dva, GFP_KERNEL); + } if (!allocated.buf) return resp; ret = dma_get_sgtable(dcp->dev, &allocated.map, allocated.buf, allocated.dva, size); - if (ret) { - dma_free_coherent(dcp->dev, size, allocated.buf, allocated.dva); + if (ret || (dcp_is_ext26(dcp) && (allocated.map.orig_nents != 1 || + ((sg_phys(allocated.map.sgl) | allocated.dva) & (SZ_16K - 1))))) { + if (!ret) + sg_free_table(&allocated.map); + if (dcp_is_ext26(dcp)) + dma_free_attrs(dcp->dev, size, allocated.buf, allocated.dva, + DMA_ATTR_FORCE_CONTIGUOUS); + else + dma_free_coherent(dcp->dev, size, allocated.buf, allocated.dva); return resp; } @@ -1349,6 +1387,10 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + if (dcp_is_ext26(dcp)) + return ext26_prop_start(dcp, *length); +#endif if (dcp->chunks.data != NULL) { dev_warn(dcp->dev, "ignoring spurious transfer start\n"); return false; @@ -1421,8 +1463,11 @@ static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp, return false; } - if (req->offset != dcp->chunks.received) + if (req->offset != dcp->chunks.received) { dcp->chunks.contiguous = false; + if (dcp_is_ext26(dcp)) + return false; + } if (dcp->chunks.contiguous) dcp->chunks.received += req->length; memcpy(dcp->chunks.data + req->offset, req->data, req->length); @@ -1455,6 +1500,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp, if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && (!strcmp(req->key, "EDID") || !strcmp(req->key, "edid"))) return true; + if (dcp_is_ext26(dcp) && (!dcp->chunks.contiguous || + dcp->chunks.received != dcp->chunks.length)) + return false; ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx); if (ret) { @@ -2022,6 +2070,14 @@ static void dcp_prepare_clear_swap(struct apple_dcp *dcp, void *request) #endif } +static void dcp_present_rejected(struct apple_dcp *dcp) +{ + if (dcp_is_ext26(dcp)) { + WRITE_ONCE(dcp->ext26_blocked, true); + WRITE_ONCE(dcp->crashed, true); + } +} + static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data; @@ -2041,6 +2097,8 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie) #endif if (status) { + dcp_present_rejected(dcp); + dcp->ext26_early_complete = false; /* An aborted present has already been reported. */ if (ret != -ECANCELED) dev_err(dcp->dev, "swap_clear failed! status %u\n", status); @@ -2092,7 +2150,8 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data, if (info) info->swap_id = resp->swap_id; - dcp_h17p_prepare_swap(dcp); + if (!dcp_is_ext26(dcp)) + dcp_h17p_prepare_swap(dcp); dcp_swap_submit(dcp, false, &DCP_FW_SWAP(dcp), dcp_swap_cleared, cookie); } @@ -2200,6 +2259,11 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) u32 handle; #endif dev_info(dcp->dev, "dcp_poweron() starting\n"); +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + if (dcp_is_ext26(dcp)) + return; /* Native26 publishes A473 only after a successful A413. */ +#endif + /* The firmware announces its compact services again after poweron. */ WRITE_ONCE(dcp->afk_power_gen, dcp->afk_power_gen + 1); @@ -2872,6 +2936,8 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) #endif if (status) { + dcp_present_rejected(dcp); + dcp->ext26_early_complete = false; /* An aborted present has already been reported. */ if (ret != -ECANCELED) dev_err(dcp->dev, "swap failed! status %u\n", status); @@ -2882,6 +2948,17 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie) } return; } + if (dcp_is_ext26(dcp) && dcp->ext26_early_complete && dcp->ext26_early_id == swap_id) { + struct DCP_FW_NAME(dc_swap_complete_resp) completed = { .swap_id = swap_id }; + + u64 received = dcp->ext26_rx_ns; + + dcp->ext26_early_complete = false; + dcp->ext26_rx_ns = dcp->ext26_early_ns; + dcpep_cb_swap_complete(dcp, &completed); + dcp->ext26_rx_ns = received; + return; + } dcp->swap_start = ktime_get(); dcp->swap_submit_timestamp = arch_timer_read_counter(); dcp_swap_watchdog_arm(dcp); @@ -2891,13 +2968,15 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) { struct DCP_FW_NAME(dcp_swap_start_resp) *resp = data; - if (!resp || resp->ret) { + if (!resp || resp->ret || (dcp_is_ext26(dcp) && !resp->swap_id)) { + dcp_present_rejected(dcp); dev_err(dcp->dev, "swap_start was rejected\n"); if (dcp_present_retires(dcp)) dcp_drm_crtc_vblank(dcp->crtc); return; } if (!dcp_present_begin(dcp, resp->swap_id)) { + dcp_present_rejected(dcp); if (dcp_present_retires(dcp)) dcp_drm_crtc_vblank(dcp->crtc); return; @@ -2921,7 +3000,8 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie) } #endif trace_iomfb_swap_submit(dcp, resp->swap_id); - dcp_h17p_prepare_swap(dcp); + if (!dcp_is_ext26(dcp)) + dcp_h17p_prepare_swap(dcp); #if DCP_FW_DESKTOP_26(DCP_FW_VER) dcp_peer_swap_submit_26_6(dcp, &DCP_FW_SWAP(dcp), resp->swap_id); #endif @@ -3117,7 +3197,10 @@ static void dcp_pre_swap_a428(struct apple_dcp *dcp, void *out, void *cookie) { u32 identity = 0x10000; - dcp_pipe_set_428(dcp, false, &identity, dcp_post_swap_a428, cookie); + if (dcp_is_ext26(dcp)) + do_swap(dcp, out, cookie); + else + dcp_pipe_set_428(dcp, false, &identity, dcp_post_swap_a428, cookie); } #endif @@ -3490,13 +3573,47 @@ static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 min_vrr, } int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, - struct drm_crtc_state *crtc_state) + struct drm_crtc_state *crtc_state +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + , struct drm_plane_state *primary +#endif + ) { struct dcp_display_mode selected, *mode = &selected; struct dcp_wait_cookie *cookie; struct dcp_color_mode *cmode = NULL; int ret; +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + if (dcp_is_ext26(dcp)) { + u32 pitch = primary && primary->fb ? primary->fb->pitches[0] : dcp->ext26_stride; + + if (READ_ONCE(dcp->ext26_blocked)) + return -EIO; + if (READ_ONCE(dcp->pipe_enabled_h17p) && + (pitch != dcp->ext26_stride || + !drm_mode_equal(&dcp->ext26_mode, &crtc_state->mode))) { + /* A layout change cannot resume the old gentle-blank surface. */ + ret = dcp_ext26_layout_stop(dcp); + if (ret) + return ret; + } + dcp->ext26_stride = pitch; + if ((READ_ONCE(dcp->ext26_blanked) || READ_ONCE(dcp->ext26_normal)) && + READ_ONCE(dcp->pipe_enabled_h17p) && + dcp->ext26_generation == READ_ONCE(dcp->typec_generation) && + drm_mode_equal(&dcp->ext26_mode, &crtc_state->mode)) { + WRITE_ONCE(dcp->ext26_blanked, false); + return 0; + } + WRITE_ONCE(dcp->ext26_normal, false); + WRITE_ONCE(dcp->ext26_power_sent, false); + dcp->ext26_generation = READ_ONCE(dcp->typec_generation); + reinit_completion(&dcp->ext26_ready); + } + +#endif + if (!lookup_mode(dcp, &crtc_state->mode, mode)) { dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n", DRM_MODE_ARG(&crtc_state->mode)); @@ -3521,6 +3638,14 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, cmode = &mode->sdr; else if (mode->color_mode_id == mode->best.id) cmode = &mode->best; + if (dcp_is_ext26(dcp)) { + if (mode->sdr_rgb.score < 0 || mode->sdr_rgb.depth != 8 || + mode->sdr_rgb.format != DCP_COLOR_FORMAT_RGB || crtc_state->vrr_enabled) + return -EINVAL; + mode->color_mode_id = mode->sdr_rgb.id; + mode->vrr = false; + cmode = &mode->sdr_rgb; + } if (cmode) dev_info(dcp->dev, "set_digital_out_mode() color mode depth:%hhu format:%u " @@ -3549,8 +3674,17 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->swap_submit_timestamp = 0; - - if (DCP_INHERIT_BOOT_MODE && +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + if (dcp_is_ext26(dcp)) { + ret = ext26_open_client(dcp); + if (ret) { + kref_put(&cookie->refcount, release_wait_cookie); + kref_put(&cookie->refcount, release_wait_cookie); + return ret; + } + } +#endif + if (DCP_INHERIT_BOOT_MODE && !dcp_is_ext26(dcp) && dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL) { dev_dbg(dcp->dev, "inheriting the bootloader's display mode\n"); @@ -3606,6 +3740,17 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dev_info(dcp->dev, "set_digital_out_mode returned %#x\n", READ_ONCE(cookie->status)); } +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + if (dcp_is_ext26(dcp)) { + ret = READ_ONCE(cookie->replied) && !READ_ONCE(cookie->status) ? 0 : -EREMOTEIO; + kref_put(&cookie->refcount, release_wait_cookie); + if (!ret) + ret = ext26_activate(dcp); + if (!ret) + drm_mode_copy(&dcp->ext26_mode, &crtc_state->mode); + return ret; + } +#endif kref_put(&cookie->refcount, release_wait_cookie); dcp->vrr_enabled = mode->vrr && crtc_state->vrr_enabled; if (dcp->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL) diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 3bd2b0d2c214b..716761d59a324 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -380,9 +380,14 @@ struct DCP_FW_NAME(dcp_map_reg_resp) { struct apple_dcp; +struct drm_plane_state; int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, - struct drm_crtc_state *crtc_state); + struct drm_crtc_state *crtc_state +#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) + , struct drm_plane_state *primary +#endif + ); void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_commit *commit); void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp); void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp); diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index 87fecce18ee20..5d379272f05f3 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -6,6 +6,7 @@ #include "plane.h" #include "dcp.h" +#include "dcp.h" #include "dcp-internal.h" #include "iomfb_internal.h" @@ -232,6 +233,27 @@ static int apple_plane_atomic_check(struct drm_plane *plane, if (IS_ERR(crtc_state)) return PTR_ERR(crtc_state); + { + struct platform_device *pdev = to_apple_crtc(new_plane_state->crtc)->dcp; + struct apple_dcp *target = pdev ? platform_get_drvdata(pdev) : NULL; + struct drm_framebuffer *fb = new_plane_state->fb; + + if (target && dcp_is_ext26(target) && fb && !iomfb_ext26_backing_fresh(target, fb)) + return -EINVAL; + if (target && dcp_is_ext26(target) && fb && + (plane->type != DRM_PLANE_TYPE_PRIMARY || fb->format->format != DRM_FORMAT_XRGB8888 || + fb->modifier != DRM_FORMAT_MOD_LINEAR || fb->offsets[0] || + crtc_state->vrr_enabled || new_plane_state->rotation != DRM_MODE_ROTATE_0 || + new_plane_state->src_x || new_plane_state->src_y || + new_plane_state->src_w != ((u32)crtc_state->mode.hdisplay << 16) || + new_plane_state->src_h != ((u32)crtc_state->mode.vdisplay << 16) || + new_plane_state->crtc_x || new_plane_state->crtc_y || + new_plane_state->crtc_w != crtc_state->mode.hdisplay || + new_plane_state->crtc_h != crtc_state->mode.vdisplay || + fb->width != crtc_state->mode.hdisplay || fb->height != crtc_state->mode.vdisplay)) + return -EINVAL; + } + /* * DCP limits downscaling to 2x and upscaling to 4x. Attempting to * scale outside these bounds errors out when swapping. @@ -745,8 +767,9 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, bool supports_xrgb2101010, enum drm_plane_type type, bool interchange, - bool premultiplied) + bool premultiplied, bool native26) { + static const u32 native_formats[] = { DRM_FORMAT_XRGB8888 }; struct apple_plane *plane; unsigned int blend_modes; const u32 *fmts; @@ -755,7 +778,10 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, switch (type) { case DRM_PLANE_TYPE_PRIMARY: - if (supports_l10r && supports_xrgb2101010) { + if (native26) { + fmts = native_formats; + num_fmts = ARRAY_SIZE(native_formats); + } else if (supports_l10r && supports_xrgb2101010) { fmts = dcp_primary_formats; num_fmts = ARRAY_SIZE(dcp_primary_formats); } else if (supports_l10r) { diff --git a/drivers/gpu/drm/apple/plane.h b/drivers/gpu/drm/apple/plane.h index 69229ad6a5775..2baa27e86d846 100644 --- a/drivers/gpu/drm/apple/plane.h +++ b/drivers/gpu/drm/apple/plane.h @@ -39,7 +39,7 @@ struct drm_plane *apple_plane_init(struct drm_device *dev, bool supports_xrgb2101010, enum drm_plane_type type, bool interchange, - bool premultiplied); + bool premultiplied, bool native26); struct drm_plane *apple_plane_init_v14_7(struct drm_device *dev, unsigned long possible_crtcs, u32 iomfb_surf, diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c index 44e239c6016b0..32880aefb56d6 100644 --- a/drivers/gpu/drm/apple/systemep.c +++ b/drivers/gpu/drm/apple/systemep.c @@ -134,7 +134,11 @@ int systemep_init(struct apple_dcp *dcp) init_completion(&dcp->systemep_done); dcp->systemep = afk_init(dcp, SYSTEM_ENDPOINT, systemep_ops); - afk_start(dcp->systemep); + if (IS_ERR(dcp->systemep)) + return PTR_ERR(dcp->systemep); + int ret = afk_start(dcp->systemep); + if (ret || dcp_is_ext26(dcp)) + return ret; if (!enable_verbose_logging) return 0; From 809a114ae012cf01e10a53007c77ae4cbc346a10 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 11:34:31 -0500 Subject: [PATCH 05/32] drm/apple: test native26 wire, attachment and PM boundaries Keep executable host fixtures for production wire encoding, admission, changesets, startup, modeset and lifecycle behavior, including classic peers. Extend the existing apple-dcp selftest harness and host-test registry; these tests do not access hardware. Retain the imported GPU UAPI fixture registration correction. Drop diagnostic-only catalogue tests. Signed-off-by: Satya Benson --- tools/asahi/dcpext26-admission-tests.py | 44 +++ tools/asahi/dcpext26-changeset-tests.py | 61 ++++ tools/asahi/dcpext26-edid-tests.py | 63 ++++ tools/asahi/dcpext26-lifecycle-tests.py | 183 +++++++++++ tools/asahi/dcpext26-modeset-tests.py | 114 +++++++ tools/asahi/dcpext26-parity-tests.py | 250 +++++++++++++++ tools/asahi/dcpext26-present-tests.py | 72 +++++ tools/asahi/dcpext26-profile-tests.py | 135 ++++++++ tools/asahi/dcpext26-startup-tests.py | 298 ++++++++++++++++++ tools/asahi/dcpext26-wire-tests.py | 149 +++++++++ tools/asahi/pmp-native26-tests.py | 106 +++++++ tools/asahi/run-host-tests.py | 4 + tools/asahi/t6030-pmp-graph-tests.c | 10 + tools/asahi/t6030-pmp-graph-tests.py | 2 +- .../selftests/drivers/gpu/apple-dcp/common.py | 58 ++++ .../selftests/drivers/gpu/apple-dcp/edid.py | 4 +- .../drivers/gpu/apple-dcp/lifecycle.py | 3 +- .../drivers/gpu/apple-dcp/recovery.py | 2 + 18 files changed, 1554 insertions(+), 4 deletions(-) create mode 100644 tools/asahi/dcpext26-admission-tests.py create mode 100644 tools/asahi/dcpext26-changeset-tests.py create mode 100644 tools/asahi/dcpext26-edid-tests.py create mode 100644 tools/asahi/dcpext26-lifecycle-tests.py create mode 100644 tools/asahi/dcpext26-modeset-tests.py create mode 100644 tools/asahi/dcpext26-parity-tests.py create mode 100644 tools/asahi/dcpext26-present-tests.py create mode 100644 tools/asahi/dcpext26-profile-tests.py create mode 100644 tools/asahi/dcpext26-startup-tests.py create mode 100644 tools/asahi/dcpext26-wire-tests.py create mode 100644 tools/asahi/pmp-native26-tests.py diff --git a/tools/asahi/dcpext26-admission-tests.py b/tools/asahi/dcpext26-admission-tests.py new file mode 100644 index 0000000000000..bd862bab75989 --- /dev/null +++ b/tools/asahi/dcpext26-admission-tests.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Actual native identity and live-session predicates; classic stays separate.""" +from pathlib import Path +import os,subprocess,tempfile +ROOT=Path(__file__).resolve().parents[2] +header=(ROOT/'drivers/gpu/drm/apple/j613-25g83.h').read_text() +def function(name): + pos=header.index('static inline bool '+name+'(') + return header[pos:header.index('\n}',pos)+2] +source=r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; +#define BIT(n) (1U<<(n)) +#define J613_25G83_DCP_UUID "C042E95C-B9D8-3F0E-94B3-582A08AA6FDD" +'''+function('j613_25g83_identity')+function('j613_25g83_external_state')+r''' +int main(void) { + u32 version[]={26,6,2},state[]={1,16,34,1}; + assert(j613_25g83_identity(true,1,version,3,J613_25G83_DCP_UUID)); + for(unsigned i=0;i<3;i++) { version[i]++;assert(!j613_25g83_identity(true,1,version,3,J613_25G83_DCP_UUID));version[i]--; } + assert(!j613_25g83_identity(false,1,version,3,J613_25G83_DCP_UUID)); + assert(!j613_25g83_identity(true,0,version,3,J613_25G83_DCP_UUID)); + assert(!j613_25g83_identity(true,1,version,2,J613_25G83_DCP_UUID)); + assert(!j613_25g83_identity(true,1,version,3,"other image")); + assert(j613_25g83_external_state(state,4)); + for(unsigned i=0;i<4;i++)assert(!j613_25g83_external_state(state,i)); + assert(!j613_25g83_external_state(NULL,4)); + for(unsigned bit=0;bit<32;bit++) { + state[1]=16^(1U< +#include +#include +#include +#include +#include +#include +#define __init +#define GFP_KERNEL 0 +#define kzalloc_obj(x) zalloc(sizeof(x)) +#define raw_spin_lock_irqsave(l,f) ((f)=0) +#define raw_spin_unlock_irqrestore(l,f) ((void)(f)) +#define of_prop_cmp strcmp +#define __of_remove_property_sysfs(n,p) ((void)0) +#define __of_update_property_sysfs(n,p,o) ((void)0) +struct property { char *name; void *value; unsigned length; struct property *next; }; +struct device_node { struct property *properties,*deadprops; }; +struct of_changeset {int unused;};static struct of_changeset external_gate; +enum {OF_RECONFIG_ATTACH_NODE,OF_RECONFIG_DETACH_NODE,OF_RECONFIG_ADD_PROPERTY,OF_RECONFIG_REMOVE_PROPERTY,OF_RECONFIG_UPDATE_PROPERTY}; +struct of_changeset_entry {int action;struct device_node *np;struct property *prop,*old_prop;}; +static struct of_changeset_entry queued;static int queue_error,alloc_fail,alloc_calls,allocations; +static void *allocate(size_t n) { if (++alloc_calls==alloc_fail)return NULL;void *p=malloc(n);assert(p);allocations++;return p; } +static void *zalloc(size_t n){void *p=allocate(n);if(p)memset(p,0,n);return p;} +static char *kstrdup(const char *s,int f){(void)f;char *p=allocate(strlen(s)+1);if(p)strcpy(p,s);return p;} +static void *kmemdup(const void *s,size_t n,int f){(void)f;void *p=allocate(n);if(p)memcpy(p,s,n);return p;} +static void kfree(void *p){if(p){allocations--;free(p);}} +static int of_changeset_update_property(struct of_changeset *c,struct device_node *n,struct property *p){(void)c;if(queue_error)return queue_error;queued=(struct of_changeset_entry){OF_RECONFIG_UPDATE_PROPERTY,n,p,NULL};return 0;} +''' +code=pre+'\n'.join([fn(base,'__of_remove_property_from_list'),fn(base,'__of_update_property'),fn(base,'__of_remove_property'),fn(dyn,'__of_changeset_entry_invert'),fn(a,'j613_external_update')])+r''' +static void apply(void){assert(queued.action==OF_RECONFIG_UPDATE_PROPERTY);assert(!__of_update_property(queued.np,queued.prop,&queued.old_prop));} +static void revert(void){struct of_changeset_entry e;__of_changeset_entry_invert(&queued,&e);if(e.action==OF_RECONFIG_REMOVE_PROPERTY)assert(!__of_remove_property(e.np,e.prop));else{assert(e.action==OF_RECONFIG_UPDATE_PROPERTY);assert(!__of_update_property(e.np,e.prop,&e.old_prop));}} +int main(void){ + unsigned char oldbytes[4]={0,0,0,4},newbytes[4]={0,0,0,15};struct property old={.name="apple,min-state",.value=oldbytes,.length=4};struct device_node n={.properties=&old}; + assert(!j613_external_update(&n,"apple,min-state",newbytes,4));assert(n.properties==&old);apply();assert(n.properties->length==4&&!memcmp(n.properties->value,newbytes,4));revert();assert(n.properties==&old&&!memcmp(old.value,oldbytes,4)); + n=(struct device_node){0};assert(!j613_external_update(&n,"apple,min-state",newbytes,4));apply();revert();assert(!n.properties); + n=(struct device_node){0};assert(!j613_external_update(&n,"apple,always-on",NULL,0));apply();assert(n.properties&&!n.properties->length);revert();assert(!n.properties); + struct property boolean={.name="apple,always-on"};n=(struct device_node){.properties=&boolean};assert(!j613_external_update(&n,"apple,always-on",NULL,0));apply();revert();assert(n.properties==&boolean); + old.value=newbytes;n=(struct device_node){.properties=&old};assert(!j613_external_update(&n,"apple,min-state",newbytes,4));apply();revert();assert(n.properties==&old&&!memcmp(n.properties->value,newbytes,4)); + for(int i=1;i<=3;i++){alloc_calls=0;alloc_fail=i;int before=allocations;assert(j613_external_update(&n,"apple,min-state",newbytes,4)==-ENOMEM);assert(allocations==before&&n.properties==&old);} + alloc_fail=0;queue_error=-EIO;int before=allocations;assert(j613_external_update(&n,"apple,min-state",newbytes,4)==-EIO);assert(allocations==before&&n.properties==&old); + puts("PASS actual native UPDATE builder and Linux OF apply/revert: existing/absent/equal u32, existing/absent bool, allocation/queue failures"); +} +''' +with tempfile.TemporaryDirectory() as t: + p=Path(t);(p/'test.c').write_text(code);subprocess.run([os.environ.get('CC','clang'),'-std=gnu11','-Wall','-Wextra','-Werror','-fsanitize=address,undefined',str(p/'test.c'),'-o',str(p/'test')],check=True);subprocess.run([str(p/'test')],check=True,env={**os.environ,'ASAN_OPTIONS':'detect_leaks=0'}) diff --git a/tools/asahi/dcpext26-edid-tests.py b/tools/asahi/dcpext26-edid-tests.py new file mode 100644 index 0000000000000..5244bd35f6fb9 --- /dev/null +++ b/tools/asahi/dcpext26-edid-tests.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Native AV identity/attachment predicates and public EDID wire validation.""" +from pathlib import Path +import os,re,subprocess,tempfile +ROOT=Path(__file__).resolve().parents[2] +source=(ROOT/'drivers/gpu/drm/apple/epic/dpavservep.c').read_text() +def function(name): + m=re.search(r'^(?:static )?bool '+name+r'\(',source,re.M);assert m + return source[m.start():source.index('\n}',m.start())+2] +pre=r''' +#include +#include +#include +#include +#include +#include "epic/dpavserv-edid.h" +typedef int64_t s64; +#define DPAVSERV_ENDPOINT 0x28 +#define DPAVSERV_ENDPOINT_V2 0x2a +#define U16_MAX 65535 +#define DCP_IOMFB_METHODS_H17P_EXTERNAL 2 +#define READ_ONCE(x) (x) +#define lockdep_assert_held(x) ((void)0) +struct apple_epic_service; +struct apple_dcp { + bool native,typec_cable_connected,quiescing,external_physically_absent;u64 typec_generation;int hpd_mutex; + struct { int iomfb_method_profile; } hw; + struct { int lock;bool enabled;u64 epoch;struct apple_epic_service *service; } dcpavserv; +}; +struct ep { struct apple_dcp *dcp;u32 endpoint;bool wire_v2; }; +struct apple_epic_service { struct ep *ep;u32 channel; }; +static bool dcp_is_ext26(struct apple_dcp *d) { return d->native; } +''' +check=r''' +static void checksum(u8 *b) { u8 sum=0;b[127]=0;for(unsigned i=0;i<127;i++)sum+=b[i];b[127]=-sum; } +int main(void) { + struct apple_dcp d={.native=true,.typec_cable_connected=true,.typec_generation=7}; + struct ep ep={&d,0x28,true};struct apple_epic_service svc={&ep,1}; + assert(dcpavserv_identity(&svc,"dcpav-service-epic","DCPDPService",0)); + assert(!dcpavserv_identity(&svc,"dcpav-service-epic","DCPDPService",1)); + assert(!dcpavserv_identity(&svc,"dcpav-service-epic","DCPDP13Service",0)); + ep.endpoint=0x2a;assert(!dcpavserv_identity(&svc,"dcpav-service-epic","DCPDPService",0)); + ep.endpoint=0x28;svc.channel=0;assert(!dcpavserv_identity(&svc,"dcpav-service-epic","DCPDPService",0));svc.channel=1; + d.dcpavserv.enabled=true;d.dcpavserv.service=&svc;d.dcpavserv.epoch=10; + assert(dcpavserv_edid_current(&d,7)&&dcpavserv_edid_service_current(&d,10)); + assert(!dcpavserv_edid_current(&d,6)&&!dcpavserv_edid_service_current(&d,9)); + d.external_physically_absent=true;assert(!dcpavserv_edid_current(&d,7)&&!dcpavserv_edid_service_current(&d,10)); + d.external_physically_absent=false;d.quiescing=true;assert(!dcpavserv_edid_current(&d,7)); + d.quiescing=false;d.typec_cable_connected=false;assert(!dcpavserv_edid_current(&d,7)); + u8 wire[48+8+256]={0};u8 *base=wire+56;const u8 header[]={0,255,255,255,255,255,255,0}; + afkv2_put64(wire,EDID_BUF_SIZE);afkv2_put64(wire+32,8+256);memcpy(base,header,8);base[126]=1;checksum(base);checksum(base+128); + size_t size=0;assert(!dpavserv_edid_size(wire,sizeof(wire),&size)&&size==256); + for(size_t n=0;n +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; typedef uint64_t u64; typedef uint8_t u8; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,y) ((x)=(y)) +#define lockdep_assert_held(x) ((void)0) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) +#define IS_ERR_OR_NULL(x) (!(x)) +#define scoped_guard(...) for (bool once=true;once;once=false) +#define dev_info(...) ((void)0) +#define dev_dbg(...) ((void)0) +#define dev_warn(...) ((void)0) +#define dev_err(...) mock_log(__VA_ARGS__) +static void mock_log(int dev,const char *fmt,...) { (void)dev;(void)fmt; } +#define msecs_to_jiffies(x) (x) +#define DCP_SWAP_WATCHDOG_MS 2000 +#define DCP_EXTERNAL_STOP_RETRIES 3 +#define AFK_MAX_CHANNEL 3 +#define MAX_PENDING_CMDS 16 +#define spin_lock_irqsave(l,f) ((void)(l),(void)(&(f))) +#define spin_unlock_irqrestore(l,f) ((void)(l),(void)(&(f))) +static bool bitmap_empty(const unsigned long *v,unsigned n) { (void)n;return !*v; } +struct apple_epic_service { int lock; unsigned long cmd_map[1]; }; +struct ring { bool ready; u32 *rptr,*wptr; }; +struct apple_dcp_afkep { int lock,wq; struct ring rxbfr,txbfr; struct apple_epic_service services[AFK_MAX_CHANNEL]; }; +struct port { bool connected; int *service; }; +struct ext26_control; +#define SWAP_SURFACES 4 +#define guard(x) (void) +struct drm_gem_object { int tag; }; +struct drm_framebuffer { struct drm_gem_object *obj[1]; }; +struct iomfb_scanout_h17p { struct drm_framebuffer *fb[4]; struct { struct iomfb_scanout_h17p *next; } link; }; +#define list_for_each_entry(pos,head,member) for ((pos)=(head)->first;(pos);(pos)=(pos)->member.next) +struct apple_dcp { + struct { int lock; } iomfb; struct { struct iomfb_scanout_h17p *first; } ext26_retained; + bool external_suspended,ext26_pm_preparing,ext26_pm_work_off;u8 *coproc_reg; + u64 external_generation; int dev,external_error; bool external_stopped,external_power_attempted,external_running; + bool external_stop_deferred,pipe_enabled_h17p,typec_crtc_off,ext26_normal,ext26_stopping; + unsigned external_stop_retries; int external_lock,hpd_mutex,external_stop_retry_wq,rtk,avep; + struct port dptxport[1]; + struct apple_dcp_afkep *ext26_aux[3]; + struct apple_dcp_afkep *systemep,*ibootep,*dptx_controller_ep,*dptxep,*dcpavservep; + struct ext26_control *ext26_control; u32 ext26_control_id; bool ext26_control_pending,crashed,ext26_blocked; +}; +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN 16 +static u32 readl(const void *p) { u32 v;memcpy(&v,p,4);return v; } +static unsigned discoveries; +static void dcp_enable_typec_work(struct apple_dcp *d) { (void)d;discoveries++; } +static bool dcp_is_ext26(struct apple_dcp *d) { (void)d;return true; } +static char trace[128]; static unsigned used; static char fault; static unsigned settlements; +static void note(char c) { assert(usedservice; } +static int dptxport_set_hpd(int *s,bool on) { assert(s&&!on);return result('H'); } +static int dptxport_release_display(int *s) { assert(s);return result('R'); } +static void apple_rtkit_flush_rx(int r) { (void)r;note('X'); } +static void flush_workqueue(int q) { (void)q;note('W'); } +static int iomfb_ext26_retain_epoch(struct apple_dcp *d) { (void)d;return result('K'); } +static void dpavservep_detach(struct apple_dcp *d) { (void)d;note('E'); } +static void dcp_modes_begin_attachment(struct apple_dcp *d) { (void)d;note('M'); } +static void av_service_disconnect(struct apple_dcp *d) { (void)d;note('A'); } +static int system_dfl_wq; +static void mod_delayed_work(int q,int *w,int t) { (void)q;(void)w;(void)t;note('D'); } +struct completion { unsigned signaled; }; +static void complete_all(struct completion *c) { c->signaled++; } +#define DCPEXT26_SUBMIT_BYTES 0x1bd8 +struct ext26_control { struct dcpext26_exchange exchange; struct completion done; u8 wire[DCPEXT26_SUBMIT_BYTES]; }; +''' +checks = r''' +static struct apple_dcp fresh(struct apple_dcp_afkep *ep) { + static int service; struct apple_dcp d={0}; d.external_generation=7; d.external_power_attempted=true; + d.external_running=true; d.pipe_enabled_h17p=true; d.ext26_normal=true; + d.dptxport[0].connected=true; d.dptxport[0].service=&service; d.systemep=ep; + used=settlements=0;trace[0]=0;fault=0;return d; +} +static void native_pm_complete(struct apple_dcp *dcp); +static int native_pm_resume(struct apple_dcp *dcp); +int main(void) { + struct ext26_control c={0}; struct apple_dcp d={0};d.ext26_control=&c;d.ext26_control_pending=true; + assert(!dcpext26_exchange_begin(&c.exchange,0)); + assert(dcpext26_exchange_begin(&c.exchange,42));assert(!dcpext26_exchange_begin(&c.exchange,43)); + assert(!iomfb_ext26_control_complete(&d,41,false));assert(!c.done.signaled); + assert(iomfb_ext26_control_complete(&d,42,false));assert(!c.done.signaled&&d.ext26_control_pending); + c.exchange.replied=true; ext26_control_signal(&d,&c);assert(c.done.signaled&&!d.ext26_control_pending); + c=(struct ext26_control){0};d.ext26_control=&c;d.ext26_control_pending=true; + assert(dcpext26_exchange_begin(&c.exchange,51));c.exchange.replied=true; + ext26_control_signal(&d,&c);assert(!c.done.signaled); + assert(iomfb_ext26_control_complete(&d,51,true));assert(c.exchange.failed&&c.done.signaled); + assert(!dcpext26_epoch_fits(8,1,1)); assert(dcpext26_epoch_fits(7,0,(960ULL<<20))); + assert(!dcpext26_epoch_fits(7,(960ULL<<20),1));assert(!dcpext26_epoch_fits(0,~0ULL,1)); + struct apple_dcp_afkep ep={0};u32 read=0,write=0; + ep.rxbfr=(struct ring){true,&read,&write};assert(afk_host_idle(&ep)); + write=64;assert(!afk_host_idle(&ep));read=64;assert(afk_host_idle(&ep)); + ep.services[1].cmd_map[0]=1;assert(!afk_host_idle(&ep));ep.services[1].cmd_map[0]=0; + d=fresh(&ep);assert(!dcp_external_stop_locked(&d,6)&&!used); /* stale stop token */ + assert(!dcp_external_stop_locked(&d,7));assert(!strcmp(trace,"FEQPQHRXWXQKM")); + assert(d.external_stopped&&!d.pipe_enabled_h17p&&!d.ext26_normal&&!d.ext26_stopping); + assert(d.typec_crtc_off&&!d.dptxport[0].connected); + assert(!dcp_external_stop_locked(&d,7)); /* idempotent stop */ + d=fresh(&ep);fault='P';assert(dcp_external_stop_locked(&d,7)==-EIO); + assert(d.external_error==-EIO&&!d.external_stopped&&d.dptxport[0].connected); + assert(!strcmp(trace,"FEQP"));assert(dcp_external_stop_locked(&d,7)==-EIO); + d=fresh(&ep);fault='H';assert(dcp_external_stop_locked(&d,7)==-EIO);assert(!strchr(trace,'R')); + d=fresh(&ep);fault='R';assert(dcp_external_stop_locked(&d,7)==-EIO);assert(!strchr(trace,'K')); + d=fresh(&ep);ep.services[0].cmd_map[0]=1;assert(dcp_external_stop_locked(&d,7)==-EBUSY); + assert(!strchr(trace,'K'));ep.services[0].cmd_map[0]=0; + d=fresh(&ep);d.external_power_attempted=false;assert(!dcp_external_stop_locked(&d,7)); + assert(!strchr(trace,'P')&&strchr(trace,'K')); /* no-sink / pre-picture stop */ + d=fresh(&ep);assert(d.external_running);assert(!dcp_external_start_locked(&d));assert(!strcmp(trace,"LU")); + d=fresh(&ep);d.external_running=false;assert(!dcp_external_start_locked(&d));assert(d.external_running&&!strcmp(trace,"LU")); + struct drm_gem_object oldobj={1},newobj={2}; + struct drm_framebuffer oldfb={{&oldobj}},aliasfb={{&oldobj}},newfb={{&newobj}}; + struct iomfb_scanout_h17p retained={.fb={&oldfb}};d.ext26_retained.first=&retained; + assert(!iomfb_ext26_backing_fresh(&d,&oldfb));assert(!iomfb_ext26_backing_fresh(&d,&aliasfb)); + assert(iomfb_ext26_backing_fresh(&d,&newfb)); + u8 registers[0x50]={0};u32 running=16;memcpy(registers+0x44,&running,4); + d=(struct apple_dcp){.coproc_reg=registers,.ext26_pm_preparing=true,.ext26_pm_work_off=true,.external_suspended=true}; + assert(!native_pm_resume(&d));assert(!discoveries&&d.ext26_pm_preparing&&d.ext26_pm_work_off); + native_pm_complete(&d);assert(discoveries==1&&!d.ext26_pm_preparing&&!d.ext26_pm_work_off); + native_pm_complete(&d);assert(discoveries==1); + memset(registers,0,sizeof(registers));d.ext26_pm_preparing=d.ext26_pm_work_off=true; + assert(native_pm_resume(&d)==-EHOSTDOWN&&d.ext26_blocked&&d.external_error==-EHOSTDOWN); + native_pm_complete(&d);assert(discoveries==1); /* a dead coprocessor gets no discovery */ + puts("PASS native26 exact control order, stale IDs, abort, retained-stop sequencing/faults, host drain, old-GEM rejection, epoch bounds and PM-complete ordering"); +} +''' +life = (ROOT/'drivers/gpu/drm/apple/dcpext26-lifecycle.h').read_text().replace('#include ','') +# Put the pure state before the mock control container. +pre = pre.replace('struct ext26_control { struct dcpext26_exchange',life+'\nstruct ext26_control { struct dcpext26_exchange') +code = pre + '\n' + '\n'.join([ + body('drivers/gpu/drm/apple/afk.c','afk_host_idle'), + body('drivers/gpu/drm/apple/iomfb.c','iomfb_ext26_backing_fresh'), + body('drivers/gpu/drm/apple/iomfb_h17p.c','ext26_control_signal'), + body('drivers/gpu/drm/apple/iomfb_h17p.c','iomfb_ext26_control_complete'), + body('drivers/gpu/drm/apple/dcp.c','dcp_external_stop_locked')]) + checks +# dcp.c has a forward declaration; select the actual definition. +s=(ROOT/'drivers/gpu/drm/apple/dcp.c').read_text();pos=s.index('static int dcp_external_stop_locked(struct apple_dcp *dcp, u64 generation)\n{');actual=s[pos:s.index('\n}',pos)+2] +code=code[:code.index('static int dcp_external_stop_locked')] + actual + '\n' + body('drivers/gpu/drm/apple/dcp.c','dcp_external_start_locked') + checks +# Execute the native branches of the actual PM hooks, preserving their returns. +def native_branch(name): + pos=s.index('static '+('void' if name.endswith('complete') else 'int')+' '+name+'(') + pos=s.index('if (dcp_is_ext26(dcp)) {',pos) + opening=s.index('{',pos);end=opening+1;depth=1 + while depth: + depth+=(s[end]=='{')-(s[end]=='}');end+=1 + return s[pos:end] +code += '\nstatic void native_pm_complete(struct apple_dcp *dcp) { '+native_branch('dcp_platform_complete')+' }\n' +code += 'static int native_pm_resume(struct apple_dcp *dcp) { int dev=0; '+native_branch('dcp_platform_resume')+' return -EINVAL; }\n' + +with tempfile.TemporaryDirectory() as t: + p=Path(t);(p/'check.c').write_text(code) + subprocess.run([os.environ.get('CC','clang'),'-std=gnu11','-Wall','-Wextra','-Werror','-fsanitize=address,undefined',str(p/'check.c'),'-o',str(p/'check')],check=True) + subprocess.run([str(p/'check')],check=True) diff --git a/tools/asahi/dcpext26-modeset-tests.py b/tools/asahi/dcpext26-modeset-tests.py new file mode 100644 index 0000000000000..0c3afa1b0638d --- /dev/null +++ b/tools/asahi/dcpext26-modeset-tests.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise native preflight with DRM's detached CRTC state after state swap.""" +from pathlib import Path +import subprocess,tempfile +R=Path(__file__).resolve().parents[2];s=(R/'drivers/gpu/drm/apple/iomfb_template.c').read_text();caller=(R/'drivers/gpu/drm/apple/iomfb.c').read_text() +a=s.index('\tif (dcp_is_ext26(dcp)) {',s.index('int DCP_FW_NAME(iomfb_modeset)')) +b=s.index('\n\tif (!lookup_mode',a) +a=s.rfind('#if DCP_FW_VER',0,a) +body=s[a:b] +assert 'crtc_state->state' not in body +assert 'drm_atomic_get_new_plane_state(commit, crtc->primary)' in caller +pre=r''' +#include +#include +#include +#include +typedef uint32_t u32; +#define DCP_FW_VERSION(a,b,c) ((a)*10000+(b)*100+(c)) +#define DCP_FW_VER DCP_FW_VERSION(26,6,0) +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +struct mode {int id;}; +struct drm_framebuffer {u32 pitches[1];}; +struct drm_plane_state {struct drm_framebuffer *fb;}; +struct drm_crtc_state {void *state;struct mode mode;}; +struct apple_dcp {bool ext26_blocked,pipe_enabled_h17p,ext26_blanked,ext26_normal,ext26_power_sent;u32 ext26_stride,ext26_generation,typec_generation;struct mode ext26_mode;int ext26_ready;}; +static bool dcp_is_ext26(struct apple_dcp *d){return true;} +static bool drm_mode_equal(const struct mode *a,const struct mode *b){return a->id==b->id;} +static unsigned stops; +static int dcp_ext26_layout_stop(struct apple_dcp *d){stops++;d->pipe_enabled_h17p=false;return 0;} +#define reinit_completion(c) (*(c)=0) +''' +code=pre+'static int prepare(struct apple_dcp *dcp,struct drm_crtc_state *crtc_state,struct drm_plane_state *primary){int ret;\n'+body+'\nreturn 0;}'+r''' +int main(void){ + struct drm_crtc_state c={.state=0,.mode.id=1};struct drm_framebuffer fb={.pitches={15360}};struct drm_plane_state p={.fb=&fb};struct apple_dcp d={.typec_generation=7}; + assert(!prepare(&d,&c,&p)&&d.ext26_stride==15360&&d.ext26_generation==7); + d.pipe_enabled_h17p=true;d.ext26_blanked=true;d.ext26_mode=c.mode; + assert(!prepare(&d,&c,&p)&&!d.ext26_blanked&&!stops); + // G's post-resume repeat must preserve the already proven active mode. + d.ext26_normal=true;d.ext26_power_sent=true;d.ext26_ready=1; + assert(!prepare(&d,&c,&p)&&d.ext26_normal&&d.ext26_power_sent&&d.ext26_ready==1&&!stops); + // A different physical generation must still acquire fresh readiness. + d.typec_generation++;assert(!prepare(&d,&c,&p)&&!d.ext26_normal&&!d.ext26_power_sent&&d.ext26_ready==0); + fb.pitches[0]=16384;assert(!prepare(&d,&c,&p)&&stops==1&&d.ext26_stride==16384); + d.ext26_blocked=true;assert(prepare(&d,&c,&p)==-EIO); + d.ext26_blocked=false;assert(!prepare(&d,&c,0)); +} +''' +with tempfile.TemporaryDirectory() as t: + p=Path(t)/'test.c';p.write_text(code);subprocess.run(['clang','-std=gnu11','-Wall','-Werror','-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True);subprocess.run([str(p.with_suffix(''))],check=True) +print('PASS actual native preflight: detached CRTC atomic backlink, 4K pitch, gentle resume, changed layout, missing plane and blocked session') + +# The driver enable callback must finish route ownership and wake a pending flip +# on native modeset errors; the DRM core, not this callback, owns commit completion. +drv=(R/'drivers/gpu/drm/apple/apple_drv.c').read_text() +a=drv.index('static void apple_crtc_atomic_enable(');b=drv.index('\n}',a)+2 +actual=drv[a:b] +preamble=r''' +#include +#include +#include +#define dev_warn(...) ((void)0) +#define dev_err(...) ((void)0) +struct drm_crtc {int unused;}; +struct drm_crtc_state {bool active,active_changed,color_mgmt_changed;}; +struct drm_atomic_commit {struct drm_crtc_state *state;unsigned failed_routes;}; +struct apple_dcp {void *dev;bool native,main_display;int mode_state,vblank_wq;void *connector;}; +struct apple_crtc {struct apple_dcp *dcp;bool fabric_held;}; +static struct apple_crtc owner; +static struct apple_crtc *to_apple_crtc(struct drm_crtc *c){return &owner;} +static struct apple_dcp *platform_get_drvdata(struct apple_dcp *d){return d;} +static struct drm_crtc_state *drm_atomic_get_new_crtc_state(struct drm_atomic_commit *c,struct drm_crtc *r){return c->state;} +#define to_apple_atomic_state(c) (c) +static unsigned drm_crtc_mask(struct drm_crtc *c){return 1;} +static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static unsigned done,woken,notified,invalidated,powered,programmed; +static int mode_error; +static int dcp_typec_follow_crtc(struct drm_crtc *c,struct drm_atomic_commit *a,bool *locked){*locked=true;return 0;} +static void dcp_typec_follow_done(bool locked){assert(locked);done++;} +static bool dcp_crtc_route_ready(struct drm_crtc *c,struct drm_atomic_commit *a,bool enable){return true;} +static bool dcp_crtc_needs_route_start(struct apple_dcp *d){return false;} +static int dcp_typec_prepare_route(struct apple_dcp *d){return 0;} +static int apple_drm_fabric_hold(void){return 0;} +static void dcp_poweron(struct apple_dcp *d){powered++;} +static void dcp_external_recover(struct apple_dcp *d){} +static bool drm_atomic_crtc_needs_modeset(struct drm_crtc_state *s){return true;} +static int dcp_crtc_atomic_modeset(struct drm_crtc *c,struct drm_atomic_commit *a){programmed++;return mode_error;} +static void dcp_mode_invalidate(int *m){invalidated++;*m=0;} +static void schedule_work(int *w){woken++;} +static void dcp_route_failure_notify(void *c){notified++;} +''' +checks=r''' +int main(void){ + struct drm_crtc crtc={0};struct drm_crtc_state state={.active=true,.active_changed=true}; + struct drm_atomic_commit commit={.state=&state};struct apple_dcp d={.native=true,.connector=(void *)1};owner.dcp=&d; + const int errors[]={-EIO,-ENOMEM,-ETIMEDOUT,-EPIPE}; + for(unsigned i=0;i<4;i++){ + done=woken=notified=invalidated=powered=programmed=0;mode_error=errors[i];d.mode_state=1; + apple_crtc_atomic_enable(&crtc,&commit); + assert(done==1&&woken==1&¬ified==1&&invalidated==1&&programmed==1&&powered==1&&!d.mode_state); + } + done=woken=notified=invalidated=0;mode_error=0; + apple_crtc_atomic_enable(&crtc,&commit);assert(done==1&&!woken&&!notified&&!invalidated); + // Existing non-native error policy is preserved. + d.native=false;done=woken=notified=invalidated=0;mode_error=-EIO; + apple_crtc_atomic_enable(&crtc,&commit);assert(done==1&&!woken&&!notified&&!invalidated); +} +''' +with tempfile.TemporaryDirectory() as t: + p=Path(t)/'enable.c';p.write_text(preamble+actual+checks) + subprocess.run(['clang','-std=gnu11','-Wall','-Werror','-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) + subprocess.run([str(p.with_suffix(''))],check=True) +print('PASS actual enable callback: native mode errors invalidate/wake flips/finish route once; success and classic policy preserved') diff --git a/tools/asahi/dcpext26-parity-tests.py b/tools/asahi/dcpext26-parity-tests.py new file mode 100644 index 0000000000000..4cb08bba288ed --- /dev/null +++ b/tools/asahi/dcpext26-parity-tests.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Ordered native startup and the native DP callback contract, actual C bodies. + +Synthetic transport/MMIO boundaries. This cannot prove a physical picture or PM. +""" +from pathlib import Path +import os,re,subprocess,tempfile +R=Path(__file__).resolve().parents[2] +def fn(s,n): + m=re.search(r'^(?:static )?(?:int|bool|void) '+n+r'\([^;]*?\)\n\{',s,re.M|re.S);assert m,n + i=m.end();depth=1 + while depth:depth+=(s[i]=='{')-(s[i]=='}');i+=1 + return s[m.start():i] +def run(name,code): + with tempfile.TemporaryDirectory() as t: + p=Path(t)/'test.c';p.write_text(code) + subprocess.run([os.environ.get('CC','clang'),'-std=gnu11','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-Wno-missing-field-initializers','-Wno-sign-compare','-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) + subprocess.run([str(p.with_suffix(''))],check=True) + print('PASS',name) +dcp=(R/'drivers/gpu/drm/apple/dcp.c').read_text() +pre=r''' +#include +#include +#include +#include +#include +#include +#include +typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;typedef uint64_t u64; +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,y) ((x)=(y)) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) +#define IS_ERR(p) ((intptr_t)(p)<0) +#define PTR_ERR(p) ((int)(intptr_t)(p)) +#define ERR_PTR(e) ((void *)(intptr_t)(e)) +#define dev_info(...) ((void)0) +#define dev_dbg(...) ((void)0) +#define APPLE_DCP_COPROC_CPU_CONTROL 0x44 +#define APPLE_DCP_COPROC_CPU_CONTROL_RUN 16 +#define APPLE_DCP_COPROC_CPU_STATUS 0x48 +#define msecs_to_jiffies(x) (x) +struct device {int unused;}; +struct apple_dcp_afkep {u8 endpoint;bool match_epic_name;}; +struct apple_dcp {struct device *dev;u8 *coproc_reg;void *rtk;bool crashed;int ext26_link_ready; + struct apple_dcp_afkep *ext26_aux[3];}; +static char trace[256];static unsigned used;static char fault; +static void note(char c){assert(used<255);trace[used++]=c;trace[used]=0;} +static int step(char c){note(c);return fault==c?-EIO:0;} +static void reset(char f){memset(trace,0,sizeof(trace));used=0;fault=f;} +static int dev_err_probe(struct device *d,int e,const char *fmt,...){return e;} +static u32 readl(void *p){u32 x;memcpy(&x,p,4);return x;} +static void writel(u32 x,void *p){assert(x&16);note('R');memcpy(p,&x,4);} +static int dcp_pin_live_session(struct apple_dcp *d){return step('P');} +static const int rtkit_ops; +static void *apple_rtkit_init(struct device *dev,struct apple_dcp *d,const char *name,int channel,const int *ops){assert(!strcmp(name,"mbox")&&!channel&&ops==&rtkit_ops);return step('I')?ERR_PTR(-EIO):d;} +static void dcp_release_rtkit(void *d){} +static int devm_add_action_or_reset(struct device *d,void (*action)(void *),void *p){return step('A');} +static int apple_rtkit_wake(void *r){return step('W');} +static int apple_rtkit_boot(void *r){return step('B');} +static int iomfb_start_rtkit(struct apple_dcp *d){return step('L');} +static unsigned wait_for_completion_timeout(int *c,unsigned t){note('l');return fault!='l';} +static int systemep_init(struct apple_dcp *d){return step('0');} +static int ibootep_init(struct apple_dcp *d){return step('3');} +static int dptx_controller_init(struct apple_dcp *d){return step('4');} +static int dpavservep_init(struct apple_dcp *d){return step('8');} +static int dptxep_init(struct apple_dcp *d){return step('a');} +static const int ext26_aux_ops[1]; +static struct apple_dcp_afkep aux[3];static unsigned auxiliary; +static struct apple_dcp_afkep *afk_init(struct apple_dcp *d,u8 ep,const int *ops){assert(ops==ext26_aux_ops);struct apple_dcp_afkep *a=&aux[auxiliary++%3];a->endpoint=ep;return a;} +static int afk_start(struct apple_dcp_afkep *ep){char c=ep->endpoint==0x27?'7':ep->endpoint==0x29?'9':'c';return step(c);} +static void iomfb_ext26_start(struct apple_dcp *d){note('S');} +''' +checks=r''' +int main(void){ + u8 mmio[0x50]={0};struct device dev={0};struct apple_dcp d={.dev=&dev,.coproc_reg=mmio};u32 run=16; + reset(0);memcpy(mmio+0x44,&run,4);assert(!dcp_ext26_start_resident(&d));assert(!strcmp(trace,"PIAW")); + reset(0);memset(mmio,0,sizeof(mmio));assert(!dcp_ext26_start_resident(&d));assert(!strcmp(trace,"PIARB")&&readl(mmio+0x44)==16); + const char stages[]="PIAWB"; + for(unsigned i=0;idp.lanes=capacity;return fault;} +static int dcp_direct_crossbar_link(struct apple_dcp *d,bool on){if(on)up++;else down++;return fault;} +static void mdelay(unsigned n){assert(n==10);delay++;} +static void complete(int *c){(*c)++;} +static int dptx_j700_training(struct apple_epic_service *s,bool w,const void *d,size_t n,void *r,size_t room){training++;return 0;} +static int dptx_j700_validate_lanes(struct dptx_port *p,u64 n){return n<=capacity&&n!=3?0:-EINVAL;} +static int dptxport_call_set_link_rate(struct apple_epic_service *s,const void *d,size_t n,void *r,size_t room){assert(down);return 0;} +static int dptxport_call(struct apple_epic_service *s,u32 cmd,const void *d,size_t n,void *r,size_t room){fallback++;return 0;} +''' +run('native DP callback wire and ordering',pre2+fn(s,'dptxport_native26_call')+r''' +int main(void){ + struct apple_dcp d={0};struct ep ep={&d};struct dptx_port p={.atcphy=&d};struct apple_epic_service svc={&ep,&p};u8 input[160]={0},out[160]; + #define CALL(c,n,r) dptxport_native26_call(&svc,0,(c),input,(n),out,(r)) + memset(out,0xa5,sizeof(out));assert(!CALL(2,32,32));assert(word(out+20)==3&&word(out+24)==3&&!word(out+16)); + assert(!CALL(7,32,32)&&word(out+16)==0x14); + assert(!CALL(18,32,32)&&word(out+16)==1);assert(!CALL(21,160,160)&&word(out)==1); + assert(!CALL(10,32,32)&&p.native26_max_lanes==4&&!p.lane_count); + put_unaligned_le64(4,input+16);assert(!CALL(12,32,32)&&p.lane_count==4&&p.linkcfg_completion==1); + put_unaligned_le64(3,input+16);assert(CALL(12,32,32)==-EINVAL); + assert(!CALL(0,16,16)&&up==1);assert(!CALL(1,16,16)&&down==1); + assert(!CALL(5,16,16)&&down==2);p.link_rate=0x14;assert(!CALL(6,16,16)&&delay==1&&up==2); + fault=-EIO;assert(CALL(0,16,16)==-EIO);put_unaligned_le64(0x14,input+16);assert(CALL(9,32,32)==-EIO&&!fallback);fault=0; + put_unaligned_le64(0x99,input+16);assert(CALL(9,32,32)==-EINVAL); + assert(!dptxport_native26_call(&svc,1,3,input,80,out,80)&&training==1); + assert(dptxport_native26_call(&svc,2,3,input,80,out,80)==-EOPNOTSUPP); + puts("native DP HPD/tiled=1, drive offsets20/24, maxHBR2, distinct max/active lanes, crossbar errors propagated"); +} +''') +# Deferred OPEN executes only after A401, including ops without a prepare/revalidate hook. +s=(R/'drivers/gpu/drm/apple/afk.c').read_text() +pre3=pre[:pre.index('struct device {')]+r''' +#define kfree(p) ((void)0) +#define dev_err(...) ((void)0) +#define EPIC_TYPE_NOTIFY 1 +#define EPIC_CAT_REPORT 2 +#define AFKV2_OPEN 3 +struct apple_epic_service; +struct apple_epic_service_ops {int (*revalidate)(struct apple_epic_service *,const char *,const char *,int);void (*init)(struct apple_epic_service *,const char *,const char *,int);}; +struct apple_epic_service {bool pending_open,enabled;const struct apple_epic_service_ops *ops;const char *pending_name,*pending_class;int pending_unit;u32 channel;}; +struct apple_dcp {bool ext26_services_open;struct apple_dcp_afkep *dptxep;}; +static bool dptx_native_controllers_ready(struct apple_dcp *d){return true;} +struct apple_dcp_afkep {struct apple_dcp *dcp;unsigned num_channels;struct apple_epic_service services[2];}; +static bool dcp_is_ext26(struct apple_dcp *d){return true;} +static unsigned sent,published,discarded,initialized;static int fault; +static void afk_discard_pending_open(struct apple_epic_service *s){discarded++;s->pending_open=false;} +static void *afk_epic_find_service(struct apple_dcp_afkep *e,u32 c){return NULL;} +static int afk_send_epic(struct apple_dcp_afkep *e,u32 c,u16 tag,int type,int cat,u8 subtype,const void *data,size_t size){assert(c&&subtype==AFKV2_OPEN&&!size);sent++;return fault;} +static void afk_service_publish(struct apple_epic_service *s){published++;s->enabled=true;} +static void afk_populate_service_debugfs(struct apple_epic_service *s){} +static void initialized_service(struct apple_epic_service *s,const char *n,const char *c,int u){initialized++;} +''' +run('A401 service OPEN barrier',pre3+fn(s,'afk_retry_service_init')+r''' +int main(void){struct apple_dcp d={0};struct apple_epic_service_ops ops={.init=initialized_service};struct apple_dcp_afkep e={.dcp=&d,.num_channels=1};e.services[0]=(struct apple_epic_service){.pending_open=true,.ops=&ops,.channel=1}; + afk_retry_service_init(&e);assert(!sent&&!published&&!initialized&&e.services[0].pending_open); + d.ext26_services_open=true;afk_retry_service_init(&e);assert(sent==1&&published==1&&initialized==1&&!e.services[0].pending_open&&e.services[0].enabled); + afk_retry_service_init(&e);assert(sent==1);ops.init=NULL;e.services[0].pending_open=true;fault=-EIO;afk_retry_service_init(&e);assert(discarded==1&&published==1); + puts("native deferred OPEN survives absent revalidate/init hooks, opens after A401 once, rejects failed transport");} +''') +# Native property transfer's sentinel count and replacing a prior transfer. +s=(R/'drivers/gpu/drm/apple/iomfb_h17p.c').read_text() +# fn() also accepts the u8 return type for this small callback. +m=re.search(r'^static u8 ext26_prop_start\([^;]*?\)\n\{',s,re.M|re.S);a=m.start();i=m.end();depth=1 +while depth:depth+=(s[i]=='{')-(s[i]=='}');i+=1 +run('native property transfer count',pre[:pre.index('struct device {')]+r''' +#define SZ_1M (1U<<20) +#define GFP_KERNEL 0 +#define kfree free +#define max_t(t,a,b) ((t)(a)>(t)(b)?(t)(a):(t)(b)) +static bool fail_alloc; +static void *kzalloc(size_t n,int f){return fail_alloc?NULL:calloc(1,n);} +struct dcp_chunks {void *data;size_t length,received;bool contiguous;u64 modes_generation;}; +struct apple_dcp {struct dcp_chunks chunks;}; +static u64 generation; +static u64 dcp_modes_transfer_begin(struct apple_dcp *d){return ++generation;} +static void dcp_firmware_metadata_begin(struct apple_dcp *d){} +'''+s[a:i]+r''' +int main(void){struct apple_dcp d={0};assert(!ext26_prop_start(&d,0));assert(!ext26_prop_start(&d,(1U<<20)+2)); + assert(ext26_prop_start(&d,4097)&&d.chunks.length==4096&&d.chunks.contiguous&&!d.chunks.received); + memset(d.chunks.data,0x55,4096);d.chunks.received=11;assert(ext26_prop_start(&d,2)&&d.chunks.length==1&&!d.chunks.received&&!*(u8 *)d.chunks.data); + assert(ext26_prop_start(&d,1)&&d.chunks.length==0&&d.chunks.data);free(d.chunks.data);d.chunks.data=NULL; + fail_alloc=true;assert(!ext26_prop_start(&d,4097)&&!d.chunks.data);puts("native D127 count-1, replaced transfer, empty payload, bounds and allocation refusal");} +''') +# Physical-address allocation and PIODMA lifetime: exercise the common allocator's +# native branch, plus the native wire admission, rather than a model allocator. +s=(R/'drivers/gpu/drm/apple/iomfb_template.c').read_text();pos=s.index('dcpep_cb_allocate_buffer(struct');a=s.rfind('static struct ',0,pos);i=s.index('\n}',pos)+2 +alloc=s[a:i] +h=(R/'drivers/gpu/drm/apple/iomfb_h17p.c').read_text() +pre4=pre[:pre.index('struct device {')]+r''' +#define SZ_16K 16384UL +#define DCP_MAX_MAPPINGS 128 +#define GFP_KERNEL 0 +#define DMA_ATTR_FORCE_CONTIGUOUS 8 +#define DCP_FW_VERSION(a,b,c) ((a)*10000+(b)*100+(c)) +#define DCP_FW_VER DCP_FW_VERSION(26,6,0) +#define DCP_FW_NAME(n) n +#define round_down(x,a) ((x)&~((__typeof__(x))(a)-1)) +#define check_add_overflow(a,b,p) __builtin_add_overflow((a),(b),(p)) +#define dev_warn(...) ((void)0) +struct scatterlist {u64 phys;}; +struct sg_table {struct scatterlist *sgl;unsigned orig_nents;}; +struct dcp_mem_descriptor {size_t size;void *buf;u64 dva;struct sg_table map;bool piodma_mapped;}; +struct apple_dcp {bool native;void *dev;struct dcp_mem_descriptor memdesc[128];unsigned long memdesc_map[2],ext26_released[2];}; +struct dcp_allocate_buffer_req {u64 size;}; +struct dcp_allocate_buffer_resp {u32 mem_desc_id;u64 dva_size,dva,paddr;}; +static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static bool test_bit(unsigned id,const unsigned long *b){return !!(b[id/64]&(1UL<<(id%64)));} +static void set_bit(unsigned id,unsigned long *b){b[id/64]|=1UL<<(id%64);} +static unsigned find_first_zero_bit(unsigned long *b,unsigned n){for(unsigned i=0;isgl=&sg;t->orig_nents=fragmented?2:1;return 0;} +static void sg_free_table(struct sg_table *t){} +static u64 sg_phys(struct scatterlist *s){return s->phys;} +static void dma_free_attrs(void *d,size_t n,void *p,u64 a,unsigned attrs){assert(attrs==8);frees++;free(p);} +static void dma_free_coherent(void *d,size_t n,void *p,u64 a){frees++;free(p);} +static u64 get_unaligned_le64(const void *p){u64 v;memcpy(&v,p,8);return v;} +static u32 get_unaligned_le32(const void *p){u32 v;memcpy(&v,p,4);return v;} +static void put_unaligned_le32(u32 v,void *p){memcpy(p,&v,4);} +static void put_unaligned_le64(u64 v,void *p){memcpy(p,&v,8);} +static bool trampoline_map_piodma_h17p(struct apple_dcp *d,int tag,void *out,void *in){u64 id=get_unaligned_le64(in);maps++;d->memdesc[id].piodma_mapped=true;put_unaligned_le32(id,out);put_unaligned_le64(d->memdesc[id].dva,(u8 *)out+4);return true;} +''' +run('native contiguous buffer and PIODMA wire ownership',pre4+alloc+fn(h,'ext26_map_piodma')+r''' +int main(void){struct apple_dcp d={.native=true};set_bit(0,d.memdesc_map);struct dcp_allocate_buffer_req q={4096}; + struct dcp_allocate_buffer_resp r=dcpep_cb_allocate_buffer(&d,&q);assert(r.mem_desc_id==1&&r.dva_size==16384&&r.paddr==0x200000&&r.dva==0x800000&&attributes==8); + u8 in[12]={0},out[16];put_unaligned_le64(1,in);put_unaligned_le32(0,in+8);ext26_map_piodma(&d,201,out,in);assert(!maps&&!get_unaligned_le32(out)); + put_unaligned_le32(1,in+8);ext26_map_piodma(&d,201,out,in);assert(maps==1&&get_unaligned_le32(out)==1&&get_unaligned_le64(out+4)==r.dva&&!get_unaligned_le32(out+12)); + ext26_map_piodma(&d,201,out,in);assert(maps==1&&get_unaligned_le64(out+4)==r.dva);set_bit(1,d.ext26_released);ext26_map_piodma(&d,201,out,in);assert(maps==1&&!get_unaligned_le32(out)&&d.memdesc[1].buf); + fragmented=true;r=dcpep_cb_allocate_buffer(&d,&q);assert(!r.mem_desc_id&&frees==1&&!test_bit(2,d.memdesc_map)); + free(d.memdesc[1].buf);puts("native D451 physically contiguous16K reply and D201 word1, packed16-byte reply, repeat mapping and logical retention");} +''') diff --git a/tools/asahi/dcpext26-present-tests.py b/tools/asahi/dcpext26-present-tests.py new file mode 100644 index 0000000000000..930c808a712b5 --- /dev/null +++ b/tools/asahi/dcpext26-present-tests.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Actual DRM primary start/submit callbacks: faults and early exact completion.""" +from pathlib import Path +import os,re,subprocess,tempfile +ROOT=Path(__file__).resolve().parents[2] +s=(ROOT/'drivers/gpu/drm/apple/iomfb_template.c').read_text() +def function(name): + m=re.search(r'^static void '+name+r'\(',s,re.M);assert m + return s[m.start():s.index('\n}',m.start())+2] +pre=r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32;typedef uint64_t u64; +#define WRITE_ONCE(x,y) ((x)=(y)) +#define DCP_FW_NAME(n) n +#define DCP_FW_UNION(x) ((x).h17p) +#define dev_err(...) ((void)0) +#define trace_iomfb_swap_submit(...) ((void)0) +struct dc_swap_complete_resp { u32 swap_id; }; +struct dcp_swap_submit_resp { u32 ret; }; +struct dcp_swap_start_resp { u32 swap_id,ret; }; +struct apple_dcp { bool native,crashed,ext26_blocked,ext26_early_complete;u32 ext26_early_id; + u64 ext26_rx_ns,ext26_early_ns,swap_start,swap_submit_timestamp;void *dev,*crtc; + struct { struct { struct {u32 swap_id;} swap; } h17p; } swap; }; +static int submit_result;static bool begin_allowed=true; +static unsigned failures,events,armed,watchdogs,completed,sent;static u64 completion_time; +static bool dcp_is_ext26(struct apple_dcp *d) { return d->native; } +static int dcp_present_submit(struct apple_dcp *d,u32 id,bool ok) { (void)d;(void)id;(void)ok;return submit_result; } +static void dcp_present_failed(struct apple_dcp *d) { (void)d;failures++; } +static bool dcp_present_retires(struct apple_dcp *d) { (void)d;return true; } +static void dcp_unarm_retained_framebuffers(struct apple_dcp *d,u32 id) { (void)d;(void)id; } +static void dcp_drm_crtc_vblank(void *c) { (void)c;events++; } +static void dcpep_cb_swap_complete(struct apple_dcp *d,struct dc_swap_complete_resp *r) { assert(r->swap_id==d->swap.h17p.swap.swap_id);completed++;completion_time=d->ext26_rx_ns; } +static u64 ktime_get(void) { return 30; } +static u64 arch_timer_read_counter(void) { return 40; } +static void dcp_swap_watchdog_arm(struct apple_dcp *d) { (void)d;watchdogs++; } +static bool dcp_present_begin(struct apple_dcp *d,u32 id) { (void)d;assert(id);return begin_allowed; } +static void dcp_arm_retained_framebuffers(struct apple_dcp *d,u32 id) { (void)d;(void)id;armed++; } +static void dcp_h17p_prepare_swap(struct apple_dcp *d) { assert(!d->native); } +static void dcp_swap_submit(struct apple_dcp *d,bool oob,void *r,void (*cb)(struct apple_dcp *,void *,void *),void *c) { (void)d;(void)r;(void)cb;(void)c;assert(!oob);sent++; } +''' +check=r''' +static struct apple_dcp fresh(bool native) { + struct apple_dcp d={.native=native};d.swap.h17p.swap.swap_id=7; + failures=events=armed=watchdogs=completed=sent=0;submit_result=0;begin_allowed=true;return d; +} +int main(void) { + struct dcp_swap_submit_resp bad={1},ok={0};struct apple_dcp d=fresh(true); + dcp_swapped(&d,&bad,NULL);assert(d.crashed&&d.ext26_blocked&&failures==1&&events==1&&!watchdogs); + d=fresh(false);dcp_swapped(&d,&bad,NULL);assert(!d.crashed&&!d.ext26_blocked&&failures==1); + d=fresh(true);dcp_swapped(&d,NULL,NULL);assert(d.crashed&&d.ext26_blocked&&!watchdogs); + d=fresh(true);submit_result=-ECANCELED;dcp_swapped(&d,&ok,NULL);assert(d.crashed&&d.ext26_blocked); + d=fresh(true);d.ext26_early_complete=true;d.ext26_early_id=7;d.ext26_early_ns=15;d.ext26_rx_ns=20; + dcp_swapped(&d,&ok,NULL);assert(completed==1&&completion_time==15&&d.ext26_rx_ns==20&&!d.ext26_early_complete&&!watchdogs&&!d.crashed); + d=fresh(true);dcp_swapped(&d,&ok,NULL);assert(watchdogs==1&&!d.crashed&&!failures); + struct dcp_swap_start_resp start={8,1};d=fresh(true);dcp_swap_started(&d,&start,NULL);assert(d.crashed&&d.ext26_blocked&&!armed&&!sent); + d=fresh(false);dcp_swap_started(&d,&start,NULL);assert(!d.crashed&&!sent); + d=fresh(true);start=(struct dcp_swap_start_resp){0,0};dcp_swap_started(&d,&start,NULL);assert(d.crashed&&!sent); + d=fresh(true);start.swap_id=8;begin_allowed=false;dcp_swap_started(&d,&start,NULL);assert(d.crashed&&!sent&&!armed); + d=fresh(true);dcp_swap_started(&d,&start,NULL);assert(!d.crashed&&sent==1&&armed==1&&d.swap.h17p.swap.swap_id==8); + puts("PASS actual native DRM start/submit rejection, classic controls and original-time early D590 joining"); +} +''' +with tempfile.TemporaryDirectory() as t: + p=Path(t);(p/'check.c').write_text(pre+'\n'.join(function(n) for n in ['dcp_present_rejected','dcp_swapped','dcp_swap_started'])+check) + subprocess.run([os.environ.get('CC','clang'),'-std=gnu11','-O1','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-fsanitize=address,undefined',str(p/'check.c'),'-o',str(p/'check')],check=True) + subprocess.run([str(p/'check')],check=True) diff --git a/tools/asahi/dcpext26-profile-tests.py b/tools/asahi/dcpext26-profile-tests.py new file mode 100644 index 0000000000000..ca8c29b4cff6e --- /dev/null +++ b/tools/asahi/dcpext26-profile-tests.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Native26 callback/startup/readiness helpers with a synthetic peer boundary.""" +from pathlib import Path +import os +import subprocess +import tempfile + +ROOT = Path(__file__).resolve().parents[2] +source = (ROOT / 'drivers/gpu/drm/apple/iomfb_h17p.c').read_text() +header = (ROOT / 'drivers/gpu/drm/apple/iomfb.h').read_text() + + +def function(name): + start = source.index('static ', source.index(name) - 40) + # Find the signature's start without relying on the return type's line wrapping. + pos = source.index(name + '(') + start = source.rfind('static ', 0, pos) + return source[start:source.index('\n}', pos)+2] + + +def block(text, name): + start = text.index(name) + return text[start:text.index('\n};', start)+3] + + +pre = r''' +#include +#include +#include +#include +#include +#include +#include +typedef uint8_t u8; typedef uint32_t u32; typedef uint64_t u64; +#define GFP_KERNEL 0 +#define kzalloc_obj(v) calloc(1,sizeof(v)) +#define kfree free +#define READ_ONCE(v) (v) +#define WRITE_ONCE(v,x) ((v)=(x)) +#define dev_err(...) ((void)0) +#define dev_info(...) ((void)0) +#define dev_dbg(...) ((void)0) +static u32 get_unaligned_le32(const void *p) { const u8 *b=p; return (u32)b[0]|(u32)b[1]<<8|(u32)b[2]<<16|(u32)b[3]<<24; } +static void put_unaligned_le32(u32 v,void *p) { u8 *b=p;for(unsigned i=0;i<4;i++) b[i]=v>>(8*i); } +static void put_unaligned_le64(u64 v,void *p) { u8 *b=p;for(unsigned i=0;i<8;i++) b[i]=v>>(8*i); } +struct mock_connector { bool connected; }; +struct apple_dcp { struct mock_connector *connector; bool crashed, ext26_normal, ext26_power_sent; u64 ext26_rx_ns,ext26_power_ns,ext26_generation,typec_generation; int ext26_ready; }; +static unsigned withdrawn,invalidated,notifications; +static void dcp_modes_begin_attachment(struct apple_dcp *d) { (void)d;withdrawn++; } +static void apple_connector_invalidate_edid(struct mock_connector *c) { (void)c;invalidated++; } +static void dcp_queue_hotplug(struct mock_connector *c) { (void)c;notifications++; } +static void complete_all(int *c) { (*c)++; } +static bool iomfb_validate_callback_h17p(int t,u32 i,u32 o) { (void)t;(void)i;(void)o;return false; } +struct dcp_method_entry { const char *name; char tag[4],tag_h17g[4],tag_ext26[4]; }; +#define IOMFB_METHOD(t,n) [n]={#n,{t[0],t[1],t[2],t[3]},{0},{0}} +#define IOMFB_METHOD_H17(t,h,n) [n]={#n,{t[0],t[1],t[2],t[3]},{h[0],h[1],h[2],h[3]},{0}} +#define IOMFB_METHOD_EXT26(t,e,n) [n]={#n,{t[0],t[1],t[2],t[3]},{0},{e[0],e[1],e[2],e[3]}} +static unsigned count,done,fault,bad_status; +static void boot_done(struct apple_dcp *d,void *o,void *c) { (void)d;(void)o;(void)c;done++; } +static void dcp_push(struct apple_dcp *,bool,const struct dcp_method_entry *,u32,u32,void *,void (*)(struct apple_dcp *,void *,void *),void *); +''' +peer = r''' +static void dcp_push(struct apple_dcp *d,bool oob,const struct dcp_method_entry *m,u32 in,u32 out,void *input,void (*next)(struct apple_dcp *,void *,void *),void *cookie) { + const char *expected[]={"A389","A446","A029","A468","A000","A465"}; + const u32 ins[]={0,0,0,4,4,0},outs[]={0,4,0,0,4,4}; + assert(count<6 && !oob); + const char *tag=m->tag_ext26[0]?m->tag_ext26:m->tag; + assert(!memcmp(tag,expected[count],4)); + assert(in==ins[count] && out==outs[count]); + if(in) assert(get_unaligned_le32(input)==1); + u32 reply=count>=4 ? 1 : 0; + count++; + if (bad_status==count) reply=0; + next(d,fault==count?NULL:&reply,cookie); +} +int main(void) { + struct apple_dcp d={0};struct ext26_boot *b=calloc(1,sizeof(*b));b->one=1; + ext26_boot_next(&d,NULL,b);assert(done==1 && count==6 && !d.crashed); + for(fault=1;fault<=6;fault++) { + d=(struct apple_dcp){0};done=count=0;b=calloc(1,sizeof(*b));b->one=1; + ext26_boot_next(&d,NULL,b);assert(!done && d.crashed && count==fault); + } + fault=0; + for(bad_status=5;bad_status<=6;bad_status++) { + d=(struct apple_dcp){0};done=count=0;b=calloc(1,sizeof(*b));b->one=1; + ext26_boot_next(&d,NULL,b);assert(!done && d.crashed && count==bad_status); + } + unsigned char input[128]={0}, output[128]; + memset(output,0xcd,sizeof(output));ext26_bandwidth(&d,3,output+1,input); + for(unsigned i=0;i<60;i++) assert(output[i+1]==(i==56)); + assert(output[0]==0xcd && output[61]==0xcd); + for(unsigned i=0;i<80;i++) input[i]=i; + input[80]=0;ext26_frame_sync(&d,6,output,input);assert(!memcmp(output,input,80)); + input[80]=1;ext26_frame_sync(&d,6,output,input); + for(unsigned i=0;i<80;i++) assert(!output[i]); + memcpy(input,"VORP",4);put_unaligned_le32(0,input+4);ext26_clock(&d,408,output,input); + assert(get_unaligned_le32(output)==48000000); + put_unaligned_le32(1,input+4);ext26_clock(&d,408,output,input);assert(get_unaligned_le32(output)==24000000); + put_unaligned_le32(2,input+4);ext26_clock(&d,408,output,input);assert(!get_unaligned_le32(output)); + d=(struct apple_dcp){.ext26_power_sent=true,.ext26_power_ns=10,.ext26_generation=7,.typec_generation=7}; + memset(input,0,sizeof(input));memcpy(input,"NormalModeActive",17);input[64]=1; + for(unsigned t=0;t<=10;t++) { d.ext26_rx_ns=t;ext26_property(&d,104,NULL,input);assert(!d.ext26_normal && !d.ext26_ready); } + d.ext26_rx_ns=11;ext26_property(&d,104,NULL,input);assert(d.ext26_normal && d.ext26_ready==1); + input[64]=0;ext26_property(&d,104,NULL,input);assert(!d.ext26_normal); + input[64]=1;d.typec_generation++;ext26_property(&d,104,NULL,input);assert(!d.ext26_normal); + d.typec_generation--;d.ext26_power_sent=false;ext26_property(&d,104,NULL,input);assert(!d.ext26_normal); + d.ext26_power_sent=true;ext26_property(&d,104,NULL,input);assert(d.ext26_normal); + ext26_property(&d,107,NULL,input);assert(!d.ext26_normal); + struct mock_connector connector={true};d.connector=&connector; + memset(input,0,sizeof(input));memcpy(input,"TimingElements",15);ext26_property(&d,107,NULL,input); + assert(withdrawn==1&&invalidated==1&¬ifications==1&&!connector.connected); + const unsigned tags[]={3,6,107,115,116,134,408,454,567,572,576,588}; + const unsigned ins[]={4,84,64,16,16,0,8,4,128,4,4,0},outs[]={60,80,0,8,4,4,8,4,4,4,0,0}; + for(unsigned i=0;i deferred OPEN, and nested D121 transport regressions. + +Extract production C bodies. Firmware/queues are peers, not hardware proof. +An optional --capture JSON supplies bounded logged prefixes, never fabricated +complete captures. Unlogged dictionaries below are explicitly synthetic. +""" +from pathlib import Path +import argparse,json,os,re,subprocess,tempfile +R=Path(__file__).resolve().parents[2];A=R/'drivers/gpu/drm/apple' +def fn(s,name): + matches=list(re.finditer(r'(?m)^[^\n;{}]*\b'+name+r'\([^;{}]*\)\n\{',s)) + if not matches: raise RuntimeError('missing body '+name) + m=matches[0];start=m.start() + if s[start:m.end()].lstrip().startswith(name+'('):start=s.rfind('\n',0,start-1)+1 + end=m.end();depth=1 + while depth:depth+=(s[end]=='{')-(s[end]=='}');end+=1 + return s[start:end] +def block(s,name): + start=s.index(name);end=s.index('\n};',start)+3;return s[start:end] +def run(name,code,reject=False): + with tempfile.TemporaryDirectory() as t: + p=Path(t)/'test.c';p.write_text(code) + subprocess.run([os.environ.get('CC','clang'),'-std=gnu11','-Wall','-Wextra','-Werror','-Wno-unused-function','-Wno-unused-parameter','-Wno-sign-compare','-Wno-missing-field-initializers','-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) + result=subprocess.run([str(p.with_suffix(''))],capture_output=reject,check=False) + if reject: + if result.returncode == 0:raise RuntimeError('mutant survived: '+name) + else:result.check_returncode() + print('PASS',name,flush=True) +args=argparse.ArgumentParser();args.add_argument('--capture',type=Path);args.add_argument('--mutants',action='store_true');args=args.parse_args() +pre=r''' +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef uint8_t u8;typedef uint16_t u16;typedef uint32_t u32;typedef uint64_t u64;typedef int64_t s64;typedef uint32_t __le32; +#define __packed __attribute__((packed)) +#define GFP_KERNEL 0 +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) +#define round_up(x,a) (((x)+(a)-1)&~((a)-1)) +#define ALIGN round_up +#define IS_ERR(p) ((uintptr_t)(p)>=(uintptr_t)-4095) +#define IS_ERR_OR_NULL(p) (!(p)||IS_ERR(p)) +#define PTR_ERR(p) ((int)(intptr_t)(p)) +#define ERR_PTR(e) ((void *)(intptr_t)(e)) +#define min_t(t,a,b) ((t)(a)<(t)(b)?(t)(a):(t)(b)) +#define BIT(n) (1UL<<(n)) +#define U32_MAX UINT32_MAX +#define S64_MIN INT64_MIN +#define S64_MAX INT64_MAX +#define kmalloc(n,f) malloc(n) +#define kzalloc_obj(v) calloc(1,sizeof(v)) +#define kfree(p) free((void *)(p)) +#define max(a,b) ((a)>(b)?(a):(b)) +#define dev_info(...) ((void)0) +#define dev_err(...) ((void)0) +#define dev_dbg(...) ((void)0) +#define dev_warn(...) ((void)0) +#define dev_warn_ratelimited(...) ((void)0) +#define EXPORT_SYMBOL_IF_KUNIT(...) +#define spin_lock_irqsave(l,f) ((f)=0) +#define spin_unlock_irqrestore(l,f) ((void)(f)) +static void put_unaligned_le32(u32 v,void *p){memcpy(p,&v,4);} +static u32 get_unaligned_le32(const void *p){u32 v;memcpy(&v,p,4);return v;} +static u64 get_unaligned_le64(const void *p){u64 v;memcpy(&v,p,8);return v;} +''' +parser=(A/'parser.c').read_text();afk=(A/'afk.c').read_text();dptx=(A/'dptxep.c').read_text();dcp=(A/'dcp.c').read_text();h=(A/'afk.h').read_text() +codec=re.sub(r'^#include[^\n]*\n','',(A/'afkv2-codec.h').read_text(),flags=re.M) +identity=pre+codec+r''' +#define DCP_FIRMWARE_H17P 1 +#define DCP_FIRMWARE_V_14_7 2 +#define AFK_MAX_CHANNEL 16 +#define SYSTEM_ENDPOINT 0x20 +#define DISP0_ENDPOINT 0x23 +#define EPIC_TYPE_NOTIFY 0 +#define EPIC_CAT_REPORT 0 +#define bitmap_empty(p,n) 1 +#define MAX_PENDING_CMDS 16 +struct dcp_parse_ctx {void *dcp;const void *blob;u32 pos,len;}; +struct apple_epic_service;struct apple_dcp_afkep; +struct apple_epic_service_ops {const char *name;bool reusable,optional;int (*prepare)(struct apple_epic_service *,const char *,const char *,s64);int (*revalidate)(struct apple_epic_service *,const char *,const char *,s64);void (*init)(struct apple_epic_service *,const char *,const char *,s64);void (*teardown)(struct apple_epic_service *);}; +struct apple_epic_service {struct apple_dcp_afkep *ep;const struct apple_epic_service_ops *ops;bool enabled,torndown,pending_open;unsigned users;int lock;u32 channel;void *cookie;const char *pending_name,*pending_class;s64 pending_unit;int cmd_map[1];}; +struct dptx_port {struct apple_epic_service *service;s64 unit;bool enabled,connected;int enable_completion;}; +struct apple_dcp {bool native,ext26_services_open;int fw_compat,index,dptx_controller_ready;struct {unsigned num_dptx_ports;} hw;struct dptx_port dptxport[2];void *dev;struct apple_dcp_afkep *systemep,*ibootep,*dptx_controller_ep,*dcpavservep,*dptxep,*ext26_aux[3];}; +struct apple_dcp_afkep {struct apple_dcp *dcp;u32 endpoint,num_channels;bool wire_v2,stopping,match_epic_name;int lock;void *wq,*service_work;const struct apple_epic_service_ops *ops;struct apple_epic_service services[AFK_MAX_CHANNEL];}; +static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static bool dcp_is_v14_7_external(struct apple_dcp *d){return false;} +static bool dcp_fw_is_14_7(struct apple_dcp *d){return d->fw_compat==DCP_FIRMWARE_V_14_7;} +static size_t strscpy(char *d,const void *v,size_t n){const char *s=v;size_t l=strnlen(s,n-1);memcpy(d,s,l);d[l]=0;return l;} +static void complete(int *c){(*c)++;} +#define trace_dptxport_init(...) +#define smp_store_release(p,v) (*(p)=(v)) +static unsigned opened[256][16],queued,published; +static int open_error; +static void afk_populate_service_debugfs(struct apple_epic_service *s){} +static void afk_recv_handle_teardown(struct apple_dcp_afkep *e,u32 c){} +static void afk_retry_service_init(struct apple_dcp_afkep *ep); +void afk_service_retry(struct apple_dcp_afkep *ep); +bool dptx_native_controllers_ready(struct apple_dcp *dcp); +static int queue_work(void *wq,void *work){struct apple_dcp_afkep *e=wq;assert(e->dcp->ext26_services_open||!e->stopping);queued++;afk_retry_service_init(e);return 1;} +static int afk_send_epic(struct apple_dcp_afkep *e,u32 ch,u16 tag,int ty,int cat,u8 sub,const void *p,size_t n){assert(sub==AFKV2_OPEN&&!n&&ch<16&&e->dcp->ext26_services_open);opened[e->endpoint][ch]++;return open_error;} +''' +identity+=parser[parser.index('#define DCP_PARSE_HEADER'):parser.index('static int parse_bool(')] +# Drop the audio-only helper region from the extracted prefix. +identity=re.sub(r'#if IS_ENABLED\(CONFIG_DRM_APPLE_AUDIO\).*?#endif','',identity,flags=re.S) +identity+=block(parser,'struct iterator {')+'\n'+fn(parser,'iterator_begin')+'\n'+fn(parser,'parse')+'\n'+fn(parser,'parse_epic_service_init')+'\n' +for name in ['afk_service_reclaimable','afk_service_reinit','afk_service_discard','afk_service_publish','afk_service_matches']: + identity+=fn(h,name)+'\n' +identity+=fn(dptx,'dptx_native_controllers_ready')+'\n'+fn(dptx,'dptx_native_controller_prepare')+'\n'+fn(dptx,'dptx_controller_service_init')+'\n' +for name in ['afk_match_service','afk_epic_find_service','afk_discard_pending_open','afk_retry_service_init','afk_service_retry']: + identity+=fn(afk,name)+'\n' +service=(A/'dptx-service.h').read_text() +for name in ['dptx_service_port','dptx_service_bind','dptx_service_publish']: + identity+=fn(service,name)+'\n' +identity+=fn(dptx,'dptxport_prepare')+'\n'+fn(dcp,'dcp_ext26_open_services')+'\n'+fn(afk,'afk_recv_handle_init')+r''' +static void port_init(struct apple_epic_service *s,const char *n,const char *c,s64 u){dptx_service_publish(s);published++;} +static const struct apple_epic_service_ops port_ops[]={{.name="AppleDCPDPTXRemotePort",.prepare=dptxport_prepare,.init=port_init},{0}}; +static const struct apple_epic_service_ops controller_ops[]={{.name="AppleDCPDPTXController",.prepare=dptx_native_controller_prepare,.init=dptx_controller_service_init},{0}}; +static const struct apple_epic_service_ops av_ops[]={{.name="DCPDPService"},{0}}; +struct blob {u8 p[2048];size_t n;}; +static void tag(struct blob *b,u32 t,u32 n){b->n=ALIGN(b->n,4);put_unaligned_le32(t<<24|n,b->p+b->n);b->n+=4;} +static void text(struct blob *b,const char *s){tag(b,9,strlen(s));memcpy(b->p+b->n,s,strlen(s));b->n+=strlen(s);} +static void number(struct blob *b,s64 v){tag(b,4,64);memcpy(b->p+b->n,&v,8);b->n+=8;} +static struct blob description(const char *name,const char *class,s64 unit,u32 channel,bool alias_first){ + struct blob b={0};memcpy(b.p,"dispext0",9);b.n=32;put_unaligned_le32(0xd3,b.p+b.n);b.n+=4;tag(&b,1,4); + if(alias_first){text(&b,"interface-id");number(&b,channel);} + text(&b,"EPICName");text(&b,name);text(&b,"EPICProviderClass");text(&b,class);text(&b,"EPICUnit");number(&b,unit); + if(!alias_first){text(&b,"interface-id");number(&b,channel);}return b; +} +static void announce(struct apple_dcp_afkep *e,unsigned ch,struct blob b){ + u8 record[4096];size_t size;struct afkv2_frame f; + assert(!afkv2_encode_frame(record,sizeof(record),0,ch,0,0x11,AFKV2_REPORT,4,b.p,b.n,&size)); + assert(!afkv2_decode_frame(record,size,false,&f));afk_recv_handle_init(e,f.interface,(void *)f.payload,f.payload_size); +} +int main(void){ + struct apple_dcp d={.native=true,.fw_compat=DCP_FIRMWARE_H17P,.index=1,.hw.num_dptx_ports=2};struct apple_dcp_afkep eps[8]={0}; + struct apple_dcp_afkep **slots[]={&d.systemep,&d.ibootep,&d.dptx_controller_ep,&d.ext26_aux[0],&d.dcpavservep,&d.ext26_aux[1],&d.dptxep,&d.ext26_aux[2]}; + const unsigned ids[]={0x20,0x23,0x24,0x27,0x28,0x29,0x2a,0x2c}; + for(unsigned i=0;i<8;i++){*slots[i]=&eps[i];eps[i]=(struct apple_dcp_afkep){.dcp=&d,.endpoint=ids[i],.wire_v2=true};eps[i].wq=&eps[i];} + d.dptxep->ops=port_ops;d.dptx_controller_ep->ops=controller_ops;d.dcpavservep->ops=av_ops; + // Infrastructure dictionaries have interface metadata but no EPIC identity, as on the working daily run. + struct blob infrastructure={0};memcpy(infrastructure.p,"system",7);infrastructure.n=32; + put_unaligned_le32(0xd3,infrastructure.p+32);infrastructure.n+=4;tag(&infrastructure,1,2); + text(&infrastructure,"interface-id");number(&infrastructure,1);text(&infrastructure,"ep-has-desc-mgr");tag(&infrastructure,11,1); + announce(d.systemep,1,infrastructure);announce(d.ibootep,1,infrastructure);assert(!d.systemep->num_channels&&!d.ibootep->num_channels); + // Both alias orders, distinct interface and EPICUnit, pass through production AFK/parser/prepare. + announce(d.dptxep,1,description("dcpdptx-port-epic","AppleDCPDPTXRemotePort",0,1,false)); + announce(d.dptxep,3,description("dcpdptx-port-epic","AppleDCPDPTXRemotePort",1,3,true)); + assert(d.dptxep->num_channels==2&&d.dptxport[0].unit==0&&d.dptxport[1].unit==1); + assert(!d.dptxport[0].service&&!d.dptxport[1].service); + for(unsigned i=0;i<4;i++)announce(d.dptx_controller_ep,2*i+1,description(i&1?"dcpdp-controller-epic":"dcpav-controller-epic","AppleDCPDPTXController",i/2,2*i+1,false)); + announce(d.dcpavservep,1,description("dcpav-service-epic","DCPDPService",0,1,false)); + // No service becomes visible and no OPEN goes out before A401. + assert(!published);for(unsigned i=0;i<8;i++)for(unsigned c=0;c<16;c++)assert(!opened[ids[i]][c]); + d.ext26_services_open=true;afk_service_retry(d.dptxep);assert(!published&&!opened[0x2a][1]); + queued=0;dcp_ext26_open_services(&d);assert(queued==9&&published==2&&dptx_native_controllers_ready(&d)); + assert(d.dptxport[0].service&&d.dptxport[1].service&&d.dptxport[0].enabled&&d.dptxport[1].enabled); + assert(opened[0x2a][1]==1&&opened[0x2a][3]==1&&opened[0x28][1]==1); + for(unsigned i=0;i<4;i++)assert(opened[0x24][2*i+1]==1); + dcp_ext26_open_services(&d);assert(published==2&&opened[0x2a][1]==1); + d.dptx_controller_ep->services[0].enabled=false;assert(!dptx_native_controllers_ready(&d));d.dptx_controller_ep->services[0].enabled=true; + // Native invalid units still fail closed; truncated identities publish nothing. + announce(d.dptxep,5,description("dcpdptx-port-epic","AppleDCPDPTXRemotePort",3,5,false));assert(!opened[0x2a][5]); + struct blob short_b={0};short_b.n=1;announce(d.dptxep,7,short_b);assert(!opened[0x2a][7]); + // Panel aliases retain their existing meaning; native explicit EPIC keys do not consume interface-id. + struct blob b=description("dcpdptx-port-epic","AppleDCPDPTXRemotePort",0,3,false);struct dcp_parse_ctx ctx;const char *n,*c;s64 u=-1; + assert(!parse(b.p+32,b.n-32,&ctx));assert(!parse_epic_service_init(&ctx,&n,&c,&u,false)&&u==0);free((void *)n);free((void *)c); + assert(!parse(b.p+32,b.n-32,&ctx));assert(!parse_epic_service_init(&ctx,&n,&c,&u,true)&&u==3);free((void *)n);free((void *)c); + // Preserve panel-only lowercase aliases, which native26 intentionally does not accept. + struct blob panel={0};put_unaligned_le32(0xd3,panel.p);panel.n=4;tag(&panel,1,3); + text(&panel,"compartment");text(&panel,"system");text(&panel,"name");text(&panel,"system");text(&panel,"interface-id");number(&panel,1); + assert(!parse(panel.p,panel.n,&ctx));assert(!parse_epic_service_init(&ctx,&n,&c,&u,true)&&u==1&&!strcmp(n,"system"));free((void *)n);free((void *)c); + assert(!parse(panel.p,panel.n,&ctx));assert(parse_epic_service_init(&ctx,&n,&c,&u,false)==-ENOENT); + // A failed OPEN cannot publish a prepared port; closed workqueues are never retried. + struct apple_dcp_afkep failed={.dcp=&d,.endpoint=0x2a,.wire_v2=true,.ops=port_ops};failed.wq=&failed; + d.dptxport[0].service=NULL;d.dptxport[0].enabled=false;open_error=-EIO;announce(&failed,1,description("dcpdptx-port-epic","AppleDCPDPTXRemotePort",0,1,false));assert(!d.dptxport[0].service&&!failed.services[0].enabled); + failed.stopping=true;unsigned q=queued;afk_service_retry(&failed);assert(queued==q); + d.native=false;failed.stopping=false;afk_service_retry(&failed);assert(queued==q); // classic only retries its EDID endpoint + for(unsigned i=0;i<8;i++)for(unsigned j=0;j A401 queues all eight endpoints -> exact once OPEN and publication; aliases, truncation, bad units, transport failure and classic retry guard"); +} +''' +run('native AFK announcements through deferred OPEN',identity) +# Compile the actual command allocator and ACK dispatch with the production D121 +# callback. A peer can write one Boolean byte or all four bytes, then nest D003. +iom=(A/'iomfb.c').read_text();native=(A/'iomfb_h17p.c').read_text();ih=(A/'iomfb.h').read_text();internal=(A/'dcp-internal.h').read_text() +transport=pre+r''' +#define DCP_FIRMWARE_H17P 1 +#define DCP_IOMFB_METHODS_H17G 2 +#define DCP_IOMFB_METHODS_H17P_EXTERNAL 3 +#define DCP_MAX_CALL_DEPTH 8 +#define IOMFB_MAX_CB 600 +#define DCP_PACKET_ALIGNMENT (0x40) +#define IOMFB_ENDPOINT 0x37 +#define IOMFB_MESSAGE_TYPE 15ULL +#define IOMFB_MESSAGE_TYPE_MSG 2 +#define IOMFB_MSG_CONTEXT 0xf00ULL +#define IOMFB_MSG_OFFSET 0xffff0000ULL +#define IOMFB_MSG_LENGTH 0xffffffff00000000ULL +#define IOMFB_MSG_ACK 64ULL +#define FIELD_PREP(m,v) (((u64)(v)<<__builtin_ctzll(m))&(m)) +#define FIELD_GET(m,v) (((v)&(m))>>__builtin_ctzll(m)) +#define WARN_ON(x) assert(!(x)) +#define IS_ENABLED(x) 0 +#define current ((void *)1) +#define dma_wmb() ((void)0) +#define dma_rmb() ((void)0) +#define trace_iomfb_push(...) +#define IOMFB_METHOD(t,n) [n]={#n,{t[0],t[1],t[2],t[3]},{0},{0}} +#define IOMFB_METHOD_H17(t,h,n) [n]={#n,{t[0],t[1],t[2],t[3]},{h[0],h[1],h[2],h[3]},{0}} +#define IOMFB_METHOD_EXT26(t,e,n) [n]={#n,{t[0],t[1],t[2],t[3]},{0},{e[0],e[1],e[2],e[3]}} +struct apple_dcp;typedef void (*dcp_callback_t)(struct apple_dcp *,void *,void *); +struct dcp_method_entry {const char *name;char tag[4],tag_h17g[4],tag_ext26[4];}; +''' +transport+=block(ih,'enum dcp_context_id {')+'\n'+block(ih,'struct dcp_packet_header {')+'\n'+block(internal,'struct dcp_channel {')+'\n'+block(ih,'enum dcpep_method {')+r''' +struct apple_dcp {void *dev;u8 *shmem;int fw_compat;bool native,crashed,quiescing,ext26_blocked,ext26_power_sent;u64 ext26_power_ns;struct {int iomfb_method_profile;}hw;struct {void *owner;} iomfb;struct dcp_channel ch_cb,ch_cmd,ch_oobcb,ch_oobcmd,ch_async,ch_oobasync;}; +static bool iomfb_uses_queue(struct apple_dcp *d){return true;} +static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static int iomfb_queue_command(struct apple_dcp *d,bool o,const struct dcp_method_entry *m,u32 i,u32 n,void *p,dcp_callback_t cb,void *c){abort();} +static u64 ktime_get_ns(void){return 1;} +static void dcp_send_message(struct apple_dcp *,u8,u64); +static void dcp_ack(struct apple_dcp *,enum dcp_context_id); +static void dcp_push(struct apple_dcp *,bool,const struct dcp_method_entry *,u32,u32,void *,dcp_callback_t,void *); +static void boot_done(struct apple_dcp *,void *,void *); +static unsigned commands,callback_acks;static int bad_status,corrupt;static bool byte_reply;static u8 memory[0x100000]; +''' +for name in ['dcp_tx_offset','dcp_call_context','dcp_get_channel','dcp_packet_start','dcp_push_depth','dcp_pop_depth','dcpep_msg','dcpep_ack','dcp_push','dcp_ack','dcpep_handle_ack']: + code=fn(iom,name) + if name in ['dcp_push','dcp_ack']:code='static '+code + transport+=code+'\n' +transport+=block(native,'static const struct dcp_method_entry dcp_methods[')+'\n'+block(native,'struct ext26_boot {')+'\n' +for name in ['ext26_boot_next','ext26_boot_callback','ext26_bandwidth']: + transport+=fn(native,name)+'\n' +transport+=fn((A/'iomfb_template.c').read_text(),'boot_done')+r''' +static void callback(struct apple_dcp *d,int tag,unsigned in,unsigned out,unsigned off){ + struct dcp_channel *ch=&d->ch_cb;u8 *record=d->shmem+0x60000+off; + // Firmware-originated callback record; production handlers and ACK share this backing. + memset(record,0,12+in+out);struct dcp_packet_header *h=(void *)record;h->in_len=in;h->out_len=out; + if(tag==3){u8 raw[4]={0x00,0x0b,0x20,0xf1};memcpy(record+12,raw,4);} // daily D003 input, captured in full + unsigned depth=dcp_push_depth(&ch->depth);ch->output[depth]=record+12+in;ch->end[depth]=off+ALIGN(12+in+out,64); + bool ack=tag==121?ext26_boot_callback(d,tag,record+12+in,record+12):ext26_bandwidth(d,tag,record+12+in,record+12); + if(ack)dcp_ack(d,DCP_CONTEXT_CB); +} +static void dcp_send_message(struct apple_dcp *d,u8 ep,u64 message){ + assert(ep==IOMFB_ENDPOINT);enum dcp_context_id context=FIELD_GET(IOMFB_MSG_CONTEXT,message); + if(message&IOMFB_MSG_ACK){callback_acks++;return;} + unsigned offset=FIELD_GET(IOMFB_MSG_OFFSET,message);struct dcp_packet_header *h=(void *)(d->shmem+dcp_tx_offset(context)+offset);u8 *in=(u8 *)(h+1),*out=in+h->in_len; + const char *tags[]={"983A","644A","920A","864A","000A","564A"};unsigned inputs[]={0,0,0,4,4,0},outputs[]={0,4,0,0,4,4}; + assert(commands<6&&!memcmp(h->tag,tags[commands],4)&&h->in_len==inputs[commands]&&h->out_len==outputs[commands]); + assert(context==DCP_CONTEXT_CB&&offset>=64);if(h->in_len)assert(get_unaligned_le32(in)==1); + unsigned call=++commands; + if(call==5){callback(d,3,4,60,64);assert(get_unaligned_le32(d->shmem+0x60000+64+12+4+56)==1);} + if(h->out_len){u32 value=call>=5?1:0;if(bad_status==call)value=0;if(byte_reply)*out=value;else put_unaligned_le32(value,out);} + if(corrupt==call)h->tag[0]^=1; + dcpep_handle_ack(d,context); +} +int main(void){ + for(unsigned full=0;full<2;full++){ + memset(memory,0,sizeof(memory));struct apple_dcp d={.shmem=memory,.native=true,.fw_compat=1,.iomfb.owner=current};d.ch_cmd.depth=1;commands=callback_acks=0;byte_reply=!full; + callback(&d,121,0,4,0);assert(!d.crashed&&commands==6&&callback_acks==2&&!d.ch_cb.depth&&get_unaligned_le32(memory+0x60000+12)==1); + } + for(bad_status=5;bad_status<=6;bad_status++){ + memset(memory,0,sizeof(memory));struct apple_dcp d={.shmem=memory,.native=true,.fw_compat=1,.iomfb.owner=current};d.ch_cmd.depth=1;commands=callback_acks=0;byte_reply=true;callback(&d,121,0,4,0);assert(d.crashed&&commands==bad_status&&callback_acks==1&&d.ch_cb.depth==1); + } + bad_status=0; + for(corrupt=1;corrupt<=6;corrupt++){ + memset(memory,0,sizeof(memory));struct apple_dcp d={.shmem=memory,.native=true,.fw_compat=1,.iomfb.owner=current};d.ch_cmd.depth=1;commands=callback_acks=0;callback(&d,121,0,4,0);assert(d.crashed&&commands==corrupt); + } + puts("actual dcp_push/shared memory/ACK/D121 nested D003: Boolean byte and word replies pass, semantic refusal and corrupt command records stop successors and retain the session"); +} +''' +run('native D121 through actual shared-memory command and ACK bodies',transport) +if args.capture: + capture=json.loads(args.capture.read_text());assert capture['candidate_raw_messages_logged'] is False + for record in capture['daily_afk_prefixes']: + b=bytes.fromhex(record['hex']);assert b[:4]==b'IOP ' and len(b)==128 + declared=int.from_bytes(b[20:24],'little')+8 + assert len(b)-16dcp->fw_compat == DCP_FIRMWARE_H17P &&\s*!dcp_is_ext26\(ep->dcp\)', 'ep->dcp->fw_compat == DCP_FIRMWARE_H17P', identity) + if old_alias==identity:raise RuntimeError('identity mutation missed') + run('reject restored panel interface-id alias on native port',old_alias,True) + old_retry=identity.replace('(!dcp_is_ext26(ep->dcp) && ep != ep->dcp->dcpavservep)', 'ep != ep->dcp->dcpavservep') + if old_retry==identity:raise RuntimeError('retry mutation missed') + run('reject restored EDID-only deferred OPEN queue',old_retry,True) + old_padding=transport.replace('dcp_is_ext26(dcp) ? 0 : 0xff', '0xff') + if old_padding==transport:raise RuntimeError('padding mutation missed') + run('reject restored native all-ones Boolean reply padding',old_padding,True) diff --git a/tools/asahi/dcpext26-wire-tests.py b/tools/asahi/dcpext26-wire-tests.py new file mode 100644 index 0000000000000..ea465f215f33e --- /dev/null +++ b/tools/asahi/dcpext26-wire-tests.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Execute the production native26 encoder against independent byte fixtures.""" +import ctypes as C +import os +from pathlib import Path +import struct +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[2] +SIZE = 0x1BD8 + + +class Primary(C.Structure): + _fields_ = [("address", C.c_uint64), ("backing", C.c_uint64), + ("width", C.c_uint32), ("height", C.c_uint32), ("pitch", C.c_uint32)] + + +def fixture(layer, address, width, height, pitch, swap_id=17): + """Byte schema, including every zero byte, independent of C field writes.""" + fields = {0x98: struct.pack('\n#include \ntypedef uint8_t u8; typedef uint32_t u32; typedef uint64_t u64;\n', + 'errno.h': '#ifdef __linux__\n#include \n#else\n#include \n#endif\n', 'string.h': '#include \n', + 'unaligned.h': '''#include +#include +static inline void put_unaligned_le32(uint32_t v, void *p) { + unsigned char *b=p; for (unsigned i=0;i<4;i++) b[i]=v>>(8*i); +} +static inline void put_unaligned_le64(uint64_t v, void *p) { + unsigned char *b=p; for (unsigned i=0;i<8;i++) b[i]=v>>(8*i); +} +static inline uint32_t get_unaligned_le32(const void *p) { + const unsigned char *b=p; uint32_t v=0; + for (unsigned i=0;i<4;i++) { v|=(uint32_t)b[i]<<(8*i); } + return v; +} +''', + }.items(): + (p / 'linux' / name).write_text(body) + (p / 'test.c').write_text('''#include "dcpext26-wire.h" +int encode(void *b, size_t n, u32 id, unsigned layer, const struct dcpext26_primary *p) { + return dcpext26_primary_encode(b,n,id,layer,p); +} +int start(const void *r,size_t n,u32 *id) { return dcpext26_start_reply(r,n,id); } +int submit(const void *r,size_t n) { return dcpext26_submit_reply(r,n); } +''') + exe = p / 'wire.so' + subprocess.run([os.environ.get('CC', 'cc'), '-shared', '-fPIC', '-O2', + '-Wall', '-Wextra', '-Werror', '-I' + str(p), + '-I' + str(ROOT / 'drivers/gpu/drm/apple'), str(p / 'test.c'), + '-o', str(exe)], check=True) + cls.lib = C.CDLL(str(exe)) + cls.lib.encode.argtypes = [C.c_void_p, C.c_size_t, C.c_uint32, + C.c_uint, C.POINTER(Primary)] + cls.lib.start.argtypes = [C.c_void_p, C.c_size_t, C.POINTER(C.c_uint32)] + cls.lib.submit.argtypes = [C.c_void_p, C.c_size_t] + + @classmethod + def tearDownClass(cls): + cls.temp.cleanup() + + def test_complete_surface_records(self): + for layer in range(4): + for w, h, pitch in [(3840, 2160, 15360), (1920, 1080, 8192), (1, 1, 4)]: + p = Primary(0x12345678000, pitch * h + 4096, w, h, pitch) + b = C.create_string_buffer(SIZE + 2) + C.memset(b, 0xCD, SIZE + 2) + self.assertEqual(self.lib.encode(C.byref(b, 1), SIZE, 17, layer, C.byref(p)), 0) + self.assertEqual(b.raw[1:-1], fixture(layer, p.address, w, h, pitch)) + self.assertEqual(b.raw[0], 0xCD) + self.assertEqual(b.raw[-1], 0xCD) + + def test_null_surface(self): + b = C.create_string_buffer(SIZE) + self.assertEqual(self.lib.encode(b, SIZE, 17, 0, None), 0) + self.assertEqual(b.raw, fixture(0, 0, 0, 0, 0)) + + def test_invalid_inputs_preserve_buffer(self): + good = (0x100000, 33177600, 3840, 2160, 15360) + cases = [(0, *good[1:]), (good[0], 4, *good[2:]), + (*good[:2], 0, *good[3:]), (*good[:3], 0, good[4]), + (*good[:4], 15359), (*good[:4], 16), + (2**64-4, *good[1:]), (good[0], 2**64-1, 1, 2**32-1, 8)] + for values in cases: + b = C.create_string_buffer(b'\xcd' * SIZE, SIZE) + p = Primary(*values) + self.assertLess(self.lib.encode(b, SIZE, 17, 0, C.byref(p)), 0) + self.assertEqual(b.raw, b'\xcd' * SIZE) + p = Primary(*good) + for length, swap_id, layer in [(SIZE-1, 17, 0), (SIZE+1, 17, 0), + (SIZE, 0, 0), (SIZE, 17, 4), (SIZE, 17, 2**32-1)]: + b = C.create_string_buffer(b'\xcd' * SIZE, SIZE) + self.assertLess(self.lib.encode(b, length, swap_id, layer, C.byref(p)), 0) + self.assertEqual(b.raw, b'\xcd' * SIZE) + + def test_reply_lengths_and_status_offsets(self): + ident = C.c_uint32(0xFFFFFFFF) + for n in range(20): + start = C.create_string_buffer(struct.pack(' +#include +#include +#include +#include +#include +#include +typedef uint8_t u8; typedef uint32_t u32; typedef uint64_t u64; +#define BIT(x) (1U<<(x)) +#define BIT_ULL(x) (1ULL<<(x)) +#define __ffs64(x) __builtin_ctzll(x) +static u32 get_unaligned_le32(const void *p) { const u8 *b=p; return (u32)b[0]|(u32)b[1]<<8|(u32)b[2]<<16|(u32)b[3]<<24; } +static u64 get_unaligned_le64(const void *p) { return get_unaligned_le32(p)|(u64)get_unaligned_le32((const u8 *)p+4)<<32; } +static void put32(u32 v,void *p) { u8 *b=p; for(unsigned i=0;i<4;i++) b[i]=v>>(8*i); } +struct device_node { bool available; struct device_node *report; }; +struct device { struct device_node *of_node; }; +struct apple_pmp_report { struct device *dev; struct device_node *native_pmp; }; +static struct device_node chosen, report, suppliers[2]; +static bool board=true, native=true, optin=true, baduuid; +static int nodes=1, nt=20*0x7c, ng=8, nr=64; +static u8 table[20*0x7c], group[8], ranges[64]; +#define dev_info(...) ((void)0) +#define dev_dbg(...) ((void)0) +#define for_each_compatible_node(np,t,c) for(int it=0;itavailable; } +static struct device_node *of_parse_phandle(struct device_node *p,const char *k,int i) { (void)k;(void)i;return p->report; } +static int of_property_read_string(struct device_node *p,const char *k,const char **s) { + (void)p;(void)k;*s=baduuid?"bad":"EFD60284-7C58-33A9-8522-CB3366AF6040";return 0; +} +static const void *of_get_property(struct device_node *p,const char *k,int *len) { + (void)p; + if (!strcmp(k,"asahi,j613-25g83-dcpext")) { *len=2;return optin?"1":NULL; } + if (!strcmp(k,"apple,tunable-soc-device")) { *len=nt;return table; } + if (!strcmp(k,"apple,tunable-soc-device-ps-group")) { *len=ng;return group; } + if (!strcmp(k,"apple,tunable-ptd-range")) { *len=nr;return ranges; } + return NULL; +} +''' +main = r''' +static void reset(void) { + board=native=optin=true;baduuid=false;nodes=1;nt=sizeof(table);ng=8;nr=64; + memset(table,0,sizeof(table));memset(group,0,sizeof(group));memset(ranges,0,sizeof(ranges)); + for(unsigned i=0;i<20;i++) put32(100+i,table+i*0x7c); + put32(8,table+11*0x7c);put32(9,table+12*0x7c);put32(18,table+19*0x7c); + put32(2,table+11*0x7c+8);put32(2,table+12*0x7c+8); + memcpy(table+11*0x7c+0x74,"DISP",5);memcpy(table+12*0x7c+0x74,"DISPEXT",8); + memcpy(table+19*0x7c+0x74,"ANS",4);put32(0x3fff0,group); + put32(10,ranges);put32(256,ranges+4);put32(8,ranges+8); + put32(11,ranges+32);put32(264,ranges+36);put32(8,ranges+40); + for(unsigned i=0;i<2;i++) { suppliers[i].available=true;suppliers[i].report=&report; } +} +static int admit(void) { struct device d={.of_node=&report};struct apple_pmp_report r={.dev=&d};return apple_pmp_native_records(&r); } +int main(void) { + reset();assert(!admit()); + reset();optin=false;board=false;assert(!admit()); /* Off leaves the old path intact. */ + reset();native=false;assert(!admit()); + reset();board=false;assert(admit()); + reset();nodes=0;assert(admit()); + reset();nodes=2;assert(admit()); + reset();baduuid=true;assert(admit()); + reset();suppliers[0].available=false;assert(admit()); + reset();nt--;assert(admit()); + reset();ng--;assert(admit()); + reset();nr--;assert(admit()); + reset();group[0]^=1;assert(admit()); + for(unsigned i=0;i<20;i++) { + if(i==11 || i==12 || i==19) continue; + reset();put32(8,table+i*0x7c);assert(admit()); + } + const unsigned positions[]={11,12,19}; + for(unsigned i=0;i<3;i++) { + reset();table[positions[i]*0x7c+0x74]^=1;assert(admit()); + reset();table[positions[i]*0x7c+8]^=2;assert(admit()); + } + reset();put32(257,ranges+4);assert(admit()); + puts("PASS native26 PTD personality, unique ordinals, acknowledgment and range refusals"); + return 0; +} +''' +with tempfile.TemporaryDirectory() as tmp: + p = Path(tmp) / 'native.c' + p.write_text(pre + validation + body + main) + binary = p.with_suffix('') + subprocess.run([os.environ.get('CC', 'cc'), '-std=gnu11', '-Wall', '-Wextra', '-Werror', + '-Wno-unused-function', '-O2', str(p), '-o', str(binary)], check=True) + subprocess.run([str(binary)], check=True) diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 5dfda83574ea6..d60c684e25b7c 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -53,6 +53,10 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { + 'dcpext26-startup-tests.py': [([], PASS)], + 'dcpext26-wire-tests.py': [([], PASS)], + 'pmp-native26-tests.py': [([], PASS)], + 'test-m3-power.py': [([], PASS)], 'test-m3-uapi.py': [([], PASS)], 'test-m3-mailbox.py': [([], PASS)], diff --git a/tools/asahi/t6030-pmp-graph-tests.c b/tools/asahi/t6030-pmp-graph-tests.c index ca1cde04f1363..c6db59c99e6af 100644 --- a/tools/asahi/t6030-pmp-graph-tests.c +++ b/tools/asahi/t6030-pmp-graph-tests.c @@ -162,6 +162,8 @@ struct apple_pmp_report { unsigned int polls; struct delayed_work ready_work; struct apple_pmp_ane ane; + struct device_node *native_pmp; + u64 native_claimed; }; struct completion { bool done; @@ -200,6 +202,7 @@ struct apple_pmp_report_entry { bool sleep_release, released; u32 dvfs_entry; u64 dvfs_on; + struct apple_pmp_report *native_owner; }; #define PMP_DVFS_VALID BIT_ULL(61) #define genpd_to_apple_pmp_report_entry(p) \ @@ -728,6 +731,13 @@ static const struct apple_pmp_start_ops apple_pmp_start_hw_ops = { hw_check, hw_bound }; static const struct apple_pmp_start_timing apple_pmp_start_timing = { 2, 0, 2, 2 }; +/* Native26 table admission is exercised by its separate actual-body fixture. */ +static int apple_pmp_native_records(struct apple_pmp_report *rep) +{ + assert(!rep->native_pmp); + return 0; +} + #include "production.h" #if HOST_HAS_DRAM static int devm_device_add_group(struct device *dev, const struct attribute_group *group) diff --git a/tools/asahi/t6030-pmp-graph-tests.py b/tools/asahi/t6030-pmp-graph-tests.py index 4c85001acf1a8..79b9522b7a325 100644 --- a/tools/asahi/t6030-pmp-graph-tests.py +++ b/tools/asahi/t6030-pmp-graph-tests.py @@ -36,7 +36,7 @@ def main(): 'apple,irq-safe;' in node)) assert len(entries) == 4 entry_data = ',\n'.join('{%d, %d, %d, %d, %d}' % e for e in entries) - names = ['static int apple_pmp_report_bit(', 'static int apple_pmp_start_check_layout(', + names = ['static int apple_pmp_report_record(', 'static int apple_pmp_report_bit(', 'static int apple_pmp_start_check_layout(', 'static int apple_pmp_start_describe(', 'static void apple_pmp_start_finish(', 'static bool apple_pmp_start_stopping(', 'static void apple_pmp_start_work(', 'static void apple_pmp_start_drain(', 'static int apple_pmp_start_init(', diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py index c15ae40d30b8d..96dae4db81605 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/common.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/common.py @@ -63,10 +63,68 @@ def tipd_pm_header(root): '#define clamp(val, lo, hi) ({ __auto_type _v = (val); ' '__typeof__(_v) _lo = (lo), _hi = (hi); _v < _lo ? _lo : _v > _hi ? _hi : _v; })') +def classic_unit(unit): + # This suite exercises established profiles. Native26 has separate actual-body + # fixtures; model its optional branch and storage without changing assertions. + if 'dcp_is_ext26(' in unit and not re.search(r'^static [^\n]+ dcp_is_ext26\(', unit, re.M): + unit = '#define dcp_is_ext26(d) 0\n' + unit + # Native-only diagnostic/OPEN branches still need declarations when parsed. + for token, value in (('SYSTEM_ENDPOINT', '0x20'), ('DISP0_ENDPOINT', '0x23')): + if token in unit and not re.search(r'#define ' + token + r'\s', unit): + unit = '#define ' + token + ' ' + value + '\n' + unit + if 'dptx_native_controllers_ready(' in unit: + unit = '#define dptx_native_controllers_ready(d) 0\n' + unit + if 'dcp_is_retained_external(' in unit and not re.search(r'^(?:static )?bool dcp_is_retained_external\(', unit, re.M): + predicate = 'dcp_is_external_h17p(d)' if 'dcp_is_external_h17p(' in unit else '((d)->hw.iomfb_method_profile == DCP_IOMFB_METHODS_H17P_EXTERNAL)' + unit = '#define dcp_is_retained_external(d) ' + predicate + '\n' + unit + if 'ext26_' in unit and 'struct apple_dcp {' in unit: + mock = block(unit, 'struct apple_dcp {') + fields = [] + for name in ('ext26_pm_preparing', 'ext26_stopping', 'ext26_normal', 'ext26_blocked', 'external_stopped', 'crashed', 'ext26_services_open', 'ext26_client_open', 'ext26_edid_tried'): + if name not in mock: + fields.append('bool ' + name + ';') + if 'ext26_edid_generation' not in mock: + fields.append('u64 ext26_edid_generation;') + if 'ext26_aux' not in mock: + fields.append('struct apple_dcp_afkep *ext26_aux[3];') + if 'ext26_blank_work' not in mock: + fields.append('int ext26_blank_work;') + for name in ('systemep', 'ibootep', 'dptx_controller_ep', 'dptxep', 'dcpavservep'): + if 'dcp->' + name in unit and not re.search(r'\b' + name + r'\b', mock): + fields.append('struct apple_dcp_afkep *' + name + ';') + if 'dcp->rtk' in unit and not re.search(r'\brtk\b', mock): + fields.append('void *rtk;') + unit = unit.replace('struct apple_dcp {', 'struct apple_dcp {\n' + '\n'.join(fields), 1) + if 'struct apple_dcp_afkep *endpoints' in unit and 'struct apple_dcp_afkep {' not in unit: + unit = 'struct apple_dcp_afkep { void *wq; };\n' + unit + for name, expression in (('afk_host_idle', '1'), ('iomfb_ext26_retain_epoch', '0'), + ('apple_rtkit_flush_rx', '((void)0)'), ('flush_workqueue', '((void)0)'), + ('flush_work', '((void)0)'), ('queue_work', '0'), + ('dcp_modes_begin_attachment', '((void)0)')): + if name + '(' in unit and not re.search(r'^(?:static )?[^\n;]+\b' + name + r'\([^;]*\)\s*\{', unit, re.M): + unit = '#define ' + name + '(...) ' + expression + '\n' + unit + if 'struct apple_dcp_afkep *endpoints' in unit: + unit = '#ifndef ARRAY_SIZE\n#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*(a)))\n#endif\n#ifndef IS_ERR_OR_NULL\n#define IS_ERR_OR_NULL(p) (!(p))\n#endif\n' + unit + if 'WRITE_ONCE(' in unit and '#define WRITE_ONCE' not in unit: + unit = '#define WRITE_ONCE(x,v) ((x)=(v))\n' + unit + if 'DPAVSERV_ENDPOINT' in unit and not re.search(r'#define DPAVSERV_ENDPOINT\s', unit): + unit = '#define DPAVSERV_ENDPOINT 0x28\n' + unit + if 'dcp_direct_crossbar_link(' in unit and not re.search(r'^(?:static )?[^\n;]+\bdcp_direct_crossbar_link\([^;]*\)\s*\{', unit, re.M): + unit = '#define dcp_direct_crossbar_link(d,on) 0\n' + unit + if 'ext26_prop_start(' in unit: + unit = '#define ext26_prop_start(d,n) 0\n' + unit + if 'dcp->chunks.received' in unit: + unit = unit.replace('void *data;\n size_t length;', 'void *data;\n size_t received; bool contiguous; size_t length;') + if 'ext26_command_bytes' in unit: + unit = unit.replace('struct apple_dcp_afkep {', 'struct apple_dcp_afkep { size_t ext26_command_bytes; unsigned ext26_command_count;', 1) + unit = '#ifndef ALIGN\n#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1))\n#endif\n#ifndef SZ_16K\n#define SZ_16K 16384UL\n#endif\n#ifndef SZ_16M\n#define SZ_16M (16UL<<20)\n#endif\n' + unit + return unit + def run_unit(name, unit, output, cases=((),), includes=(), extra=()): work = output / name work.mkdir(parents=True, exist_ok=True) source = work / 'host.c' + unit = classic_unit(unit) source.write_text(unit) command = shlex.split(os.environ.get('HOSTCC', 'clang')) + [ '-std=gnu11', '-Wall', '-Werror', '-O1', '-g', diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py b/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py index 9b949a1a6ff1d..eb19bc2693c18 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/edid.py @@ -854,9 +854,9 @@ def function(text, signature): struct dcp_desktop_state { struct apple_dcp_afkep *graph[0x20]; } *desktop; }; struct afk_receive_message_work { struct apple_dcp_afkep *ep; u64 message; struct work_struct work; }; -struct apple_rtkit_ops { void (*recv_message)(void *,u8,u64); }; +struct apple_rtkit_ops { void (*recv_message)(void *,u8,u64); void (*recv_message_timed)(void *,u8,u64,u64); }; struct apple_rtkit { bool shutting_down, sync_syslog_ack, pmp_registry_v2; const struct apple_rtkit_ops *ops; void *cookie,*dev; }; -struct apple_rtkit_rx_work { struct apple_rtkit *rtk; u8 ep; u64 msg; struct work_struct work; }; +struct apple_rtkit_rx_work { u64 received_ns; struct apple_rtkit *rtk; u8 ep; u64 msg; struct work_struct work; }; #define current ((void *)1) static pthread_mutex_t reply_lock=PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t reply_ready=PTHREAD_COND_INITIALIZER; diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py index 80dfb5a517e6c..e865cff4daeee 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/lifecycle.py @@ -1378,7 +1378,8 @@ def function(source, marker): # The T8122 dcpext wrapper moved the pipe's probe into dcp_platform_probe_pipe(). probe = function(dcp, 'static int dcp_platform_probe_pipe(' if 'static int dcp_platform_probe_pipe(' in dcp else 'static int dcp_platform_probe(') - publish = probe[probe.rindex('\tif (dcp->hw.iomfb_method_profile ==\n\t DCP_IOMFB_METHODS_H17P_EXTERNAL) {'):] + publish_marker = '\tif (dcp_is_retained_external(dcp)) {' if 'dcp_is_retained_external(dcp)' in probe else '\tif (dcp->hw.iomfb_method_profile ==\n\t DCP_IOMFB_METHODS_H17P_EXTERNAL) {' + publish = probe[probe.rindex(publish_marker):] # Which processor must come up before the boot framebuffer is taken over. header = root / 'drivers/gpu/drm/apple/apple_boot_display.h' boot_display = '' diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py b/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py index 0f6a1591408c7..0a21fa195ded3 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/recovery.py @@ -7,6 +7,7 @@ """ from pathlib import Path import json, hashlib, subprocess +from common import classic_unit def check(root, output): import edid @@ -276,6 +277,7 @@ def extract(text,signature): includes=[root/'drivers/gpu/drm/apple',root/'tools/testing/selftests/drivers/gpu/apple-afkv2/host-include'] results=[] def run(name,text,cases): + text=classic_unit(text) area=out/name;area.mkdir(exist_ok=True);(area/'host.c').write_text(text) cmd=['clang','-std=gnu11','-Wall','-Werror','-Wno-unused-function','-O1','-g','-fsanitize=address,undefined','-fno-omit-frame-pointer']+['-I'+str(p) for p in includes]+[str(area/'host.c'),'-o',str(area/'host')] (area/'command.json').write_text(json.dumps(cmd,indent=2)+'\n') From 9a5ea4bddf35e1745ac46c03dec6d63c9b13931e Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 13:55:11 -0500 Subject: [PATCH 06/32] selftests: apple: refresh display and PMP host boundaries Signed-off-by: Satya Benson --- tools/asahi/t6030-pmp-graph-tests.c | 1 + tools/asahi/t6030-pmp-graph-tests.py | 2 +- tools/asahi/t6030-pmp-supplier-tests.c | 9 +++++++++ .../drivers/gpu/apple-dcp/plane-layout.py | 17 ++++++++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/tools/asahi/t6030-pmp-graph-tests.c b/tools/asahi/t6030-pmp-graph-tests.c index c6db59c99e6af..a54962fe4b1b0 100644 --- a/tools/asahi/t6030-pmp-graph-tests.c +++ b/tools/asahi/t6030-pmp-graph-tests.c @@ -24,6 +24,7 @@ typedef uint8_t u8; #define GFP_KERNEL 0 #define GENPD_FLAG_ACTIVE_WAKEUP 1 #define GENPD_FLAG_IRQ_SAFE 2 +#define GENPD_FLAG_ALWAYS_ON 4 #define MAX_PHANDLE_ARGS 4 #define EPROBE_DEFER 517 #define SZ_4K 4096 diff --git a/tools/asahi/t6030-pmp-graph-tests.py b/tools/asahi/t6030-pmp-graph-tests.py index 79b9522b7a325..7b05e85536f45 100644 --- a/tools/asahi/t6030-pmp-graph-tests.py +++ b/tools/asahi/t6030-pmp-graph-tests.py @@ -112,7 +112,7 @@ def main(): ('no-managed-owner', '.starts_pmp = IS_ENABLED(CONFIG_APPLE_T6030_GPU_PMP_LINK)', '.starts_pmp = false'), ('no-managed-probe', 'ret = apple_pmp_start_init(rep);', 'ret = 0; (void)apple_pmp_start_init;'), ('no-child-seeding', 'apple_pmp_report_entry_set_state(&ent->genpd, true);', '(void)0;'), - ('no-storage-policy', 'ent->no_ack = of_property_read_bool(node, "apple,no-ack");', 'ent->no_ack = false;'), + ('no-storage-policy', 'ent->no_ack = of_property_read_bool(node, "apple,no-ack");\n\t}', 'ent->no_ack = false;\n\t}'), ('external-startup-ack', '!of_property_read_bool(child, "apple,defer-startup-ack")', 'true'), ('ignore-seeded-state', 'status || request != st->seed', 'status'), ('ignore-halted-state', 'status || request != st->seed', 'request != st->seed'), diff --git a/tools/asahi/t6030-pmp-supplier-tests.c b/tools/asahi/t6030-pmp-supplier-tests.c index ecb6dcec9fff5..ac630f7a5b046 100644 --- a/tools/asahi/t6030-pmp-supplier-tests.c +++ b/tools/asahi/t6030-pmp-supplier-tests.c @@ -47,6 +47,7 @@ struct apple_pmp_report_offsets { const char *name, *fallback, *pwrstate; }; struct apple_pmp_report { + struct device_node *native_pmp; void *base; const struct apple_pmp_report_offsets *offsets; struct device *dev; @@ -84,6 +85,14 @@ static void rcu_read_unlock(void) { assert(rcu_depth-- == 1); } +static int apple_pmp_report_record(struct device_node *pmp, + struct device_node *entry, u32 *id, bool *record) +{ + (void)pmp; + *id = entry->reg; + *record = false; + return 0; +} static int apple_pmp_report_bit(struct apple_pmp_start *st, struct device_node *entry, u32 *id, bool *record) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py b/tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py index e7b12e5bdbb59..c00431b1e9b53 100755 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/plane-layout.py @@ -110,15 +110,26 @@ def unit(root, ref=None, mutation=None, callback_ref=None): struct drm_rect { int x1,y1,x2,y2; }; enum drm_color_range { DRM_COLOR_YCBCR_LIMITED_RANGE, DRM_COLOR_YCBCR_FULL_RANGE }; enum drm_color_encoding { DRM_COLOR_YCBCR_BT601, DRM_COLOR_YCBCR_BT709, DRM_COLOR_YCBCR_BT2020 }; -struct drm_crtc_state { struct { int hdisplay,vdisplay; } mode; }; -struct drm_plane_state { void *crtc; bool visible; struct drm_framebuffer *fb; struct drm_rect src,dst; enum drm_color_range color_range; enum drm_color_encoding color_encoding; }; +struct drm_crtc_state { struct { int hdisplay,vdisplay; } mode; bool vrr_enabled; }; +struct drm_plane_state { u32 rotation,src_x,src_y,src_w,src_h; int crtc_x,crtc_y,crtc_w,crtc_h; void *crtc; bool visible; struct drm_framebuffer *fb; struct drm_rect src,dst; enum drm_color_range color_range; enum drm_color_encoding color_encoding; }; struct drm_plane; struct drm_plane_funcs { bool (*format_mod_supported)(struct drm_plane *,u32,u64); }; -struct drm_plane { struct { u32 id; } base;struct drm_plane_state *state;u32 *format_types;unsigned int format_count;u64 *modifiers;unsigned int modifier_count;const struct drm_plane_funcs *funcs; }; +struct drm_plane { int type; struct { u32 id; } base;struct drm_plane_state *state;u32 *format_types;unsigned int format_count;u64 *modifiers;unsigned int modifier_count;const struct drm_plane_funcs *funcs; }; struct drm_atomic_commit { struct { void *dev; } *dev; }; ''' prefix += block(plane_header, 'struct apple_plane_state {') + ';\n' prefix += r''' +/* Layout controls use the legacy DCP; ext26 admission has its own suite. */ +#define DRM_PLANE_TYPE_PRIMARY 1 +#define DRM_MODE_ROTATE_0 1 +struct platform_device { int unused; }; +struct apple_dcp { int unused; }; +struct apple_crtc { struct platform_device *dcp; }; +static struct apple_crtc host_crtc; +static struct apple_crtc *to_apple_crtc(void *crtc) { return &host_crtc; } +static struct apple_dcp *platform_get_drvdata(struct platform_device *pdev) { return NULL; } +static bool dcp_is_ext26(struct apple_dcp *dcp) { return false; } +static bool iomfb_ext26_backing_fresh(struct apple_dcp *dcp,struct drm_framebuffer *fb) { return true; } #define to_apple_plane_state(p) container_of(p,struct apple_plane_state,base) static struct drm_crtc_state crtc_state = { .mode = { 3840,2160 } }; static struct drm_plane_state *drm_atomic_get_new_plane_state(struct drm_atomic_commit *c,struct drm_plane *p) { return p->state; } From 1149bbb2af71de5b1d9d302e3a1dd41ee5f8d926 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 3 Nov 2026 13:56:23 -0500 Subject: [PATCH 07/32] selftests: apple: keep child-seeding mutant compilable Signed-off-by: Satya Benson --- tools/asahi/t6030-pmp-graph-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/asahi/t6030-pmp-graph-tests.py b/tools/asahi/t6030-pmp-graph-tests.py index 7b05e85536f45..1184bc9840c41 100644 --- a/tools/asahi/t6030-pmp-graph-tests.py +++ b/tools/asahi/t6030-pmp-graph-tests.py @@ -111,7 +111,7 @@ def main(): controls = (dram_controls if has_dram else []) + [ ('no-managed-owner', '.starts_pmp = IS_ENABLED(CONFIG_APPLE_T6030_GPU_PMP_LINK)', '.starts_pmp = false'), ('no-managed-probe', 'ret = apple_pmp_start_init(rep);', 'ret = 0; (void)apple_pmp_start_init;'), - ('no-child-seeding', 'apple_pmp_report_entry_set_state(&ent->genpd, true);', '(void)0;'), + ('no-child-seeding', 'apple_pmp_report_entry_set_state(&ent->genpd, true);', '0;'), ('no-storage-policy', 'ent->no_ack = of_property_read_bool(node, "apple,no-ack");\n\t}', 'ent->no_ack = false;\n\t}'), ('external-startup-ack', '!of_property_read_bool(child, "apple,defer-startup-ack")', 'true'), ('ignore-seeded-state', 'status || request != st->seed', 'status'), From 8403c86536df1e5bf2c74f9488f1027058774555 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:09:53 -0500 Subject: [PATCH 08/32] selftests: apple: register native26 checks with the Aurora runner Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcp.c | 6 +++--- tools/asahi/run-host-tests.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index f7d4f36a0cec4..5b5168fe7bf46 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -3161,7 +3161,7 @@ int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); - + if (dcp_is_retained_external(dcp)) { bool new_cable; @@ -4219,7 +4219,7 @@ void dcp_poweron(struct platform_device *pdev) return; } - + if (dcp_is_ext26(dcp)) flush_work(&dcp->ext26_blank_work); if (dcp_is_retained_external(dcp)) { @@ -4364,7 +4364,7 @@ void dcp_poweroff(struct platform_device *pdev) return; } - + if (dcp_is_ext26(dcp)) { if (!READ_ONCE(dcp->ext26_pm_preparing) && !READ_ONCE(dcp->external_stopped)) queue_work(system_unbound_wq, &dcp->ext26_blank_work); diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index d60c684e25b7c..2959cdf716d85 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -53,6 +53,15 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { + 'dcpext26-lifecycle-tests.py': [([], PASS)], + 'dcpext26-modeset-tests.py': [([], PASS)], + 'dcpext26-parity-tests.py': [([], PASS)], + 'dcpext26-present-tests.py': [([], PASS)], + 'dcpext26-profile-tests.py': [([], PASS)], + 'dcpext26-edid-tests.py': [([], PASS)], + 'dcpext26-changeset-tests.py': [([], PASS)], + 'dcpext26-admission-tests.py': [([], PASS)], + 'dcpext26-startup-tests.py': [([], PASS)], 'dcpext26-wire-tests.py': [([], PASS)], 'pmp-native26-tests.py': [([], PASS)], @@ -197,7 +206,7 @@ def jobs(work, conf, dtc): if re.search(r'-tests\.(py|rs|sh)$', p.name) and p.name != Path(__file__).name} known = set(ASAHI_TESTS) | set(ASAHI_RUST) | set(ASAHI_SHELL) missing = sorted(found - known) - gone = sorted(known - found) + gone = sorted(name for name in known if not (ASAHI / name).is_file()) if missing or gone: for name in missing: print(f'not registered in {Path(__file__).name}: tools/asahi/{name}', file=sys.stderr) From 6e5ebfad21a2002cc37399b19e6a80f50016d6da Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:12:10 -0500 Subject: [PATCH 09/32] drm/apple: preserve native26 startup and error handling on 7.3 Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/apple_drv.c | 5 ++++- drivers/gpu/drm/apple/dcp.c | 30 +++++++++++++++++++++++++++ tools/asahi/dcpext26-modeset-tests.py | 4 +++- tools/asahi/dcpext26-present-tests.py | 2 ++ tools/asahi/dcpext26-startup-tests.py | 6 ++++-- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index e6c1fd5e45381..4d3e2ad119e42 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -372,8 +372,11 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc, } if (crtc_state->active) { ret = dcp_crtc_atomic_modeset(crtc, commit); - if (ret) + if (ret) { + if (dcp_is_ext26(dcp)) + goto failed; dcp_drm_crtc_error(apple_crtc, crtc_state, ret); + } } dcp_typec_follow_done(locked); return; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 5b5168fe7bf46..eebbb98792bb3 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -5241,6 +5241,36 @@ static int dcp_ext_wait_pmp(struct apple_dcp *dcp) return 0; } +static int dcp_ext26_start_resident(struct apple_dcp *dcp) +{ + struct device *dev = dcp->dev; + u32 control; + int ret; + + ret = dcp_pin_live_session(dcp); + if (ret) + return ret; + control = readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dcp->rtk = apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops); + if (IS_ERR(dcp->rtk)) + return dev_err_probe(dev, PTR_ERR(dcp->rtk), "native26 RTKit mailbox initialization failed\n"); + ret = devm_add_action_or_reset(dev, dcp_release_rtkit, dcp); + if (ret) + return ret; + if (!(control & APPLE_DCP_COPROC_CPU_CONTROL_RUN)) { + writel(control | APPLE_DCP_COPROC_CPU_CONTROL_RUN, + dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL); + dev_dbg(dev, "native26 resident ASC started once after RTKit mailbox initialization\n"); + } + ret = control & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? + apple_rtkit_wake(dcp->rtk) : apple_rtkit_boot(dcp->rtk); + dev_dbg(dev, "native26 initial RTKit %s result=%d control=%#x status=%#x\n", + control & APPLE_DCP_COPROC_CPU_CONTROL_RUN ? "warm INIT" : "resident boot", + ret, readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL), + readl(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_STATUS)); + return ret; +} + static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *data) { struct device_node *panel_np; diff --git a/tools/asahi/dcpext26-modeset-tests.py b/tools/asahi/dcpext26-modeset-tests.py index 0c3afa1b0638d..d3c8df7d4374e 100644 --- a/tools/asahi/dcpext26-modeset-tests.py +++ b/tools/asahi/dcpext26-modeset-tests.py @@ -75,6 +75,8 @@ static unsigned drm_crtc_mask(struct drm_crtc *c){return 1;} static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} static unsigned done,woken,notified,invalidated,powered,programmed; +static unsigned classic_errors; +static void dcp_drm_crtc_error(struct apple_crtc *c, struct drm_crtc_state *s, int error){assert(error);classic_errors++;} static int mode_error; static int dcp_typec_follow_crtc(struct drm_crtc *c,struct drm_atomic_commit *a,bool *locked){*locked=true;return 0;} static void dcp_typec_follow_done(bool locked){assert(locked);done++;} @@ -104,7 +106,7 @@ apple_crtc_atomic_enable(&crtc,&commit);assert(done==1&&!woken&&!notified&&!invalidated); // Existing non-native error policy is preserved. d.native=false;done=woken=notified=invalidated=0;mode_error=-EIO; - apple_crtc_atomic_enable(&crtc,&commit);assert(done==1&&!woken&&!notified&&!invalidated); + apple_crtc_atomic_enable(&crtc,&commit);assert(done==1&&!woken&&!notified&&!invalidated&&classic_errors==1); } ''' with tempfile.TemporaryDirectory() as t: diff --git a/tools/asahi/dcpext26-present-tests.py b/tools/asahi/dcpext26-present-tests.py index 930c808a712b5..f26aa8b80ce0e 100644 --- a/tools/asahi/dcpext26-present-tests.py +++ b/tools/asahi/dcpext26-present-tests.py @@ -19,6 +19,8 @@ def function(name): #define WRITE_ONCE(x,y) ((x)=(y)) #define DCP_FW_NAME(n) n #define DCP_FW_UNION(x) ((x).h17p) +#define DCP_FW_SWAP(d) ((d)->swap.h17p) +#define DCP_FW_DESKTOP_26(x) 0 #define dev_err(...) ((void)0) #define trace_iomfb_swap_submit(...) ((void)0) struct dc_swap_complete_resp { u32 swap_id; }; diff --git a/tools/asahi/dcpext26-startup-tests.py b/tools/asahi/dcpext26-startup-tests.py index 17deb3d8da6c1..43bfda7ccb806 100644 --- a/tools/asahi/dcpext26-startup-tests.py +++ b/tools/asahi/dcpext26-startup-tests.py @@ -87,11 +87,12 @@ def run(name,code,reject=False): struct dcp_parse_ctx {void *dcp;const void *blob;u32 pos,len;}; struct apple_epic_service;struct apple_dcp_afkep; struct apple_epic_service_ops {const char *name;bool reusable,optional;int (*prepare)(struct apple_epic_service *,const char *,const char *,s64);int (*revalidate)(struct apple_epic_service *,const char *,const char *,s64);void (*init)(struct apple_epic_service *,const char *,const char *,s64);void (*teardown)(struct apple_epic_service *);}; -struct apple_epic_service {struct apple_dcp_afkep *ep;const struct apple_epic_service_ops *ops;bool enabled,torndown,pending_open;unsigned users;int lock;u32 channel;void *cookie;const char *pending_name,*pending_class;s64 pending_unit;int cmd_map[1];}; +struct apple_epic_service {struct apple_dcp_afkep *ep;const struct apple_epic_service_ops *ops;bool enabled,torndown,pending_open,defer_start;unsigned users;int lock;u32 channel;void *cookie;const char *pending_name,*pending_class;s64 pending_unit;int cmd_map[1];}; struct dptx_port {struct apple_epic_service *service;s64 unit;bool enabled,connected;int enable_completion;}; struct apple_dcp {bool native,ext26_services_open;int fw_compat,index,dptx_controller_ready;struct {unsigned num_dptx_ports;} hw;struct dptx_port dptxport[2];void *dev;struct apple_dcp_afkep *systemep,*ibootep,*dptx_controller_ep,*dcpavservep,*dptxep,*ext26_aux[3];}; -struct apple_dcp_afkep {struct apple_dcp *dcp;u32 endpoint,num_channels;bool wire_v2,stopping,match_epic_name;int lock;void *wq,*service_work;const struct apple_epic_service_ops *ops;struct apple_epic_service services[AFK_MAX_CHANNEL];}; +struct apple_dcp_afkep {struct apple_dcp *dcp;u32 endpoint,num_channels;bool wire_v2,stopping,match_epic_name,compact;int lock;void *wq,*service_work;const struct apple_epic_service_ops *ops;struct apple_epic_service services[AFK_MAX_CHANNEL];}; static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static bool dcp_fail_stop(struct apple_dcp *d){return d->native;} static bool dcp_is_v14_7_external(struct apple_dcp *d){return false;} static bool dcp_fw_is_14_7(struct apple_dcp *d){return d->fw_compat==DCP_FIRMWARE_V_14_7;} static size_t strscpy(char *d,const void *v,size_t n){const char *s=v;size_t l=strnlen(s,n-1);memcpy(d,s,l);d[l]=0;return l;} @@ -224,6 +225,7 @@ def run(name,code,reject=False): struct apple_dcp {void *dev;u8 *shmem;int fw_compat;bool native,crashed,quiescing,ext26_blocked,ext26_power_sent;u64 ext26_power_ns;struct {int iomfb_method_profile;}hw;struct {void *owner;} iomfb;struct dcp_channel ch_cb,ch_cmd,ch_oobcb,ch_oobcmd,ch_async,ch_oobasync;}; static bool iomfb_uses_queue(struct apple_dcp *d){return true;} static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static bool dcp_fail_stop(struct apple_dcp *d){return d->native;} static int iomfb_queue_command(struct apple_dcp *d,bool o,const struct dcp_method_entry *m,u32 i,u32 n,void *p,dcp_callback_t cb,void *c){abort();} static u64 ktime_get_ns(void){return 1;} static void dcp_send_message(struct apple_dcp *,u8,u64); From 745507dc4220a00f30f457278fb79384584e4c7b Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:12:49 -0500 Subject: [PATCH 10/32] selftests: apple: select the H17 allocation profile in native fixtures Signed-off-by: Satya Benson --- tools/asahi/dcpext26-parity-tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/asahi/dcpext26-parity-tests.py b/tools/asahi/dcpext26-parity-tests.py index 4cb08bba288ed..08e8fddb71e0f 100644 --- a/tools/asahi/dcpext26-parity-tests.py +++ b/tools/asahi/dcpext26-parity-tests.py @@ -212,6 +212,7 @@ def run(name,code): #define DCP_FW_VERSION(a,b,c) ((a)*10000+(b)*100+(c)) #define DCP_FW_VER DCP_FW_VERSION(26,6,0) #define DCP_FW_NAME(n) n +#define DCP_FW_H17(x) 1 #define round_down(x,a) ((x)&~((__typeof__(x))(a)-1)) #define check_add_overflow(a,b,p) __builtin_add_overflow((a),(b),(p)) #define dev_warn(...) ((void)0) From a6b6a829031f47b3037823a9a4a3f6f1c1e84cbf Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:31:44 -0500 Subject: [PATCH 11/32] drm/apple: compose native crossbar admission with compact DPTX validation Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcp.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index eebbb98792bb3..e9b27ca6d1c2e 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -1793,14 +1793,13 @@ static int dcp_dptx_connect_raw(struct apple_dcp *dcp, u32 port, bool *retryable goto out_unlock; /* a tiled display's second half comes in on the port's dpin1 */ dfp_port = port && READ_ONCE(dcp->split.active) ? 2 : dcp->dptx_dfp_port; - /* Compact DPTX (desktop 26.x) has no connection validation call. */ - if (!dcp_ext_dptx_607(dcp) && !compact) { if (dcp_is_ext26(dcp)) { ret = dcp_direct_crossbar_link(dcp, false); if (ret) goto out_unlock; } - if (!dcp_ext_dptx_607(dcp)) { + /* Compact DPTX (desktop 26.x) has no connection validation call. */ + if (!dcp_ext_dptx_607(dcp) && !compact) { ret = dptxport_validate_connection(dcp->dptxport[port].service, dfp_port, dcp->dptx_phy, dcp->dptx_die); From ea520cf015d7b9d58e62f84a3438992f325e63f0 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 21:32:11 -0500 Subject: [PATCH 12/32] drm/apple: compose the retained external admission branch Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcp.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index e9b27ca6d1c2e..b8bf9a8abe5f7 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -2482,7 +2482,6 @@ int dcp_dptx_connect(struct apple_dcp *dcp, u32 port) if (dcp->fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP && !dcp_is_typec_output(dcp)) return dcp_desktop_dptx_connect(dcp, port); - if (!dcp_is_external_h17p(dcp)) { if (!dcp_is_retained_external(dcp)) { if (dcp_typec_sessionless(dcp)) { ret = dcp_dptx_connect_raw(dcp, port, NULL, NULL, false, false); From d051eaa60815051309efaf5b8b83473a6de84486 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 23:23:30 -0500 Subject: [PATCH 13/32] drm/apple: preserve desktop modeset and GPU RTKit interfaces on 7.3 Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/iomfb_template.h | 2 +- drivers/gpu/drm/asahi/g16/boot.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h index 716761d59a324..c94c3cd5fee20 100644 --- a/drivers/gpu/drm/apple/iomfb_template.h +++ b/drivers/gpu/drm/apple/iomfb_template.h @@ -384,7 +384,7 @@ struct drm_plane_state; int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) , struct drm_plane_state *primary #endif ); diff --git a/drivers/gpu/drm/asahi/g16/boot.rs b/drivers/gpu/drm/asahi/g16/boot.rs index e43778bb02a1f..b05185c2708b0 100644 --- a/drivers/gpu/drm/asahi/g16/boot.rs +++ b/drivers/gpu/drm/asahi/g16/boot.rs @@ -277,6 +277,7 @@ static RTKIT_OPS: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops { flags: 0, crashed: Some(rtkit_crashed), recv_message: Some(rtkit_recv), + recv_message_timed: None, recv_message_early: Some(rtkit_recv_early), shmem_setup: None, shmem_setup_ep: Some(rtkit_shmem_setup), From 414cc1169bd507cebef0259a65c9bc6d5fda3ae9 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 23:27:25 -0500 Subject: [PATCH 14/32] drm/apple: restrict native26 hooks to the H17 template lineage Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index afaffd1c4dd25..5674a4405fce7 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -1387,7 +1387,7 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp, /* Chunked data transfer for property dictionaries */ static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length) { -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) if (dcp_is_ext26(dcp)) return ext26_prop_start(dcp, *length); #endif @@ -2259,7 +2259,7 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp) u32 handle; #endif dev_info(dcp->dev, "dcp_poweron() starting\n"); -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) if (dcp_is_ext26(dcp)) return; /* Native26 publishes A473 only after a successful A413. */ #endif @@ -3574,7 +3574,7 @@ static void dcp_set_adaptive_sync(struct apple_dcp *dcp, u32 min_vrr, int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct drm_crtc_state *crtc_state -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) , struct drm_plane_state *primary #endif ) @@ -3584,7 +3584,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, struct dcp_color_mode *cmode = NULL; int ret; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) if (dcp_is_ext26(dcp)) { u32 pitch = primary && primary->fb ? primary->fb->pitches[0] : dcp->ext26_stride; @@ -3674,7 +3674,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, kref_get(&cookie->refcount); dcp->swap_submit_timestamp = 0; -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) if (dcp_is_ext26(dcp)) { ret = ext26_open_client(dcp); if (ret) { @@ -3740,7 +3740,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp, dev_info(dcp->dev, "set_digital_out_mode returned %#x\n", READ_ONCE(cookie->status)); } -#if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) +#if DCP_FW_H17(DCP_FW_VER) if (dcp_is_ext26(dcp)) { ret = READ_ONCE(cookie->replied) && !READ_ONCE(cookie->status) ? 0 : -EREMOTEIO; kref_put(&cookie->refcount, release_wait_cookie); From fac105d8513152b4d0e03cd48b134059681d5b92 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 4 Nov 2026 23:31:31 -0500 Subject: [PATCH 15/32] selftests: apple: follow the H17 modeset guard on 7.3 Signed-off-by: Satya Benson --- tools/asahi/dcpext26-modeset-tests.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/asahi/dcpext26-modeset-tests.py b/tools/asahi/dcpext26-modeset-tests.py index d3c8df7d4374e..0a3862e20a048 100644 --- a/tools/asahi/dcpext26-modeset-tests.py +++ b/tools/asahi/dcpext26-modeset-tests.py @@ -6,7 +6,9 @@ R=Path(__file__).resolve().parents[2];s=(R/'drivers/gpu/drm/apple/iomfb_template.c').read_text();caller=(R/'drivers/gpu/drm/apple/iomfb.c').read_text() a=s.index('\tif (dcp_is_ext26(dcp)) {',s.index('int DCP_FW_NAME(iomfb_modeset)')) b=s.index('\n\tif (!lookup_mode',a) -a=s.rfind('#if DCP_FW_VER',0,a) +a=s.rfind('#if DCP_FW_',0,a) +if a < 0: + raise RuntimeError('native modeset preprocessor guard not found') body=s[a:b] assert 'crtc_state->state' not in body assert 'drm_atomic_get_new_plane_state(commit, crtc->primary)' in caller @@ -18,6 +20,8 @@ typedef uint32_t u32; #define DCP_FW_VERSION(a,b,c) ((a)*10000+(b)*100+(c)) #define DCP_FW_VER DCP_FW_VERSION(26,6,0) +/* This fixture selects the native H17 template instance. */ +#define DCP_FW_H17(x) 1 #define READ_ONCE(x) (x) #define WRITE_ONCE(x,v) ((x)=(v)) struct mode {int id;}; From ce0575e474df6444e65d414cc23c7dfec5d7d1cc Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Thu, 5 Nov 2026 13:30:13 -0500 Subject: [PATCH 16/32] selftests: apple-afkv2: model the J613 ext26 command budget in the sender test The extracted sender now charges ext26 commands against a per-endpoint budget, refuses commands after a crash and latches the crash on a timeout. Give the test's DCP and endpoint the crashed and ext26_command_* fields and the size helpers it uses. Existing cases run with ext26 off; new cases cover the budget limits, the crash refusal and a timed-out ext26 command that keeps its DMA buffers. Signed-off-by: Satya Benson --- .../drivers/gpu/apple-afkv2/extract-send.py | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py index eb55dd281456f..191881806f58c 100644 --- a/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py +++ b/tools/testing/selftests/drivers/gpu/apple-afkv2/extract-send.py @@ -31,6 +31,11 @@ def function(pattern, what): #include "afkv2-codec.h" #include "afk_reply.h" #define SZ_1M 1048576U +#define SZ_16K 16384UL +#define SZ_16M (16UL<<20) +#ifndef ALIGN +#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1)) +#endif #define U32_MAX 0xffffffffU #define MSEC_PER_SEC 1000U #define GFP_KERNEL 0 @@ -43,6 +48,7 @@ def function(pattern, what): #define kmalloc(n,f) malloc(n) #define kfree(p) free(p) #define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) #define spin_lock_irqsave(l,f) ((void)(l),(f)=0) #define spin_unlock_irqrestore(l,f) ((void)(l),(void)(f)) #define dma_rmb() do {} while(0) @@ -63,10 +69,11 @@ def function(pattern, what): size_t reply_len; bool reply_len_valid; bool done,free_on_ack; int *completion; }; -struct dcp { void *dev; bool quiescing,external; void *rtk; }; +struct dcp { void *dev; bool quiescing,external,crashed,ext26; void *rtk; }; +static bool dcp_is_ext26(const struct dcp *dcp) { return dcp->ext26; } struct afk_ringbuffer { bool ready; }; struct apple_dcp_afkep { struct dcp *dcp; bool wire_v2,inline_v2,compact; int endpoint,lock;u8 *command_tags; - struct afk_ringbuffer txbfr,rxbfr; }; + struct afk_ringbuffer txbfr,rxbfr; size_t ext26_command_bytes; unsigned int ext26_command_count; }; struct ops { const char *name; }; struct apple_epic_service { struct apple_dcp_afkep *ep; struct ops *ops; unsigned int channel; unsigned long cmd_map[1]; @@ -222,8 +229,34 @@ def function(pattern, what): assert(frees==before_late+2); s.enabled=true;s.torndown=false;timeout=0; assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); - assert(!s.cmd_map[0] && s.users==1);free(output); - puts("PASS: extracted production sender, 128-byte command envelope / 32888-byte reply, short OOB, strict/legacy callers, legacy reply lengths, compact status and actual late-ACK ownership"); + assert(!s.cmd_map[0] && s.users==1); + /* J613 ext26 keeps every command's DMA: a budget bounds it, a timeout latches crashed. */ + dcp.ext26=true;unsigned int ext_frees=frees,ext_sends=sends; + assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); + size_t charge=ep.ext26_command_bytes; + assert(received==32888 && frees==ext_frees && sends==ext_sends+1 && !s.cmd_map[0]); + assert(ep.ext26_command_count==1 && charge==SZ_16K+ALIGN(32888,SZ_16K)); + free(s.cmds[0].rxbuf);free(s.cmds[0].txbuf); + ep.ext26_command_bytes=SZ_16M-charge+1; + assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-ENOSPC); + assert(ep.ext26_command_count==1 && ep.ext26_command_bytes==SZ_16M-charge+1); + ep.ext26_command_bytes=SZ_16M-charge; + assert(!afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)); + assert(ep.ext26_command_bytes==SZ_16M && ep.ext26_command_count==2 && frees==ext_frees); + free(s.cmds[0].rxbuf);free(s.cmds[0].txbuf); + ep.ext26_command_bytes=0;ep.ext26_command_count=255; + assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-ENOSPC); + assert(ep.ext26_command_count==255);ep.ext26_command_count=0;dcp.crashed=true; + assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-EIO); + assert(!ep.ext26_command_count && sends==ext_sends+2 && s.users==1 && !s.cmd_map[0]); + dcp.crashed=false;timeout=1; + assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-ETIMEDOUT); + assert(dcp.crashed && s.cmd_map[0] && !s.cmds[0].free_on_ack && !s.cmds[0].completion); + assert(frees==ext_frees && s.users==1 && ep.ext26_command_count==1); + assert(afk_send_command_timeout_len(&s,0xc0,request,112,output,32888,&status,1000,&received)==-EIO); + assert(sends==ext_sends+3 && s.users==1); + free(s.cmds[0].rxbuf);free(s.cmds[0].txbuf);s.cmd_map[0]=0;free(output); + puts("PASS: extracted production sender, 128-byte command envelope / 32888-byte reply, short OOB, strict/legacy callers, legacy reply lengths, compact status, actual late-ACK ownership and the ext26 budget"); } ''' bodies = [function(r"static u32 afk_compact_next_status", "compact status allocator"), From b46656dfbbd99eeaf328dbde321404332e5a2a06 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Sun, 8 Nov 2026 12:05:56 -0500 Subject: [PATCH 17/32] selftests: apple: model the H17G method profile in the native26 startup harness The merge of port-307 brought in the M5 (H17G) powerlog branch of afk_recv_handle_init(), which reads dcp->hw.iomfb_method_profile. The startup harness compiles that function against its own apple_dcp stub, which had no such field, so the test no longer built. Add the field (zero, not H17G) and the constant; the native26 paths it checks are unchanged. Signed-off-by: Satya Benson --- tools/asahi/dcpext26-startup-tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/asahi/dcpext26-startup-tests.py b/tools/asahi/dcpext26-startup-tests.py index 43bfda7ccb806..358e564ecc6a3 100644 --- a/tools/asahi/dcpext26-startup-tests.py +++ b/tools/asahi/dcpext26-startup-tests.py @@ -76,6 +76,7 @@ def run(name,code,reject=False): codec=re.sub(r'^#include[^\n]*\n','',(A/'afkv2-codec.h').read_text(),flags=re.M) identity=pre+codec+r''' #define DCP_FIRMWARE_H17P 1 +#define DCP_IOMFB_METHODS_H17G 2 #define DCP_FIRMWARE_V_14_7 2 #define AFK_MAX_CHANNEL 16 #define SYSTEM_ENDPOINT 0x20 @@ -89,7 +90,7 @@ def run(name,code,reject=False): struct apple_epic_service_ops {const char *name;bool reusable,optional;int (*prepare)(struct apple_epic_service *,const char *,const char *,s64);int (*revalidate)(struct apple_epic_service *,const char *,const char *,s64);void (*init)(struct apple_epic_service *,const char *,const char *,s64);void (*teardown)(struct apple_epic_service *);}; struct apple_epic_service {struct apple_dcp_afkep *ep;const struct apple_epic_service_ops *ops;bool enabled,torndown,pending_open,defer_start;unsigned users;int lock;u32 channel;void *cookie;const char *pending_name,*pending_class;s64 pending_unit;int cmd_map[1];}; struct dptx_port {struct apple_epic_service *service;s64 unit;bool enabled,connected;int enable_completion;}; -struct apple_dcp {bool native,ext26_services_open;int fw_compat,index,dptx_controller_ready;struct {unsigned num_dptx_ports;} hw;struct dptx_port dptxport[2];void *dev;struct apple_dcp_afkep *systemep,*ibootep,*dptx_controller_ep,*dcpavservep,*dptxep,*ext26_aux[3];}; +struct apple_dcp {bool native,ext26_services_open;int fw_compat,index,dptx_controller_ready;struct {unsigned num_dptx_ports;int iomfb_method_profile;} hw;struct dptx_port dptxport[2];void *dev;struct apple_dcp_afkep *systemep,*ibootep,*dptx_controller_ep,*dcpavservep,*dptxep,*ext26_aux[3];}; struct apple_dcp_afkep {struct apple_dcp *dcp;u32 endpoint,num_channels;bool wire_v2,stopping,match_epic_name,compact;int lock;void *wq,*service_work;const struct apple_epic_service_ops *ops;struct apple_epic_service services[AFK_MAX_CHANNEL];}; static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} static bool dcp_fail_stop(struct apple_dcp *d){return d->native;} From f5bf834c94433092442cb7e00a21634db9f64498 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 16:25:23 -0500 Subject: [PATCH 18/32] drm/apple: export an SDR sink contract for native26 RGB8 scanout The native26 primary accepts only XRGB8888, but the Type-C connector inherits BT2020/HDR properties and publishes raw sink chromaticity and HDR metadata. Hyprland can therefore select wide gamut without a ten-bit primary format. Withhold these properties on T8122 Type-C outputs. Publish an SDR derivative of the native26 EDID, preserving base identity, timings and CTA audio/video/speaker blocks while excluding colour-bearing vendor/extended blocks and unknown extensions. Keep the raw sink cache and attachment generation checks intact. No ten-bit format is added. The EDID-withholding workaround ran on J613 with the Dell at 4K60/XRGB8888, sRGB and no gamut warning. This identity-preserving variant has host coverage but has not run on hardware. The changed C units compile for arm64 on alba; full module and KUnit execution remain pending the shared builder lock. Fixes: 9649e5974600 ("drm/apple: support retained J613 native26 external displays") Signed-off-by: Satya Benson Assisted-by: LLM --- drivers/gpu/drm/apple/apple_drv.c | 14 ++++- drivers/gpu/drm/apple/dcp-sdr-edid.h | 86 ++++++++++++++++++++++++++++ drivers/gpu/drm/apple/iomfb.c | 27 ++++++++- tools/asahi/dcpext26-edid-tests.py | 24 ++++++++ 4 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 drivers/gpu/drm/apple/dcp-sdr-edid.h diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index b0654de7c8595..35171435081a9 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -1266,9 +1266,17 @@ static int apple_probe_typec_ports(struct drm_device *drm, ret = drm_connector_attach_vrr_capable_property(&connector->base); if (ret) return ret; - ret = apple_connector_attach_hdr(&connector->base, false); - if (ret) - return ret; + /* + * T8122's retained external scanout accepts only 8-bit RGB. + * Do not advertise the BT.2020/PQ output contract until this + * route has a qualified 10-bit primary-plane format. The sink + * EDID remains intact; its capabilities are not the pipeline's. + */ + if (!of_machine_is_compatible("apple,t8122")) { + ret = apple_connector_attach_hdr(&connector->base, false); + if (ret) + return ret; + } connector->base.polled = DRM_CONNECTOR_POLL_HPD; connector->connected = false; diff --git a/drivers/gpu/drm/apple/dcp-sdr-edid.h b/drivers/gpu/drm/apple/dcp-sdr-edid.h new file mode 100644 index 0000000000000..479be5f02a4e2 --- /dev/null +++ b/drivers/gpu/drm/apple/dcp-sdr-edid.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +#ifndef APPLE_DCP_SDR_EDID_H +#define APPLE_DCP_SDR_EDID_H + +#include +#include + +#define DCP_EDID_BLOCK_SIZE 128 +#define DCP_EDID_EXT_COUNT 126 +#define DCP_EDID_CHECKSUM 127 +#define DCP_EDID_CHROMATICITY 25 +#define DCP_EDID_CHROMATICITY_SIZE 10 +#define DCP_EDID_CTA 0x02 +#define DCP_CTA_AUDIO 1 +#define DCP_CTA_VIDEO 2 +#define DCP_CTA_SPEAKERS 4 + +static inline void dcp_edid_checksum(u8 *block) +{ + u8 sum = 0; + unsigned int i; + + for (i = 0; i < DCP_EDID_CHECKSUM; i++) + sum += block[i]; + block[DCP_EDID_CHECKSUM] = -sum; +} + +/* + * The native26 primary accepts XR24 only, with an sRGB output contract. + * Keep sink identity, timings and CTA audio, but do not export sink colour + * capabilities as pipeline capabilities. The raw firmware EDID stays cached. + * Unknown extensions (including DisplayID colour blocks) are not a proven + * part of that contract. Modes continue to come from the firmware catalogue. + * @size is the validated, whole-block allocation size; the result fits in it. + */ +static inline size_t dcp_edid_sdr(u8 *edid, size_t size) +{ + size_t offset, output = DCP_EDID_BLOCK_SIZE; + u8 count; + + if (size < DCP_EDID_BLOCK_SIZE || size % DCP_EDID_BLOCK_SIZE) + return 0; + count = edid[DCP_EDID_EXT_COUNT]; + if ((size_t)(count + 1) * DCP_EDID_BLOCK_SIZE > size) + return 0; + memset(edid + DCP_EDID_CHROMATICITY, 0, DCP_EDID_CHROMATICITY_SIZE); + /* EDID 1.4: eight bits per colour; do not inherit sink deep colour. */ + if (edid[18] == 1 && edid[19] >= 4 && (edid[20] & 0x80)) + edid[20] = (edid[20] & ~0x70) | 0x20; + for (offset = DCP_EDID_BLOCK_SIZE; + offset < (size_t)(count + 1) * DCP_EDID_BLOCK_SIZE; + offset += DCP_EDID_BLOCK_SIZE) { + u8 block[DCP_EDID_BLOCK_SIZE] = { DCP_EDID_CTA, 3 }; + const u8 *src = edid + offset; + unsigned int pos, end = src[2], dest = 4; + + if (src[0] != DCP_EDID_CTA || end < 4 || end > 127) + continue; + /* Keep basic audio and native DTD count, but no YCbCr flags. */ + block[3] = src[3] & 0x4f; + for (pos = 4; pos < end;) { + unsigned int length = (src[pos] & 0x1f) + 1; + unsigned int tag = src[pos] >> 5; + + if (pos + length > end) + break; + /* Vendor/extended blocks can describe HDR or wide gamut. */ + if (tag == DCP_CTA_AUDIO || tag == DCP_CTA_VIDEO || + tag == DCP_CTA_SPEAKERS) { + memcpy(block + dest, src + pos, length); + dest += length; + } + pos += length; + } + block[2] = dest; + memcpy(block + dest, src + end, DCP_EDID_CHECKSUM - end); + dcp_edid_checksum(block); + memcpy(edid + output, block, sizeof(block)); + output += sizeof(block); + } + edid[DCP_EDID_EXT_COUNT] = output / DCP_EDID_BLOCK_SIZE - 1; + dcp_edid_checksum(edid); + return output; +} + +#endif diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 90fd2a99291a9..c066a586a59d1 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -26,6 +26,7 @@ #include #include #include +#include "dcp-sdr-edid.h" #include #include #include @@ -2380,7 +2381,31 @@ int dcp_get_modes(struct drm_connector *connector) } } if (nr_modes && apple_connector->drm_edid) { - int update = drm_edid_connector_update(connector, apple_connector->drm_edid); + const struct drm_edid *published = apple_connector->drm_edid; + const struct drm_edid *sdr = NULL; + int update; + + if (dcp_is_ext26(dcp)) { + const struct edid *raw = drm_edid_raw(published); + size_t size = (raw->extensions + 1) * EDID_LENGTH; + u8 *copy = kmemdup(raw, size, GFP_KERNEL); + + if (!copy) { + drm_edid_connector_update(connector, NULL); + goto edid_done; + } + size = dcp_edid_sdr(copy, size); + if (size) + sdr = drm_edid_alloc(copy, size); + kfree(copy); + if (!sdr) { + drm_edid_connector_update(connector, NULL); + goto edid_done; + } + published = sdr; + } + update = drm_edid_connector_update(connector, published); + drm_edid_free(sdr); if (update) { dev_warn(dcp->dev, "raw EDID publication failed: %d\n", update); diff --git a/tools/asahi/dcpext26-edid-tests.py b/tools/asahi/dcpext26-edid-tests.py index 5244bd35f6fb9..b45346cda7509 100644 --- a/tools/asahi/dcpext26-edid-tests.py +++ b/tools/asahi/dcpext26-edid-tests.py @@ -15,6 +15,7 @@ def function(name): #include #include #include "epic/dpavserv-edid.h" +#include "dcp-sdr-edid.h" typedef int64_t s64; #define DPAVSERV_ENDPOINT 0x28 #define DPAVSERV_ENDPOINT_V2 0x2a @@ -54,6 +55,29 @@ def function(name): for(size_t n=0;n Date: Tue, 10 Nov 2026 16:53:32 -0500 Subject: [PATCH 19/32] selftests: apple: model SDR EDID allocation in publication fixtures The publication and panel-dimension fixtures extract dcp_get_modes. Include the actual SDR export helper and model kernel/DRM allocation ownership so the new native26 branch compiles without weakening their existing assertions. Both fixtures pass on the parent and updated head. Signed-off-by: Satya Benson Assisted-by: LLM --- .../selftests/drivers/gpu/apple-dcp/rate.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py index 0a1b169baf70c..e6c4b4007b661 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/rate.py @@ -879,6 +879,22 @@ def publication(root): # The desktop 26.6 VRR filter is gone once that firmware takes adaptive sync. vrr_filter = block(iomfb,'static bool dcp_mode_vrr_supported(')+'\n' if 'static bool dcp_mode_vrr_supported(' in iomfb else '' publication = block(iomfb,'static void dcp_clear_connector_edid(')+'\n'+block(iomfb,'static void dcp_consume_edid_invalidation(')+mode_helpers+vrr_filter+block(iomfb,'int dcp_get_modes(')+block(iomfb,'bool lookup_mode(') + if 'dcp_edid_sdr(' in publication: + # Keep extracting the real publication path, including its SDR helper. + # Model only kernel allocation/DRM ownership at the host boundary. + sdr = '\n'.join(line for line in (base / 'dcp-sdr-edid.h').read_text().splitlines() + if not line.startswith('#include')) + publication = sdr + r""" +static void *kmemdup(const void *data,size_t size,int gfp) { + (void)gfp;void *copy=malloc(size);if(copy)memcpy(copy,data,size);return copy; +} +static const struct drm_edid *drm_edid_alloc(const void *data,size_t size) { + assert(size<=sizeof(transport_edid.data)); + struct drm_edid *e=calloc(1,sizeof(*e)); + if(e) { memcpy(e->data,data,size);raw_allocations++; } + return e; +} +""" + publication # Drive malformed replacements through the same chunk/end ingress as firmware. # The dictionary cache is a boundary substitute and takes ownership of the blob. receipt = r''' From 3f8f6bb9731acd62fee39526d1ec79708029c05f Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 16:56:35 -0500 Subject: [PATCH 20/32] drm/apple: retain basic audio in an empty CTA collection A zero CTA data offset has no data collection or detailed timings, but its header can still advertise basic stereo audio. Keep that header in the SDR export instead of dropping the extension. Exercise the legal zero-offset case alongside the EDID metadata tests. Signed-off-by: Satya Benson Assisted-by: LLM --- drivers/gpu/drm/apple/dcp-sdr-edid.h | 2 +- tools/asahi/dcpext26-edid-tests.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dcp-sdr-edid.h b/drivers/gpu/drm/apple/dcp-sdr-edid.h index 479be5f02a4e2..5b8c710de8d7e 100644 --- a/drivers/gpu/drm/apple/dcp-sdr-edid.h +++ b/drivers/gpu/drm/apple/dcp-sdr-edid.h @@ -52,7 +52,7 @@ static inline size_t dcp_edid_sdr(u8 *edid, size_t size) offset += DCP_EDID_BLOCK_SIZE) { u8 block[DCP_EDID_BLOCK_SIZE] = { DCP_EDID_CTA, 3 }; const u8 *src = edid + offset; - unsigned int pos, end = src[2], dest = 4; + unsigned int pos, end = src[2] ? src[2] : 4, dest = 4; if (src[0] != DCP_EDID_CTA || end < 4 || end > 127) continue; diff --git a/tools/asahi/dcpext26-edid-tests.py b/tools/asahi/dcpext26-edid-tests.py index b45346cda7509..0171c1ece0fde 100644 --- a/tools/asahi/dcpext26-edid-tests.py +++ b/tools/asahi/dcpext26-edid-tests.py @@ -77,6 +77,11 @@ def function(name): memcpy(original,sdr,256);assert(dcp_edid_sdr(sdr,256)==256);assert(!memcmp(sdr,original,256)); for(unsigned n=0;n<128;n++)assert(!dcp_edid_sdr(sdr,n)); assert(!dcp_edid_sdr(sdr,128)); + /* A CTA with no data collection can still advertise basic stereo audio. */ + memset(sdr,0,sizeof(sdr));memcpy(sdr,header,8);sdr[126]=1; + cta=sdr+128;cta[0]=2;cta[1]=3;cta[3]=0x40;checksum(sdr);checksum(cta); + assert(dcp_edid_sdr(sdr,256)==256);assert(sdr[126]==1 && cta[3]==0x40); + puts("PASS SDR EDID identity, audio, timing, colour removal, checksums, bounds and idempotence"); puts("PASS native26 AV identity, stale attachment/owner, bounded EDID length, extension count and checksum failures"); } From eeb7770a453e219b63bedf8ba129534d1c0122de Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 21:43:21 -0500 Subject: [PATCH 21/32] drm/apple: restore allocator admission after integration merge The integration merge inverted the allocator-start predicate. Native26 J613 firmware advertises no endpoint 0x2d, so apple_rtkit_start_ep returns EINVAL and external startup stops before IOMFB starts. Restore H17P/firmware-27 admission with the external profile and native26 exclusions. Cover the production startup body over firmware and profile combinations and endpoint failures. Fixes: 2f4924384b73 ("drm/apple: merge current Aurora integration for native26") Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/iomfb.c | 6 ++- tools/asahi/iomfb-startup-tests.py | 62 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tools/asahi/iomfb-startup-tests.py diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index c066a586a59d1..950f1af25ff53 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -3849,8 +3849,10 @@ int iomfb_start_rtkit(struct apple_dcp *dcp) * has nothing answering it on the AP side, and without this one the * display DART faults. */ - if ((dcp->fw_compat != DCP_FIRMWARE_H17P && - dcp->fw_compat != DCP_FIRMWARE_V_27_0) || dcp_is_ext26(dcp)) { + if ((dcp->fw_compat == DCP_FIRMWARE_H17P || + dcp->fw_compat == DCP_FIRMWARE_V_27_0) && + dcp->hw.iomfb_method_profile != DCP_IOMFB_METHODS_H17P_EXTERNAL && + !dcp_is_ext26(dcp)) { ret = apple_rtkit_start_ep(dcp->rtk, REMOTE_ALLOC_ENDPOINT); if (ret) return ret; diff --git a/tools/asahi/iomfb-startup-tests.py b/tools/asahi/iomfb-startup-tests.py new file mode 100644 index 0000000000000..89a1112aa6181 --- /dev/null +++ b/tools/asahi/iomfb-startup-tests.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise production endpoint admission across firmware/profile combinations.""" +from pathlib import Path +import subprocess, tempfile, re +root = Path(__file__).resolve().parents[2] +s = (root/'drivers/gpu/drm/apple/iomfb.c').read_text() +a = s.index('int iomfb_start_rtkit(') +b = s.index('\nvoid iomfb_shutdown(', a) +body = s[a:b] +pre = r''' +#include +#include +#include +#include +#include +typedef uint64_t dma_addr_t; +#define DCP_FIRMWARE_H17P 1 +#define DCP_FIRMWARE_V_27_0 2 +#define DCP_FIRMWARE_V_26_6_DESKTOP 3 +#define DCP_IOMFB_METHODS_H17P_EXTERNAL 4 +#define REMOTE_ALLOC_ENDPOINT 0x2d +#define IOMFB_ENDPOINT 0x20 +#define DCP_SHMEM_SIZE 1 +#define GFP_KERNEL 0 +struct apple_dcp { int fw_compat; struct {int iomfb_method_profile;} hw; + struct {int lock; bool active, scanout, stopped;} iomfb; + void *rtk, *shmem, *dev; bool ext; }; +static int alloc_calls, iomfb_calls, fail_ep; +static bool iomfb_uses_queue(struct apple_dcp *d) {return false;} +static bool dcp_is_ext26(struct apple_dcp *d) {return d->ext;} +static void mutex_lock(int *p) {} +static void mutex_unlock(int *p) {} +static int apple_rtkit_start_ep(void *r, int ep) { + if (ep==fail_ep) return -EINVAL; + if(ep==REMOTE_ALLOC_ENDPOINT) alloc_calls++; else iomfb_calls++; + return 0; +} +static void *dma_alloc_coherent(void *d,int n,dma_addr_t *a,int flags) {*a=0; return (void *)1;} +static int dcpep_set_shmem(dma_addr_t a) {return 0;} +static void dcp_send_message(struct apple_dcp *d,int ep,int m) {} +''' +main = r''' +int main(void) { + for(int fw=0;fw<4;fw++) for(int ext=0;ext<2;ext++) for(int profile=0;profile<5;profile+=4) { + struct apple_dcp d={.fw_compat=fw,.ext=ext,.hw.iomfb_method_profile=profile}; + bool alloc=(fw==1 || fw==2) && !ext && profile!=4; + alloc_calls=iomfb_calls=0;fail_ep=-1; + assert(iomfb_start_rtkit(&d)==0); + assert(alloc_calls==alloc);assert(iomfb_calls==(fw!=3)); + /* The native26 map has no allocator: it must still start IOMFB. */ + if(ext) { fail_ep=REMOTE_ALLOC_ENDPOINT;assert(!iomfb_start_rtkit(&d)); } + if(alloc) { fail_ep=REMOTE_ALLOC_ENDPOINT;assert(iomfb_start_rtkit(&d)==-EINVAL); } + } + return 0; +} +''' +with tempfile.TemporaryDirectory() as t: + p=Path(t)/'test.c';p.write_text(pre+body+main) + subprocess.run(['cc','-Werror','-Wno-unused-function','-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) + subprocess.run([str(p.with_suffix(''))],check=True) +print('PASS production IOMFB startup firmware/profile matrix and allocator failures') From 662510af85b81ea832ef2093f88440ce790e27b2 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 21:49:01 -0500 Subject: [PATCH 22/32] selftests: apple: register IOMFB startup admission coverage Signed-off-by: Satya Benson --- tools/asahi/run-host-tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 71201b5f1d6e5..e2643718cc098 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -63,6 +63,7 @@ def mutations(flag, names, base, expect=KILL): 'dcpext26-admission-tests.py': [([], PASS)], 'dcpext26-startup-tests.py': [([], PASS)], + 'iomfb-startup-tests.py': [([], PASS)], 'dcpext26-wire-tests.py': [([], PASS)], 'pmp-native26-tests.py': [([], PASS)], From 982f7d23598670720dd94e299be55c23dcf17318 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 22:06:06 -0500 Subject: [PATCH 23/32] dt-bindings: display: apple: keep one firmware UUID definition The integration merge retained both the upstream and native26 schema entries for apple,firmware-uuid. They have the same string type, but the duplicate key prevents YAML loading and blocks DT schema validation. Keep the upstream inherited-image UUID definition for both users. Fixes: 2f4924384b73 ("drm/apple: merge current Aurora integration for native26") Signed-off-by: Satya Benson --- Documentation/devicetree/bindings/display/apple,dcp.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index 101ed3c014e89..fd437d939324a 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -275,10 +275,6 @@ properties: const: 1 description: All external firmware reservations were committed by the loader. - apple,firmware-uuid: - $ref: /schemas/types.yaml#/definitions/string - description: UUID of the actual loaded display image, read from its iop nub. - apple,pmp: $ref: /schemas/types.yaml#/definitions/phandle description: Native PMP supplier, independent of the report request entry. From 3ab1df5b2723ee776403ee74a754ecb5ce73c806 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Tue, 10 Nov 2026 22:46:00 -0500 Subject: [PATCH 24/32] dt-bindings: display: apple: describe the single T8122 compatible The T8122 internal display nodes use a single apple,t8122-dcp string, without the generic apple,dcp fallback. Listing it in the two-string enum rejects all seven T8122 board DTBs in CHECK_DTBS. Describe its single-string contract alongside the existing T8132 and T6031 entries. No DTS, driver or firmware handoff changes. Signed-off-by: Satya Benson --- Documentation/devicetree/bindings/display/apple,dcp.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index fd437d939324a..aa6eddf36d10d 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -24,7 +24,6 @@ properties: - apple,t6000-dcp - apple,t6020-dcp - apple,t6041-dcp - - apple,t8122-dcp - apple,t6050-dcp - apple,t8140-dcp - apple,t8142-dcp @@ -57,6 +56,9 @@ properties: T6031 (M3 Max) internal display controller. The display gate enables it after the boot loader has handed the running display over. const: apple,t6031-dcp + - description: + T8122 internal display controller using its native firmware handoff. + const: apple,t8122-dcp reg: minItems: 2 From e3a102431887edea437f2c57aa28d3318a0a6203 Mon Sep 17 00:00:00 2001 From: Dj <43033058+DjDeveloperr@users.noreply.github.com> Date: Wed, 11 Nov 2026 10:47:56 -0500 Subject: [PATCH 25/32] ci: avoid failing Azure mirror in kernel validation jobs Use the signed Ubuntu archive/security endpoints in the ephemeral runner mirror list and make apt update fail on repository errors. Preserve the existing toolchains, test commands, build configuration and job limits. Repeated host-test retries failed before testing in apt DNS bootstrap. --- .github/workflows/dt-checks.yml | 8 +++++++- .github/workflows/host-tests.yml | 8 +++++++- .github/workflows/sep-source.yml | 16 ++++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.github/workflows/dt-checks.yml b/.github/workflows/dt-checks.yml index a887f663e6955..1c2d88af7bc3e 100644 --- a/.github/workflows/dt-checks.yml +++ b/.github/workflows/dt-checks.yml @@ -50,7 +50,13 @@ jobs: - name: Install the tools run: | - sudo apt-get update -q + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -q -o APT::Update::Error-Mode=any sudo apt-get install -y -q flex bison bc libyaml-dev clang-$CLANG_VERSION lld-$CLANG_VERSION llvm-$CLANG_VERSION swig device-tree-compiler echo "/usr/lib/llvm-$CLANG_VERSION/bin" >> "$GITHUB_PATH" pip install --quiet dtschema yamllint pylibfdt diff --git a/.github/workflows/host-tests.yml b/.github/workflows/host-tests.yml index fd665f948d991..cc947aaf986ea 100644 --- a/.github/workflows/host-tests.yml +++ b/.github/workflows/host-tests.yml @@ -61,7 +61,13 @@ jobs: command -v fdtget >/dev/null || need="$need device-tree-compiler" dpkg -s "libclang-rt-$CLANG_VERSION-dev" >/dev/null 2>&1 || need="$need libclang-rt-$CLANG_VERSION-dev" if [ -n "$need" ]; then - sudo apt-get update -q + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -q -o APT::Update::Error-Mode=any sudo apt-get install -y -q $need fi # The tests call clang, ld.lld, cc and gcc by name. diff --git a/.github/workflows/sep-source.yml b/.github/workflows/sep-source.yml index 27cffa4ae1db5..552e1f149d6c7 100644 --- a/.github/workflows/sep-source.yml +++ b/.github/workflows/sep-source.yml @@ -66,7 +66,13 @@ jobs: fetch-depth: 1 - name: Install kernel build dependencies run: | - sudo apt-get update + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -o APT::Update::Error-Mode=any sudo apt-get install -y clang lld llvm libclang-dev libelf-dev libssl-dev \ flex bison bc pkg-config make perl rustup component add rust-src @@ -97,7 +103,13 @@ jobs: fetch-depth: 1 - name: Install kernel build and QEMU dependencies run: | - sudo apt-get update + # Avoid the hosted runner's Azure mirror after repeated DNS failures. + # Keep signed Ubuntu archive/security suites and fail on update errors. + if [ -f /etc/apt/apt-mirrors.txt ]; then + printf '%s\n' 'https://archive.ubuntu.com/ubuntu/' 'https://security.ubuntu.com/ubuntu/' | \ + sudo tee /etc/apt/apt-mirrors.txt >/dev/null + fi + sudo apt-get update -o APT::Update::Error-Mode=any sudo apt-get install -y clang lld llvm libclang-dev libelf-dev libssl-dev \ flex bison bc pkg-config make perl qemu-system-arm rustup component add rust-src From f6a0bf9b8d9952a0dd5f6887cf21c488cba6d66d Mon Sep 17 00:00:00 2001 From: Dj <43033058+DjDeveloperr@users.noreply.github.com> Date: Wed, 11 Nov 2026 12:33:36 -0500 Subject: [PATCH 26/32] test: align policy-window check with merged PR #1561 Use the exact upstream test from ea10c4dba3be; supersede the duplicate local repair. Production source is unchanged. Signed-off-by: Diljit Singh --- tools/aurora-sep/tests/test_policy_window.py | 83 +++++++------------- 1 file changed, 28 insertions(+), 55 deletions(-) diff --git a/tools/aurora-sep/tests/test_policy_window.py b/tools/aurora-sep/tests/test_policy_window.py index 8ae2a619e44ad..d233b8c6ebd7a 100644 --- a/tools/aurora-sep/tests/test_policy_window.py +++ b/tools/aurora-sep/tests/test_policy_window.py @@ -73,35 +73,6 @@ def setUp(self): self.sbio = (APPLE / "sbio.rs").read_text() self.activate = function(self.sbio, "activate_touchid") - def assert_cached_refkey_window(self, body, at): - # A deferred closure's wrapper comes after its definition. Match its - # complete dispatch rather than accepting any later window in the fn. - # Preserve offsets while ignoring words in comments. - clean = re.sub(r"//[^\n]*|/\*.*?\*/", lambda m: " " * len(m[0]), - body, flags=re.S) - closure = re.search(r"\blet\s+(?Punlock)\s*=\s*\|\|\s*\{", clean) - self.assertIsNotNone(closure) - opening = closure.end() - 1 - depth, end = 1, opening + 1 - while depth and end < len(clean): - depth += (clean[end] == "{") - (clean[end] == "}") - end += 1 - self.assertEqual(depth, 0) - self.assertTrue(opening < at < end) - self.assertEqual(clean.count(PREPARE), 1) - # The initrd deliberately leaves the real root's window unused; its - # guarded store is read-only. Only that branch may call unlock directly. - dispatch = re.search( - r"\blet\s+unlocked\s*=\s*if\s+self\.keystore_only\.load\(Relaxed\)\s*" - r"\{\s*(?Punlock)\(\)\s*\}\s*else\s*" - r"\{\s*crate::xart_guard::policy_window\((?Punlock)\)\s*\};", - clean) - self.assertIsNotNone(dispatch) - self.assertGreater(dispatch.start(), end) - self.assertEqual( - [m.start() for m in re.finditer(r"\bunlock\b", clean)], - [closure.start("binding"), dispatch.start("initrd"), dispatch.start("root")]) - def test_touchid_designates_inside_the_window(self): at = self.activate.index(DESIGNATE) self.assertIn("crate::xart_guard::policy_window(", statement(self.activate, at)) @@ -116,26 +87,6 @@ def test_touchid_designates_a_keybag_that_already_existed(self): self.assertLess(recovered, self.activate.index(DESIGNATE)) self.assertNotIn("sks_provision_identity_keybag(", self.activate) - def test_cached_refkey_window_wraps_deferred_unlock(self): - body = function((APPLE / "refkey.rs").read_text(), "refkey_unseal_cached") - self.assert_cached_refkey_window(body, body.index(PREPARE)) - - def test_cached_refkey_rejects_unguarded_real_root_unlock(self): - body = function((APPLE / "refkey.rs").read_text(), "refkey_unseal_cached") - wrapped = "crate::xart_guard::policy_window(unlock)" - self.assertEqual(body.count(wrapped), 1) - body = body.replace(wrapped, "unlock()") - with self.assertRaises(AssertionError): - self.assert_cached_refkey_window(body, body.index(PREPARE)) - - def test_cached_refkey_rejects_extra_unguarded_unlock(self): - body = function((APPLE / "refkey.rs").read_text(), "refkey_unseal_cached") - dispatch = "let unlocked = if self.keystore_only.load(Relaxed)" - self.assertEqual(body.count(dispatch), 1) - body = body.replace(dispatch, "unlock();\n " + dispatch) - with self.assertRaises(AssertionError): - self.assert_cached_refkey_window(body, body.index(PREPARE)) - def test_no_other_path_designates_outside_a_window(self): # A new designate of an existing keybag needs the window too; add it # here once it runs in one. @@ -146,15 +97,37 @@ def test_no_other_path_designates_outside_a_window(self): # after a ref-key create has closed the window for the boot. callers = calls(PREPARE) self.assertTrue(callers) + deferred = 0 for f, outer, body, at in callers: with self.subTest(caller=outer): - if (f, outer) == ("refkey.rs", "refkey_unseal_cached"): - self.assert_cached_refkey_window(body, at) - continue before = body[:at] - self.assertTrue("crate::xart_guard::policy_window(" in before - or "crate::xart_guard::close_policy_window(" in before, - f"{f}: {outer}") + if ("crate::xart_guard::policy_window(" in before + or "crate::xart_guard::close_policy_window(" in before): + continue + # The call sits in a closure that is run later. + self.assertEqual((f, outer), ("refkey.rs", "refkey_unseal_cached")) + self.assertIn("let unlock = || {", before) + deferred += 1 + self.assert_unlock_runs_in_the_window_or_the_initrd(body[at:]) + self.assertEqual(deferred, 1) + + def assert_unlock_runs_in_the_window_or_the_initrd(self, after): + """`unlock` is run twice: in the window, or bare in the initrd. + + The trusted-key-only initrd attach (keystore_only) leaves the boot's + one window to the real root: "Do not consume the real root's one + policy window in the initrd". Its designate runs with the window + shut, where the guard lets no existing session record be rewritten. + """ + uses = re.findall(r"\bunlock\b(?!\s*=)", after) + self.assertEqual(len(uses), 2) + self.assertRegex( + after, + r"if self\.keystore_only\.load\(Relaxed\) \{\s*" + r"unlock\(\)\s*" + r"\} else \{\s*" + r"crate::xart_guard::policy_window\(unlock\)\s*" + r"\}") if __name__ == "__main__": From d2c01162f0c2d60a8f5fd4f5c51317239fcdd324 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 11 Nov 2026 17:57:41 -0500 Subject: [PATCH 27/32] drm/apple: reserve native26 backing before exposure, never at stop Native26 keeps scanout backing across layout restarts because the link and transport acknowledgements do not prove remote DMA retirement. Retention currently admits at most eight nonempty epochs and 960 MiB of backing. The ninth nonempty stop withdraws the link before returning -ENOSPC, latches external_error and prevents SAFE from returning the shared route. Check retained, current, dropped and incoming backing against the byte budget before exposing a new scanout. Stop can then retain already exposed backing without refusing it. Remove the arbitrary eight-epoch count cap, keep the byte limit and all busy/transport fences, and release only unexposed references on admission failure without crashing the DCP. On J613 with a Dell S2725QS, control kernel E0 reproduced the wedge on left-back alone at epochs=8 with 178814976 bytes retained. Kernel E tested this change on display sources identical to PR head 3165375ed87d: two left-front boots reached 12 and 16 epochs without -ENOSPC, with mode changes, DPMS, replug and 60 s RTC-woken s2idle. The front port could reacquire the pipeline. This does not fix empty-at-boot hotplug, redundant retrain errors, AFK service-slot pressure or the visible layout re-link. The finite byte budget remains; no backing-retirement fence is claimed. Add production-body sanitizer controls for twelve epochs, byte admission, overflow and busy fences. Link: https://github.com/aurora-silicon/linux/pull/973#issuecomment-6808976834 Assisted-by: LLM Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcpext26-lifecycle.h | 6 +- drivers/gpu/drm/apple/iomfb.c | 26 ++++++- tools/asahi/dcpext26-budget-tests.py | 79 ++++++++++++++++++++++ tools/asahi/dcpext26-lifecycle-tests.py | 4 +- tools/asahi/run-host-tests.py | 1 + 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 tools/asahi/dcpext26-budget-tests.py diff --git a/drivers/gpu/drm/apple/dcpext26-lifecycle.h b/drivers/gpu/drm/apple/dcpext26-lifecycle.h index 4eb85f49e22e9..3732aaed2e56a 100644 --- a/drivers/gpu/drm/apple/dcpext26-lifecycle.h +++ b/drivers/gpu/drm/apple/dcpext26-lifecycle.h @@ -32,11 +32,11 @@ static inline bool dcpext26_exchange_finished(const struct dcpext26_exchange *x) { return x->failed || (x->id && x->replied && x->completed); } -/* Reserve headroom for an in-flight full-frame primary before readmission. */ -static inline bool dcpext26_epoch_fits(unsigned int epochs, u64 retained, u64 incoming) +/* Admission only: already exposed backing must always survive teardown. */ +static inline bool dcpext26_backing_fits(u64 retained, u64 incoming) { const u64 limit = (1ULL << 30) - (64ULL << 20); - return !incoming || (epochs < 8 && retained <= limit && incoming <= limit - retained); + return retained <= limit && incoming <= limit - retained; } #endif diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 96ef146ca53a0..d60d0132f634a 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -3327,6 +3327,23 @@ iomfb_scanout_prepare_h17p(struct apple_dcp *dcp, struct drm_crtc *crtc, drm_framebuffer_get(new_state->fb); } } + if (dcp_is_ext26(dcp)) { + struct iomfb_scanout_h17p *dropped; + u64 bytes = ext26_scanout_bytes(scanout); + + /* Reserve every exposed object, including an unsuccessful present. */ + if (dcp->iomfb.scanout) + bytes += ext26_scanout_bytes(dcp->iomfb.scanout); + list_for_each_entry(dropped, &dcp->iomfb.dropped, link) + bytes += ext26_scanout_bytes(dropped); + if (!dcpext26_backing_fits(dcp->ext26_retained_bytes, bytes)) { + dev_warn_ratelimited(dcp->dev, + "native26 backing budget exhausted; new present refused\n"); + /* This scanout has not been exposed to firmware. */ + iomfb_scanout_release_h17p(scanout); + return NULL; + } + } return scanout; } @@ -3598,7 +3615,7 @@ static void iomfb_atomic_start(struct apple_dcp *dcp, } atomic->scanout = iomfb_scanout_prepare_h17p(dcp, atomic->crtc, atomic->state); if (!atomic->scanout) { - WRITE_ONCE(dcp->crashed, true); + /* Allocation/admission failure exposed no new backing. */ schedule_work(&dcp->vblank_wq); return; } @@ -3971,8 +3988,11 @@ int iomfb_ext26_retain_epoch(struct apple_dcp *dcp) bytes += ext26_scanout_bytes(dcp->iomfb.scanout); list_for_each_entry(scanout, &dcp->iomfb.dropped, link) bytes += ext26_scanout_bytes(scanout); - if (!dcpext26_epoch_fits(dcp->ext26_epochs, dcp->ext26_retained_bytes, bytes)) - return -ENOSPC; + /* + * Admission reserved this backing before exposure. Retaining it here + * allocates nothing: a quota refusal after power/link withdrawal would + * incorrectly latch external_error and strand the shared Type-C route. + */ if (dcp->iomfb.scanout) { list_add_tail(&dcp->iomfb.scanout->link, &dcp->ext26_retained); dcp->iomfb.scanout = NULL; diff --git a/tools/asahi/dcpext26-budget-tests.py b/tools/asahi/dcpext26-budget-tests.py new file mode 100644 index 0000000000000..8c00c2c4b3091 --- /dev/null +++ b/tools/asahi/dcpext26-budget-tests.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Run production retained-stop and admission arithmetic across >8 epochs.""" +from pathlib import Path +import subprocess +import tempfile +R = Path(__file__).resolve().parents[2] +s = (R / 'drivers/gpu/drm/apple/iomfb.c').read_text() +h = (R / 'drivers/gpu/drm/apple/dcpext26-lifecycle.h').read_text().replace('#include ', '') +def function(signature): + a = s.index(signature + '\n{') + return s[a:s.index('\n}', a) + 2] +pre = r''' +#include +#include +#include +#include +#include +typedef uint32_t u32; typedef uint64_t u64; +#define SWAP_SURFACES 4 +#define guard(x) (void) +#define dev_dbg(...) ((void)0) +struct list_head { struct list_head *next,*prev; }; +static void init(struct list_head *h){h->next=h->prev=h;} +static void list_add_tail(struct list_head *n,struct list_head *h){n->next=h;n->prev=h->prev;h->prev->next=n;h->prev=n;} +static void list_splice_tail_init(struct list_head *n,struct list_head *h){while(n->next!=n){struct list_head *v=n->next;n->next=v->next;v->next->prev=n;list_add_tail(v,h);}} +#define entry(p) ((struct iomfb_scanout_h17p *)((char *)(p)-offsetof(struct iomfb_scanout_h17p,link))) +#define list_for_each_entry(p,h,m) for(struct list_head *it=(h)->next;it!=(h)&&((p)=entry(it),1);it=it->next) +struct obj {u64 size;}; +struct fb {struct obj *obj[1];}; +struct iomfb_scanout_h17p {struct fb *fb[4];struct list_head link;}; +struct apple_dcp { + struct {int lock;void *active;struct iomfb_scanout_h17p *scanout,*next_scanout;struct list_head dropped;} iomfb; + struct {bool pending;} present_state_h17p;struct {void *data;} chunks; + bool ext26_control_pending,ext26_blanked,ext26_normal,ext26_early_complete; + unsigned ext26_epochs;u64 ext26_retained_bytes;struct list_head ext26_retained; +}; +static bool idle=true; +static bool iomfb_channels_idle(struct apple_dcp *d){(void)d;return idle;} +''' +a = s.index('\tif (dcp_is_ext26(dcp)) {', s.index('iomfb_scanout_prepare_h17p(')) +b = s.index('\n\treturn scanout;', a) +admit = s[a:b] +code = pre + h + '\n' + function('static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout)') + '\n' + function('int iomfb_ext26_retain_epoch(struct apple_dcp *dcp)') + r''' +static unsigned released; +static bool dcp_is_ext26(struct apple_dcp *d){return true;} +#define dev_warn_ratelimited(...) ((void)0) +static void iomfb_scanout_release_h17p(struct iomfb_scanout_h17p *s){released++;} +static struct iomfb_scanout_h17p *admission(struct apple_dcp *dcp,struct iomfb_scanout_h17p *scanout){ +''' + admit + '\nreturn scanout;}\n' + r''' +int main(void){ + struct apple_dcp d={0};init(&d.iomfb.dropped);init(&d.ext26_retained); + struct obj o={32ULL<<20};struct fb f={{&o}};struct iomfb_scanout_h17p frames[16]={0}; + for(unsigned i=0;i<12;i++){ + frames[i].fb[0]=&f;init(&frames[i].link); + assert(admission(&d,&frames[i]));d.iomfb.scanout=&frames[i]; + assert(!iomfb_ext26_retain_epoch(&d));assert(!d.iomfb.scanout); + } + assert(d.ext26_epochs==12&&d.ext26_retained_bytes==(384ULL<<20)); + frames[12].fb[0]=&f;init(&frames[12].link); + d.ext26_retained_bytes=960ULL<<20; + assert(!admission(&d,&frames[12])&&released==1); /* unexposed: safe put */ + d.iomfb.scanout=&frames[12]; /* simulate previously exposed backing at limit */ + assert(!iomfb_ext26_retain_epoch(&d)); /* stop never strands route on quota */ + assert(d.ext26_retained_bytes==(992ULL<<20)); + assert(!dcpext26_backing_fits(~0ULL,1)); + assert(dcpext26_backing_fits((960ULL<<20)-1,1)); + assert(!dcpext26_backing_fits(960ULL<<20,1)); + idle=false;assert(iomfb_ext26_retain_epoch(&d)==-EBUSY); + idle=true;d.ext26_control_pending=true;assert(iomfb_ext26_retain_epoch(&d)==-EBUSY); +} +''' +with tempfile.TemporaryDirectory() as t: + p = Path(t)/'test.c' + p.write_text(code) + subprocess.run(['clang','-std=gnu11','-Wall','-Werror','-Wno-unused-function', + '-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) + subprocess.run([str(p.with_suffix(''))],check=True) +print('PASS production budget admission and retention: 12 epochs, byte limit, overflow, busy fences') diff --git a/tools/asahi/dcpext26-lifecycle-tests.py b/tools/asahi/dcpext26-lifecycle-tests.py index da90f1ff5d3cc..424bb2969c1c3 100644 --- a/tools/asahi/dcpext26-lifecycle-tests.py +++ b/tools/asahi/dcpext26-lifecycle-tests.py @@ -116,8 +116,8 @@ def body(file, name): assert(dcpext26_exchange_begin(&c.exchange,51));c.exchange.replied=true; ext26_control_signal(&d,&c);assert(!c.done.signaled); assert(iomfb_ext26_control_complete(&d,51,true));assert(c.exchange.failed&&c.done.signaled); - assert(!dcpext26_epoch_fits(8,1,1)); assert(dcpext26_epoch_fits(7,0,(960ULL<<20))); - assert(!dcpext26_epoch_fits(7,(960ULL<<20),1));assert(!dcpext26_epoch_fits(0,~0ULL,1)); + assert(dcpext26_backing_fits(1,1)); assert(dcpext26_backing_fits(0,(960ULL<<20))); + assert(!dcpext26_backing_fits((960ULL<<20),1));assert(!dcpext26_backing_fits(~0ULL,1)); struct apple_dcp_afkep ep={0};u32 read=0,write=0; ep.rxbfr=(struct ring){true,&read,&write};assert(afk_host_idle(&ep)); write=64;assert(!afk_host_idle(&ep));read=64;assert(afk_host_idle(&ep)); diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index c3b24b92c917d..8dcb514aa67d1 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -53,6 +53,7 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { + 'dcpext26-budget-tests.py': [([], PASS)], 'dcpext26-lifecycle-tests.py': [([], PASS)], 'dcpext26-modeset-tests.py': [([], PASS)], 'dcpext26-parity-tests.py': [([], PASS)], From d9db758aa964de4240336d541c12df81900d0b46 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 11 Nov 2026 18:19:06 -0500 Subject: [PATCH 28/32] drm/apple: scope backing refusal and exercise queue ownership Keep the previous fatal scanout-preparation failure policy on non- native26 paths. Only native26 backing admission can reject an unexposed scanout without crashing the DCP. Extend the retained-backing fixture from twelve epochs to thirty 32 MiB epochs, reaching the 960 MiB byte budget and refusing the next admission. Exercise surface, current, dropped and retained accounting overflow through the production accumulation bodies, rather than only the final fit helper. Saturate these sums so wrapping cannot reopen admission. Add a production-extraction queue fixture covering enqueue, work dispatch, scanout preparation, queue advancement and transaction release. Check DRM commit and framebuffer references on admission refusal, inherited scanout replacement, native and classic allocation failure, and exposed unsuccessful presents. Scheduling, DRM objects and firmware completion remain mocks; this is not a remote DMA- retirement proof or hardware qualification. Assisted-by: LLM Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/dcpext26-lifecycle.h | 6 + drivers/gpu/drm/apple/iomfb.c | 20 +-- tools/asahi/dcpext26-budget-tests.py | 30 ++-- tools/asahi/dcpext26-lifecycle-tests.py | 2 + tools/asahi/dcpext26-queue-tests.py | 155 +++++++++++++++++++++ tools/asahi/run-host-tests.py | 1 + 6 files changed, 198 insertions(+), 16 deletions(-) create mode 100644 tools/asahi/dcpext26-queue-tests.py diff --git a/drivers/gpu/drm/apple/dcpext26-lifecycle.h b/drivers/gpu/drm/apple/dcpext26-lifecycle.h index 3732aaed2e56a..0121faae0df10 100644 --- a/drivers/gpu/drm/apple/dcpext26-lifecycle.h +++ b/drivers/gpu/drm/apple/dcpext26-lifecycle.h @@ -32,6 +32,12 @@ static inline bool dcpext26_exchange_finished(const struct dcpext26_exchange *x) { return x->failed || (x->id && x->replied && x->completed); } +/* Saturate accounting so a wrapped sum can never reopen admission. */ +static inline u64 dcpext26_backing_add(u64 total, u64 incoming) +{ + return incoming > ~0ULL - total ? ~0ULL : total + incoming; +} + /* Admission only: already exposed backing must always survive teardown. */ static inline bool dcpext26_backing_fits(u64 retained, u64 incoming) { diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index d60d0132f634a..8c7596a7d550b 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -3333,9 +3333,10 @@ iomfb_scanout_prepare_h17p(struct apple_dcp *dcp, struct drm_crtc *crtc, /* Reserve every exposed object, including an unsuccessful present. */ if (dcp->iomfb.scanout) - bytes += ext26_scanout_bytes(dcp->iomfb.scanout); + bytes = dcpext26_backing_add(bytes, + ext26_scanout_bytes(dcp->iomfb.scanout)); list_for_each_entry(dropped, &dcp->iomfb.dropped, link) - bytes += ext26_scanout_bytes(dropped); + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(dropped)); if (!dcpext26_backing_fits(dcp->ext26_retained_bytes, bytes)) { dev_warn_ratelimited(dcp->dev, "native26 backing budget exhausted; new present refused\n"); @@ -3537,7 +3538,8 @@ VISIBLE_IF_KUNIT void iomfb_scanout_put_h17p(struct apple_dcp *dcp, if (dcp_is_ext26(dcp)) { /* An unsuccessful present can still have exposed its IOVAs. */ list_add_tail(&scanout->link, &dcp->ext26_retained); - dcp->ext26_retained_bytes += ext26_scanout_bytes(scanout); + dcp->ext26_retained_bytes = dcpext26_backing_add( + dcp->ext26_retained_bytes, ext26_scanout_bytes(scanout)); return; } iomfb_scanout_release_h17p(scanout); @@ -3615,7 +3617,9 @@ static void iomfb_atomic_start(struct apple_dcp *dcp, } atomic->scanout = iomfb_scanout_prepare_h17p(dcp, atomic->crtc, atomic->state); if (!atomic->scanout) { - /* Allocation/admission failure exposed no new backing. */ + /* Only native26 admission refuses unexposed backing nonfatally. */ + if (!dcp_is_ext26(dcp)) + WRITE_ONCE(dcp->crashed, true); schedule_work(&dcp->vblank_wq); return; } @@ -3969,7 +3973,7 @@ static u64 ext26_scanout_bytes(struct iomfb_scanout_h17p *scanout) for (i = 0; i < SWAP_SURFACES; i++) if (scanout->fb[i] && scanout->fb[i]->obj[0]) - bytes += scanout->fb[i]->obj[0]->size; + bytes = dcpext26_backing_add(bytes, scanout->fb[i]->obj[0]->size); return bytes; } @@ -3985,9 +3989,9 @@ int iomfb_ext26_retain_epoch(struct apple_dcp *dcp) dcp->ext26_control_pending) return -EBUSY; if (dcp->iomfb.scanout) - bytes += ext26_scanout_bytes(dcp->iomfb.scanout); + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(dcp->iomfb.scanout)); list_for_each_entry(scanout, &dcp->iomfb.dropped, link) - bytes += ext26_scanout_bytes(scanout); + bytes = dcpext26_backing_add(bytes, ext26_scanout_bytes(scanout)); /* * Admission reserved this backing before exposure. Retaining it here * allocates nothing: a quota refusal after power/link withdrawal would @@ -3998,7 +4002,7 @@ int iomfb_ext26_retain_epoch(struct apple_dcp *dcp) dcp->iomfb.scanout = NULL; } list_splice_tail_init(&dcp->iomfb.dropped, &dcp->ext26_retained); - dcp->ext26_retained_bytes += bytes; + dcp->ext26_retained_bytes = dcpext26_backing_add(dcp->ext26_retained_bytes, bytes); if (bytes) dcp->ext26_epochs++; dcp->ext26_blanked = false; diff --git a/tools/asahi/dcpext26-budget-tests.py b/tools/asahi/dcpext26-budget-tests.py index 8c00c2c4b3091..fcda4658d7a0e 100644 --- a/tools/asahi/dcpext26-budget-tests.py +++ b/tools/asahi/dcpext26-budget-tests.py @@ -50,22 +50,36 @@ def function(signature): ''' + admit + '\nreturn scanout;}\n' + r''' int main(void){ struct apple_dcp d={0};init(&d.iomfb.dropped);init(&d.ext26_retained); - struct obj o={32ULL<<20};struct fb f={{&o}};struct iomfb_scanout_h17p frames[16]={0}; - for(unsigned i=0;i<12;i++){ + struct obj o={32ULL<<20};struct fb f={{&o}};struct iomfb_scanout_h17p frames[32]={0}; + for(unsigned i=0;i<30;i++){ frames[i].fb[0]=&f;init(&frames[i].link); assert(admission(&d,&frames[i]));d.iomfb.scanout=&frames[i]; assert(!iomfb_ext26_retain_epoch(&d));assert(!d.iomfb.scanout); } - assert(d.ext26_epochs==12&&d.ext26_retained_bytes==(384ULL<<20)); - frames[12].fb[0]=&f;init(&frames[12].link); - d.ext26_retained_bytes=960ULL<<20; - assert(!admission(&d,&frames[12])&&released==1); /* unexposed: safe put */ - d.iomfb.scanout=&frames[12]; /* simulate previously exposed backing at limit */ + assert(d.ext26_epochs==30&&d.ext26_retained_bytes==(960ULL<<20)); + frames[30].fb[0]=&f;init(&frames[30].link); + assert(d.ext26_retained_bytes==960ULL<<20); + assert(!admission(&d,&frames[30])&&released==1); /* unexposed: safe put */ + d.iomfb.scanout=&frames[30]; /* simulate previously exposed backing at limit */ assert(!iomfb_ext26_retain_epoch(&d)); /* stop never strands route on quota */ assert(d.ext26_retained_bytes==(992ULL<<20)); assert(!dcpext26_backing_fits(~0ULL,1)); assert(dcpext26_backing_fits((960ULL<<20)-1,1)); assert(!dcpext26_backing_fits(960ULL<<20,1)); + /* Overflow in actual surface accumulation must fail closed. */ + o.size=(1ULL<<63);frames[31].fb[0]=&f;frames[31].fb[1]=&f; + assert(ext26_scanout_bytes(&frames[31])==~0ULL); + d.ext26_retained_bytes=0; + assert(!admission(&d,&frames[31])); + /* Overflow across current plus dropped must not wrap back under budget. */ + frames[30].fb[0]=&f;d.iomfb.scanout=&frames[30]; + assert(!admission(&d,&frames[30])); + d.iomfb.scanout=0; + struct iomfb_scanout_h17p dropped={.fb={&f}};init(&dropped.link); + list_add_tail(&dropped.link,&d.iomfb.dropped); + assert(!admission(&d,&frames[31])); + d.ext26_retained_bytes=~0ULL-1; + assert(!iomfb_ext26_retain_epoch(&d)&&d.ext26_retained_bytes==~0ULL); idle=false;assert(iomfb_ext26_retain_epoch(&d)==-EBUSY); idle=true;d.ext26_control_pending=true;assert(iomfb_ext26_retain_epoch(&d)==-EBUSY); } @@ -76,4 +90,4 @@ def function(signature): subprocess.run(['clang','-std=gnu11','-Wall','-Werror','-Wno-unused-function', '-fsanitize=address,undefined',str(p),'-o',str(p.with_suffix(''))],check=True) subprocess.run([str(p.with_suffix(''))],check=True) -print('PASS production budget admission and retention: 12 epochs, byte limit, overflow, busy fences') +print('PASS production budget admission and retention: 30 epochs to 960 MiB, refusal past limit, production surface/current/dropped/retained accumulation overflow, busy fences') diff --git a/tools/asahi/dcpext26-lifecycle-tests.py b/tools/asahi/dcpext26-lifecycle-tests.py index 424bb2969c1c3..4335d19d14646 100644 --- a/tools/asahi/dcpext26-lifecycle-tests.py +++ b/tools/asahi/dcpext26-lifecycle-tests.py @@ -106,6 +106,8 @@ def body(file, name): static void native_pm_complete(struct apple_dcp *dcp); static int native_pm_resume(struct apple_dcp *dcp); int main(void) { + assert(dcpext26_backing_add(1,2)==3); + assert(dcpext26_backing_add(~0ULL,1)==~0ULL); struct ext26_control c={0}; struct apple_dcp d={0};d.ext26_control=&c;d.ext26_control_pending=true; assert(!dcpext26_exchange_begin(&c.exchange,0)); assert(dcpext26_exchange_begin(&c.exchange,42));assert(!dcpext26_exchange_begin(&c.exchange,43)); diff --git a/tools/asahi/dcpext26-queue-tests.py b/tools/asahi/dcpext26-queue-tests.py new file mode 100644 index 0000000000000..aa1e927be2cfd --- /dev/null +++ b/tools/asahi/dcpext26-queue-tests.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Exercise production queue, preparation and reference release with mock DRM/RPCs.""" +from pathlib import Path +import subprocess +import tempfile +r = Path(__file__).resolve().parents[2] +s = (r / 'drivers/gpu/drm/apple/iomfb.c').read_text() +h = (r / 'drivers/gpu/drm/apple/dcpext26-lifecycle.h').read_text().replace('#include ', '') +def function(name): + a = s.index(name + '(') + if name == 'ext26_scanout_bytes': + a = s.rindex('static u64 ext26_scanout_bytes(') + else: + a = s.rfind('\n', 0, a) + 1 + b = s.index('\n}', s.index('{', a)) + 2 + text = s[a:b].replace('VISIBLE_IF_KUNIT ', '') + if name == 'iomfb_scanout_prepare_h17p': + text = 'static struct iomfb_scanout_h17p *\n' + text + return text +pre = r''' +#include +#include +#include +#include +#include +#include +typedef uint32_t u32; typedef uint64_t u64; +#define SWAP_SURFACES 4 +#define READ_ONCE(x) (x) +#define WRITE_ONCE(x,v) ((x)=(v)) +#define guard(x) (void) +#define lockdep_assert_held(x) ((void)0) +#define mutex_lock(x) ((void)0) +#define mutex_unlock(x) ((void)0) +#define dev_dbg(...) ((void)0) +#define dev_warn_ratelimited(...) ((void)0) +#define container_of(p,t,m) ((t *)((char *)(p)-offsetof(t,m))) +struct list_head { struct list_head *next,*prev; }; +static void INIT_LIST_HEAD(struct list_head *h){h->next=h->prev=h;} +static bool list_empty(struct list_head *h){return h->next==h;} +static void list_add_tail(struct list_head *n,struct list_head *h){n->next=h;n->prev=h->prev;h->prev->next=n;h->prev=n;} +static void list_del(struct list_head *n){n->prev->next=n->next;n->next->prev=n->prev;INIT_LIST_HEAD(n);} +#define list_first_entry(h,t,m) container_of((h)->next,t,m) +#define list_for_each_entry(p,h,m) for(struct list_head *it=(h)->next;it!=(h)&&((p)=container_of(it,typeof(*p),m),1);it=it->next) +#define list_for_each_entry_safe(p,n,h,m) for(struct list_head *it=(h)->next,*next;it!=(h)&&((next=it->next),(p)=container_of(it,typeof(*p),m),1);it=next) +static void list_splice_tail_init(struct list_head *n,struct list_head *h){while(!list_empty(n)){struct list_head *v=n->next;list_del(v);list_add_tail(v,h);}} +static int fail_alloc; +static void *alloc(size_t n){if(fail_alloc){fail_alloc--;return NULL;}return calloc(1,n);} +#define kzalloc_obj(x) alloc(sizeof(x)) +#define kfree(x) free(x) +struct obj {u64 size;}; +struct drm_framebuffer {struct obj *obj[1];int refs;}; +static void drm_framebuffer_get(struct drm_framebuffer *f){assert(f->refs>0);f->refs++;} +static void drm_framebuffer_put(struct drm_framebuffer *f){assert(f->refs>1);f->refs--;} +struct drm_crtc {int unused;}; +struct drm_plane {unsigned iomfb_surf;}; +#define to_apple_plane(p) (p) +struct drm_plane_state {struct drm_crtc *crtc;bool visible;struct drm_framebuffer *fb;}; +struct drm_atomic_commit {int refs,count;struct drm_plane plane[4];struct drm_plane_state old[4],new[4];}; +#define for_each_oldnew_plane_in_state(s,p,o,n,i) for(i=0;i<(s)->count&&((p)=&(s)->plane[i],(o)=&(s)->old[i],(n)=&(s)->new[i],1);i++) +static struct drm_atomic_commit *drm_atomic_commit_get(struct drm_atomic_commit *s){s->refs++;return s;} +static void drm_atomic_commit_put(struct drm_atomic_commit *s){assert(s->refs>1);s->refs--;} +struct work_struct {int scheduled;}; +static void schedule_work(struct work_struct *w){w->scheduled++;} +#define cancel_delayed_work(x) ((void)0) +#define mod_delayed_work(...) ((void)0) +#define msecs_to_jiffies(x) (x) +#define current NULL +static unsigned long jiffies; +struct apple_dcp; +struct iomfb_transaction {struct list_head link;void (*start)(struct apple_dcp *,struct iomfb_transaction *);void (*release)(struct iomfb_transaction *);bool brightness_only,presentation,completed,backlight_failed;}; +struct iomfb_scanout_h17p {int request;struct drm_framebuffer *fb[4];struct list_head link;}; +struct apple_dcp { + struct {int lock,timeout,backlight_retry,callback_count;void *owner;unsigned long deadline;unsigned queued;bool presentation_paused,backlight_queued,stopped;struct work_struct work;struct list_head pending,dropped;struct iomfb_transaction *active;struct iomfb_scanout_h17p *scanout,*next_scanout;} iomfb; + struct {bool pending;} present_state_h17p;struct {bool valid;} mode_state;struct {bool connected;} *connector; + struct {void *data;} chunks;bool native,crashed,ext26_control_pending,ext26_blanked,ext26_normal,ext26_early_complete; + unsigned ext26_epochs;u64 ext26_retained_bytes;struct list_head ext26_retained; + int fw_compat;struct work_struct vblank_wq,bl_update_wq; +}; +#define DCP_FIRMWARE_V_27_0 27 +static bool dcp_is_ext26(struct apple_dcp *d){return d->native;} +static bool iomfb_channels_idle(struct apple_dcp *d){return true;} +static bool dcp_backlight_pending(struct apple_dcp *d){return false;} +static unsigned dcp_backlight_retry_delay(struct apple_dcp *d){return 0;} +static int iomfb_enqueue_opaque_x(struct apple_dcp *d){return 0;} +static unsigned flushed; +static void iomfb_flush_h17p(struct apple_dcp *d,struct drm_crtc *c,struct drm_atomic_commit *s){assert(d->iomfb.next_scanout);flushed++;d->present_state_h17p.pending=true;} +#define iomfb_flush_v27 iomfb_flush_h17p +''' +names = ['ext26_scanout_bytes', 'iomfb_scanout_release_h17p', + 'iomfb_scanout_prepare_h17p', 'iomfb_scanout_put_h17p', + 'iomfb_ext26_retain_epoch', 'iomfb_discard_pending', + 'iomfb_queue_advance', 'iomfb_queue_work', 'iomfb_queue'] +code = pre + h + '\n' + '\n'.join(function(n) for n in names) +a = s.index('struct iomfb_atomic_transaction {') +b = s.index('\nvoid dcp_flush(', a) +code += '\n' + s[a:b] +code += r''' +static void setup(struct apple_dcp *d){ + INIT_LIST_HEAD(&d->iomfb.pending);INIT_LIST_HEAD(&d->iomfb.dropped);INIT_LIST_HEAD(&d->ext26_retained); + d->native=true;d->mode_state.valid=true; + static typeof(*d->connector) conn={.connected=true};d->connector=&conn; +} +int main(void){ + struct apple_dcp d={0};setup(&d);struct drm_crtc c={0}; + struct obj o={32ULL<<20};struct drm_framebuffer fb={.obj={&o},.refs=1}; + struct drm_atomic_commit s={.refs=1,.count=1,.new={{.crtc=&c,.visible=true,.fb=&fb}}}; + for(unsigned i=0;i<30;i++){ + iomfb_queue_atomic(&d,&c,&s);assert(s.refs==2&&d.iomfb.queued==1); + iomfb_queue_work(&d.iomfb.work);assert(d.present_state_h17p.pending&&flushed==i+1); + /* Mock the firmware completion publication; the queue/release bodies are real. */ + d.iomfb.scanout=d.iomfb.next_scanout;d.iomfb.next_scanout=NULL; + d.present_state_h17p.pending=false;iomfb_queue_advance(&d); + assert(!d.iomfb.active&&s.refs==1&&fb.refs==(int)i+2); + assert(!iomfb_ext26_retain_epoch(&d)); + } + assert(d.ext26_epochs==30&&d.ext26_retained_bytes==(960ULL<<20)); + iomfb_queue_atomic(&d,&c,&s);iomfb_queue_work(&d.iomfb.work); + assert(!d.crashed&&!d.iomfb.active&&!d.iomfb.next_scanout&&!d.iomfb.queued); + assert(s.refs==1&&fb.refs==31&&flushed==30&&d.vblank_wq.scheduled); + /* Partial prepare replaces inherited refs, then rejects without exposing. */ + struct iomfb_scanout_h17p *old=calloc(1,sizeof(*old));INIT_LIST_HEAD(&old->link); + old->fb[0]=&fb;drm_framebuffer_get(&fb);d.iomfb.scanout=old; + iomfb_queue_atomic(&d,&c,&s);iomfb_queue_work(&d.iomfb.work); + assert(!d.crashed&&fb.refs==32&&s.refs==1&&d.iomfb.scanout==old&&flushed==30); + d.iomfb.scanout=NULL;iomfb_scanout_release_h17p(old); + /* Allocation failure at preparation: native survives; classic retains fatal policy. */ + for(unsigned native=0;native<2;native++){ + struct apple_dcp x={0};setup(&x);x.native=native; + iomfb_queue_atomic(&x,&c,&s);fail_alloc=1;iomfb_queue_work(&x.iomfb.work); + assert(x.crashed==!native&&x.vblank_wq.scheduled&&!x.iomfb.next_scanout); + if(x.iomfb.active){x.iomfb.active->release(x.iomfb.active);x.iomfb.active=NULL;} + assert(s.refs==1&&fb.refs==31); + } + /* Exposed failed presents remain retained, not put. */ + struct apple_dcp x={0};setup(&x); + iomfb_queue_atomic(&x,&c,&s);iomfb_queue_work(&x.iomfb.work); + x.present_state_h17p.pending=false;iomfb_queue_advance(&x); + assert(x.ext26_retained_bytes==(32ULL<<20)&&fb.refs==32&&!x.iomfb.next_scanout); + /* Fixture-only teardown; this is not a production DMA-retirement claim. */ + struct iomfb_scanout_h17p *f,*n; + list_for_each_entry_safe(f,n,&x.ext26_retained,link){list_del(&f->link);iomfb_scanout_release_h17p(f);} + list_for_each_entry_safe(f,n,&d.ext26_retained,link){list_del(&f->link);iomfb_scanout_release_h17p(f);} + assert(fb.refs==1&&s.refs==1); +} +''' +with tempfile.TemporaryDirectory() as t: + p = Path(t) / 'queue.c' + p.write_text(code) + subprocess.run(['clang', '-std=gnu11', '-Wall', '-Werror', + '-Wno-unused-function', '-Wno-unused-variable', + '-fsanitize=address,undefined', str(p), '-o', str(p.with_suffix(''))], check=True) + subprocess.run([str(p.with_suffix(''))], check=True) +print('PASS production queue/start/prepare/advance/release: 30 epochs, budget refusal, inherited refs, native/classic allocation failure, exposed failed present') diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 8dcb514aa67d1..c88ffe08fe6a1 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -54,6 +54,7 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { 'dcpext26-budget-tests.py': [([], PASS)], + 'dcpext26-queue-tests.py': [([], PASS)], 'dcpext26-lifecycle-tests.py': [([], PASS)], 'dcpext26-modeset-tests.py': [([], PASS)], 'dcpext26-parity-tests.py': [([], PASS)], From 52a178fb904a8671fe42e52d70ff443771d5cf94 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 11 Nov 2026 22:00:38 -0500 Subject: [PATCH 29/32] usb: typec: wait for J613 native26 coldplug before collecting muxes The 25G83 profile enables the external route nodes during DRM coldplug. A PD connector probing first otherwise retains a PHY-only mux handle permanently. Delay J613 profile connector registration until coldplug has attempted its changeset, including refusal; other boards and trees without the profile keep their disabled-route policy. Once the gate completes, normal provider lookup defers until the enabled DCP mux registers. Replay the production preflight and graph/mux bodies under ASan/UBSan. Removing the preflight must fail the early-order assertion; refused gates, ordinary J613 trees and other boards still skip disabled routes. Assisted-by: LLM Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/j613_dcp_activate.c | 14 ++ drivers/usb/typec/tipd/core.c | 20 +++ tools/asahi/j613-mux-order-tests.py | 142 ++++++++++++++++++ tools/asahi/run-host-tests.py | 1 + .../drivers/gpu/apple-dcp/transaction.py | 7 + 5 files changed, 184 insertions(+) create mode 100644 tools/asahi/j613-mux-order-tests.py diff --git a/drivers/gpu/drm/apple/j613_dcp_activate.c b/drivers/gpu/drm/apple/j613_dcp_activate.c index a5c668b5ec4b8..d42a3216cd8d0 100644 --- a/drivers/gpu/drm/apple/j613_dcp_activate.c +++ b/drivers/gpu/drm/apple/j613_dcp_activate.c @@ -598,6 +598,20 @@ int __init apple_j613_25g83_coldplug(void) display_clock_hz); } out: + /* Publish completion on refusal too: disabled routes must then be skipped. */ + if (nodes[J613_DCP] && of_machine_is_compatible("apple,j613")) { + struct of_changeset *done = kzalloc_obj(*done); + + if (done) { + of_changeset_init(done); + if (of_changeset_add_prop_bool(done, nodes[J613_DCP], + "apple,j613-25g83-coldplug-done") || + of_changeset_apply(done)) { + of_changeset_destroy(done); + kfree(done); + } + } + } for (i = 0; i < ARRAY_SIZE(nodes); i++) of_node_put(nodes[i]); return ret; diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index c49b175e61715..5539128a34eb9 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -2583,6 +2583,22 @@ VISIBLE_IF_KUNIT int cd321x_init_mux(struct cd321x *cd321x) } EXPORT_SYMBOL_IF_KUNIT(cd321x_init_mux); +/* The J613 profile enables its disabled DCP routes during DRM coldplug. */ +static bool cd321x_display_gate_pending(void) +{ + struct device_node *dcp; + bool pending; + + if (!IS_ENABLED(CONFIG_DRM_APPLE) || !of_machine_is_compatible("apple,j613")) + return false; + + dcp = of_find_node_by_path("dcp"); + pending = dcp && of_property_present(dcp, "apple,j613-25g83-profile") && + !of_property_present(dcp, "apple,j613-25g83-coldplug-done"); + of_node_put(dcp); + return pending; +} + static int cd321x_register_port(struct tps6598x *tps, struct fwnode_handle *fwnode) { @@ -3219,6 +3235,10 @@ int tipd_init(struct tps6598x *tps) int ret, cleanup_ret; bool reset_allowed = true; + /* Do not retain a PHY-only mux handle before the route changeset runs. */ + if (cd321x_display_gate_pending()) + return -EPROBE_DEFER; + dev_dbg(tps->dev, "Type-C probe start\n"); tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(tps->reset)) diff --git a/tools/asahi/j613-mux-order-tests.py b/tools/asahi/j613-mux-order-tests.py new file mode 100644 index 0000000000000..b163b4b6af439 --- /dev/null +++ b/tools/asahi/j613-mux-order-tests.py @@ -0,0 +1,142 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +#!/usr/bin/env python3 +"""Model both probe orders with actual Aurora mux lookup bodies; not hardware proof. + +Usage: python3 reproduce-mux-order.py /path/to/aurora-linux +""" +from pathlib import Path +import subprocess +import sys +import tempfile +root = Path(sys.argv[1]) if len(sys.argv) > 1 and not sys.argv[1].startswith('--') else Path(__file__).resolve().parents[2] +s = (root / 'drivers/usb/typec/mux.c').read_text() +def fn(name, s=s): + a = s.index(name + '(') + a = s.rfind('\n', 0, a) + 1 + return s[a:s.index('\n}', s.index('{', a)) + 2] +pre = r''' +#include +#include +#include +#include +#include +#define EPROBE_DEFER 517 +#define ENOMEM 12 +#define ENODEV 19 +#define TYPEC_MUX_MAX_DEVS 4 +#define ERR_PTR(x) ((void *)(intptr_t)(x)) +#define PTR_ERR(x) ((int)(intptr_t)(x)) +#define IS_ERR(x) ((uintptr_t)(x) >= (uintptr_t)-4095) +#define READ_ONCE(x) (x) +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x))) +#define kzalloc_obj(x) calloc(1,sizeof(x)) +#define kfree(x) free(x) +static int typec_mux_class,typec_mux_srcu; +struct typec_mux_dev; +struct fwnode_handle {struct fwnode_handle *parent;bool available;struct typec_mux_dev *provider;}; +struct device {struct fwnode_handle *node;}; +struct typec_mux_dev {struct device dev;bool removed;int owner;unsigned sets;}; +struct typec_mux_context {int unused;}; +struct typec_mux {struct typec_mux_dev *mux_devs[4];unsigned num_mux_devs;struct typec_mux_context *context;}; +static int srcu_read_lock(int *s){return 0;} +static void srcu_read_unlock(int *s,int i){} +static bool try_module_get(int owner){return true;} +static void module_put(int owner){} +static void put_device(struct device *d){} +static bool mux_fwnode_match(struct device *d,const void *n){return d->node==n;} +static struct device *class_find_device(int *c,void *start,const struct fwnode_handle *n,bool (*match)(struct device *,const void *)){ + return n->provider ? &n->provider->dev : NULL; +} +#define to_typec_mux_dev(d) ((struct typec_mux_dev *)((char *)(d)-offsetof(struct typec_mux_dev,dev))) +#define fwnode_for_each_parent_node(n,p) for(p=(n)->parent;p;p=p->parent) +static bool fwnode_device_is_available(struct fwnode_handle *n){return n->available;} +static void fwnode_handle_put(struct fwnode_handle *n){} +static bool fwnode_property_present(const struct fwnode_handle *n,const char *id){return true;} +static struct typec_mux_context context; +static struct typec_mux_context *typec_mux_context_get(struct fwnode_handle *n){return &context;} +static struct fwnode_handle *targets[2]; +typedef void *(*devcon_match_fn_t)(const struct fwnode_handle *, const char *, void *); +#define fwnode_graph_for_each_endpoint(f,ep) for(unsigned i=0;i<2&&((ep)=targets[i],1);i++) +static struct fwnode_handle *fwnode_graph_get_remote_port_parent(struct fwnode_handle *ep){return ep;} +static unsigned int fwnode_graph_devcon_matches(const struct fwnode_handle *,const char *,void *,devcon_match_fn_t,void **,unsigned int); +static int fwnode_connection_find_matches(struct fwnode_handle *n,const char *id,void *data, + void *(*match)(const struct fwnode_handle *,const char *,void *),void **out,size_t max){ + return fwnode_graph_devcon_matches(n,id,data,match,out,max); +} +static void apply(struct typec_mux *m){for(unsigned i=0;inum_mux_devs;i++)m->mux_devs[i]->sets++;} +''' +prop = (root / 'drivers/base/property.c').read_text() +tipd = (root / 'drivers/usb/typec/tipd/core.c').read_text() +helper = fn('cd321x_display_gate_pending', tipd) +a = tipd.index('/* Do not retain a PHY-only') +b = tipd.index('dev_dbg(tps->dev,', a) +guard = tipd[a:b] +if '--negative-control' in sys.argv: + guard = '(void)cd321x_display_gate_pending();' +pre += r''' +#include +#define CONFIG_DRM_APPLE 1 +#define IS_ENABLED(x) (x) +struct device_node { bool profile, done; }; +static struct device_node dcp_node; +static bool j613 = true; +static bool of_machine_is_compatible(const char *s) { return j613; } +static struct device_node *of_find_node_by_path(const char *s) { return &dcp_node; } +static bool of_property_present(struct device_node *n,const char *s) { + return strstr(s,"coldplug-done") ? n->done : n->profile; +} +static void of_node_put(struct device_node *n) {} +''' +pre += helper + '\nstatic int probe(struct fwnode_handle *f) {\n' + guard + r''' + struct typec_mux *m = fwnode_typec_mux_get(f); + if (IS_ERR(m)) return PTR_ERR(m); + int count = m->num_mux_devs; + free(m); + return count; +} +''' +# probe calls the production lookup defined below. +pre = pre.replace(helper, 'static struct typec_mux *fwnode_typec_mux_get(struct fwnode_handle *f);\n' + helper) + +code = pre + fn('fwnode_graph_devcon_matches', prop) + '\n'.join(fn(n) for n in ['typec_mux_ancestors_available', 'typec_mux_match', 'fwnode_typec_mux_get']) + r''' +int main(void){ + struct fwnode_handle processor={.available=false},route={.parent=&processor,.available=true},phy={.available=true},connector={.available=true}; + struct typec_mux_dev atc={.dev.node=&phy},dcp={.dev.node=&route};phy.provider=&atc;targets[0]=&phy;targets[1]=&route; + struct typec_mux *early=fwnode_typec_mux_get(&connector);assert(early&&!IS_ERR(early)&&early->num_mux_devs==1); + /* A disabled route node is skipped even before mux_match sees its ancestor. */ + processor.available=true;route.available=false; + struct typec_mux *own_off=fwnode_typec_mux_get(&connector); + assert(own_off&&!IS_ERR(own_off)&&own_off->num_mux_devs==1); + route.available=true; + processor.available=true; /* J613 coldplug OF activation, provider not registered yet. */ + struct typec_mux *defer=fwnode_typec_mux_get(&connector);assert(IS_ERR(defer)&&PTR_ERR(defer)==-EPROBE_DEFER); + route.provider=&dcp; /* DCP probe registers its route after activation. */ + apply(early);apply(own_off);assert(atc.sets==2&&dcp.sets==0); /* Later enable/register never refreshes this handle. */ + struct typec_mux *late=fwnode_typec_mux_get(&connector);assert(late&&!IS_ERR(late)&&late->num_mux_devs==2); + apply(late);assert(atc.sets==3&&dcp.sets==1); + free(early);free(own_off);free(late); + /* Run the connector's actual preflight guard before the graph lookup. */ + dcp_node.profile=true; dcp_node.done=false; + processor.available=false; route.available=false; route.provider=NULL; + assert(probe(&connector)==-EPROBE_DEFER); + processor.available=true; route.available=true; + assert(probe(&connector)==-EPROBE_DEFER); /* changeset not complete */ + dcp_node.done=true; + assert(probe(&connector)==-EPROBE_DEFER); /* provider not registered */ + route.provider=&dcp; assert(probe(&connector)==2); + route.provider=NULL; route.available=false; processor.available=false; + assert(probe(&connector)==1); /* gate refusal still permits USB */ + dcp_node.done=false; dcp_node.profile=false; + assert(probe(&connector)==1); /* ordinary J613 without the changeset */ + dcp_node.profile=true; j613=false; + assert(probe(&connector)==1); /* another board never waits */ +} +''' +with tempfile.TemporaryDirectory() as t: + p = Path(t) / 'mux.c' + p.write_text(code) + subprocess.run(['clang', '-std=gnu11', '-Wall', '-Werror', '-fsanitize=address,undefined', str(p), '-o', str(p.with_suffix(''))], check=True) + result = subprocess.run([str(p.with_suffix(''))]) + if result.returncode: + sys.exit(1) +print('PASS early J613 defers; enabled/unregistered defers; registered has both muxes; refused/no-profile/other-board skip; original lookup: unavailable route or ancestor produces an early handle omits disabled DCP and stays ATC-only after coldplug/register; available-unregistered defers; late handle includes both') diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index 6fbfc5c06e49d..e05dbde4d3eae 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -53,6 +53,7 @@ def mutations(flag, names, base, expect=KILL): # test file -> [(arguments, expectation)]. '{out}' is a fresh directory. ASAHI_TESTS = { + 'j613-mux-order-tests.py': [([], PASS), (['--negative-control'], KILL)], 'dcpext26-budget-tests.py': [([], PASS)], 'dcpext26-queue-tests.py': [([], PASS)], 'dcpext26-lifecycle-tests.py': [([], PASS)], diff --git a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py index 7a2919f377b17..819d0968fb608 100644 --- a/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py +++ b/tools/testing/selftests/drivers/gpu/apple-dcp/transaction.py @@ -383,6 +383,10 @@ def probe(root): #define dev_err_probe(...) (-EINVAL) struct fwnode_handle { int unused; }; static int connect_error,irq_error,register_error,node_puts,mux_get_error,controller_mode; +#define EPROBE_DEFER 517 +/* Real OF/profile combinations are covered by j613-mux-order-tests.py. */ +static bool gate_pending; +static bool cd321x_display_gate_pending(void) { return gate_pending; } static void *devm_gpiod_get_optional(void *d,const char *s,int v) { (void)d;(void)s;(void)v;return NULL; } static void msleep(unsigned ms) { (void)ms; } static bool device_is_compatible(void *d,const char *s) { (void)d;(void)s;return false; } @@ -421,6 +425,9 @@ def probe(root): } int main(void) { struct cd321x c; + probe_initial(&c);gate_pending=true; + assert(tipd_init(&c.tps)==-EPROBE_DEFER&&!node_puts&&!reset_calls&&!gpio_calls); + gate_pending=false; probe_initial(&c);irq_error=-ENOSPC;phy_error=-ETIMEDOUT; assert(tipd_init(&c.tps)==-ENOSPC&&reset_calls==0&&gpio_calls==0&&port.owner==&route&&port.safe_pending&&unregisters==1&&node_puts==1); probe_initial(&c);connect_error=-EAGAIN;role_error=-EBUSY; From 952c236abf3f5c8f0963089f2204a17383e2c661 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Wed, 11 Nov 2026 22:00:38 -0500 Subject: [PATCH 30/32] drm/apple: recheck native26 catalogue under modeset locks A queued READY worker can acquire the locks after a layout commit withdraws admission and reconnect clears external_stopped. Avoid resetting an unadmitted mode, and skip an already completed modeset; preserve lost-route release and classic replay. The production helper replay models admission changing at lock acquisition; removing the new guard compiles and fails its assertion with -EINVAL. Assisted-by: LLM Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/iomfb.c | 12 ++++ tools/asahi/dcpext26-retrain-tests.py | 85 +++++++++++++++++++++++++++ tools/asahi/run-host-tests.py | 1 + 3 files changed, 98 insertions(+) create mode 100644 tools/asahi/dcpext26-retrain-tests.py diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c index 097d0f06bc727..39fa52cb77f92 100644 --- a/drivers/gpu/drm/apple/iomfb.c +++ b/drivers/gpu/drm/apple/iomfb.c @@ -1215,6 +1215,14 @@ static int dcp_retrain_active_crtc(struct apple_connector *connector, DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, ret); + /* + * A queued READY can wait here behind a layout-changing commit. That + * commit withdraws the native26 catalogue and queues rediscovery; the + * reconnect can already have cleared external_stopped again. Recheck + * catalogue admission under the modeset locks, before resetting a CRTC + * against a mode that mode_fixup must reject. A completed modeset also + * needs no replay. Rediscovery's READY will retry when modes are admitted. + */ crtc = connector->base.state ? connector->base.state->crtc : NULL; if (crtc && crtc->state && crtc->state->active && dcp_crtc_lost_route(crtc, connector)) { @@ -1229,6 +1237,10 @@ static int dcp_retrain_active_crtc(struct apple_connector *connector, struct drm_mode_set set = { .crtc = crtc }; ret = drm_atomic_helper_set_config(&set, &ctx); + } else if (dcp_is_ext26(dcp) && crtc && crtc->state && + (READ_ONCE(dcp->mode_state.valid) || + !dcp_has_mode(dcp, &crtc->state->mode))) { + ret = 0; } else if (crtc && crtc->state && crtc->state->active && dcp_crtc_can_retrain(crtc, connector) && power_cycle) { /* A commit that held the locks first may have set the mode. */ diff --git a/tools/asahi/dcpext26-retrain-tests.py b/tools/asahi/dcpext26-retrain-tests.py new file mode 100644 index 0000000000000..f6bd3ae394b2d --- /dev/null +++ b/tools/asahi/dcpext26-retrain-tests.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-only OR MIT +"""Replay a READY worker waiting behind a native26 layout-changing commit.""" +from pathlib import Path +import subprocess +import tempfile +import sys + +root = Path(__file__).resolve().parents[2] +s = (root / 'drivers/gpu/drm/apple/iomfb.c').read_text() +a = s.index('static int dcp_retrain_active_crtc(') +b = s.index('\nvoid dcp_handle_hotplug_actions', a) +body = s[a:b] +if '--negative-control' in sys.argv: + a = body.index('\t} else if (dcp_is_ext26(dcp) && crtc && crtc->state &&') + b = body.index('\t} else if (crtc && crtc->state && crtc->state->active &&', a) + body = body[:a] + body[b:] + +pre = r''' +#include +#include +#include +#define READ_ONCE(x) (x) +struct mode { int id; }; +struct drm_device { int unused; }; +struct drm_modeset_acquire_ctx { int unused; }; +struct drm_crtc_state { bool active; struct mode mode; }; +struct drm_crtc { struct drm_crtc_state *state; }; +struct apple_connector { struct { struct drm_device *dev; struct { struct drm_crtc *crtc; } *state; } base; }; +struct apple_dcp { bool native, admitted, external_stopped; struct { bool valid; } mode_state; }; +struct drm_mode_set { struct drm_crtc *crtc; }; +static int resets, toggles, releases, transition; +static bool lost; +static struct apple_dcp *current; +static void acquire(void) { + /* Model the commit that held the modeset locks before the queued worker. */ + if (transition) { + current->admitted = false; + current->mode_state.valid = false; + /* Reconnect has already started; the old patch's guard no longer holds. */ + current->external_stopped = false; + } +} +#define DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, flags, ret) do { acquire(); +#define DRM_MODESET_LOCK_ALL_END(dev, ctx, ret) } while (0) +static bool dcp_is_ext26(struct apple_dcp *d) { return d->native; } +static bool dcp_has_mode(struct apple_dcp *d, const struct mode *m) { return d->admitted && m->id == 1; } +static bool dcp_crtc_lost_route(struct drm_crtc *c, struct apple_connector *p) { return lost; } +static bool dcp_crtc_can_retrain(struct drm_crtc *c, struct apple_connector *p) { return true; } +static int drm_atomic_helper_set_config(struct drm_mode_set *s, void *ctx) { releases++; return 0; } +static int dcp_set_crtc_active(struct drm_crtc *c, void *ctx, bool on) { toggles++; c->state->active = on; return 0; } +static int drm_atomic_helper_reset_crtc(struct drm_crtc *c, void *ctx) { resets++; return current->admitted ? 0 : -22; } +''' +main = r''' +int main(void) { + (void)dcp_is_ext26; (void)dcp_has_mode; + struct drm_crtc_state cs = {.active=true,.mode.id=1}; + struct drm_crtc crtc = {.state=&cs}; + typeof(((struct apple_connector *)0)->base.state[0]) state = {.crtc=&crtc}; + struct apple_connector c = {.base.state=&state}; + struct apple_dcp d = {.native=true,.admitted=true}; current=&d; + transition=1; + assert(dcp_retrain_active_crtc(&c,&d,false)==0); + assert(!resets && !toggles && !releases); + transition=0; d.admitted=true; /* fresh catalogue READY must still retrain */ + assert(!dcp_retrain_active_crtc(&c,&d,false) && resets==1); + d.mode_state.valid=true; /* prior commit already completed the work */ + assert(!dcp_retrain_active_crtc(&c,&d,false) && resets==1); + d.native=false; d.mode_state.valid=false; + assert(!dcp_retrain_active_crtc(&c,&d,false) && resets==2); + assert(!dcp_retrain_active_crtc(&c,&d,true) && toggles==2); + d.native=true; d.admitted=false; lost=true; + assert(!dcp_retrain_active_crtc(&c,&d,false) && releases==1); +} +''' +with tempfile.TemporaryDirectory() as t: + p = Path(t) / 'retrain.c' + p.write_text(pre + body + main) + subprocess.run(['clang', '-std=gnu11', '-Wall', '-Werror', + '-Wno-unused-variable', '-fsanitize=address,undefined', + str(p), '-o', str(p.with_suffix(''))], check=True) + result = subprocess.run([str(p.with_suffix(''))]) + if result.returncode: + sys.exit(1) +print('PASS native26 retrain: lock-wait layout restart, fresh READY, completed commit, classic reset/power cycle') diff --git a/tools/asahi/run-host-tests.py b/tools/asahi/run-host-tests.py index e05dbde4d3eae..47c3f82f7c3c0 100755 --- a/tools/asahi/run-host-tests.py +++ b/tools/asahi/run-host-tests.py @@ -56,6 +56,7 @@ def mutations(flag, names, base, expect=KILL): 'j613-mux-order-tests.py': [([], PASS), (['--negative-control'], KILL)], 'dcpext26-budget-tests.py': [([], PASS)], 'dcpext26-queue-tests.py': [([], PASS)], + 'dcpext26-retrain-tests.py': [([], PASS), (['--negative-control'], KILL)], 'dcpext26-lifecycle-tests.py': [([], PASS)], 'dcpext26-modeset-tests.py': [([], PASS)], 'dcpext26-parity-tests.py': [([], PASS)], From 9bf9edc3dc2db14d8a0c1ce757f1f79e493ed8a9 Mon Sep 17 00:00:00 2001 From: Satya Benson Date: Fri, 13 Nov 2026 08:50:15 -0500 Subject: [PATCH 31/32] usb: typec: bound J613 display handoff waits and preserve PD fallback Replace the fallible coldplug completion property with built-in, allocation-free handoff arbitration. Publish refusal and failure at every coldplug exit, handle firmware-only display and absent DRM explicitly, and report display registration failures and unload. Serialize coldplug against fallback so a later module cannot reopen a refused handoff. Honor driver-core terminal retries and add an independent ten-second retry for the two J613 PD controllers. A missing module or failed DCP probe must not strand PD even if the global deferred-probe timeout is disabled. Ignore only the gated DCP subtree on fallback; keep PHY dependencies and working registered display providers mandatory/unchanged. Extend production extraction coverage through the full coldplug producer, allocation and changeset-application errors, refusal, nomodeset, missing DRM, late modules and both timeout paths. Register compiled runtime controls for ignored nomodeset and lost failure completion alongside the existing ordering control. Assisted-by: LLM Signed-off-by: Satya Benson --- drivers/gpu/drm/apple/apple_drv.c | 11 +- drivers/gpu/drm/apple/j613_dcp_activate.c | 20 +-- drivers/soc/apple/Kconfig | 8 + drivers/soc/apple/Makefile | 2 + drivers/soc/apple/j613-display.c | 149 ++++++++++++++++++ drivers/usb/typec/mux.c | 7 +- drivers/usb/typec/tipd/core.c | 17 +- include/linux/soc/apple/j613-display.h | 36 +++++ tools/asahi/j613-mux-order-tests.py | 145 +++++++++++++++-- tools/asahi/run-host-tests.py | 3 +- .../drivers/gpu/apple-dcp/transaction.py | 2 +- 11 files changed, 355 insertions(+), 45 deletions(-) create mode 100644 drivers/soc/apple/j613-display.c create mode 100644 include/linux/soc/apple/j613-display.h diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index bb59092bf63c9..012a22c0d93f7 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -7,6 +7,7 @@ * Copyright (C) 2014 Endless Mobile */ +#include #include #include #include @@ -2084,8 +2085,10 @@ static struct platform_driver apple_platform_driver = { static int __init appledrm_register(void) { int ret; - if (drm_firmware_drivers_only()) + if (drm_firmware_drivers_only()) { + apple_j613_display_fail(); return -ENODEV; + } /* A refused experimental profile keeps the boot framebuffer available. */ ret = apple_j613_25g83_coldplug(); @@ -2094,8 +2097,10 @@ static int __init appledrm_register(void) #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) ret = dcp_audio_register(); - if (ret) + if (ret) { + apple_j613_display_fail(); return ret; + } #endif ret = dcp_register(); if (ret) @@ -2105,6 +2110,7 @@ static int __init appledrm_register(void) return 0; dcp_unregister(); unregister_audio: + apple_j613_display_fail(); #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) dcp_audio_unregister(); #endif @@ -2113,6 +2119,7 @@ static int __init appledrm_register(void) static void __exit appledrm_unregister(void) { + apple_j613_display_fail(); #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO) dcp_audio_unregister(); #endif diff --git a/drivers/gpu/drm/apple/j613_dcp_activate.c b/drivers/gpu/drm/apple/j613_dcp_activate.c index d42a3216cd8d0..17492fb77ff7c 100644 --- a/drivers/gpu/drm/apple/j613_dcp_activate.c +++ b/drivers/gpu/drm/apple/j613_dcp_activate.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -495,6 +496,9 @@ int __init apple_j613_25g83_coldplug(void) if (!of_machine_is_compatible("apple,j613") && !of_machine_is_compatible("apple,j615")) return 0; + if (!apple_j613_display_begin()) + return -ENODEV; + nodes[J613_DCP] = of_find_node_by_path("dcp"); if (nodes[J613_DCP] && !of_device_is_compatible(nodes[J613_DCP], "apple,t8122-dcp")) { of_node_put(nodes[J613_DCP]); @@ -598,20 +602,8 @@ int __init apple_j613_25g83_coldplug(void) display_clock_hz); } out: - /* Publish completion on refusal too: disabled routes must then be skipped. */ - if (nodes[J613_DCP] && of_machine_is_compatible("apple,j613")) { - struct of_changeset *done = kzalloc_obj(*done); - - if (done) { - of_changeset_init(done); - if (of_changeset_add_prop_bool(done, nodes[J613_DCP], - "apple,j613-25g83-coldplug-done") || - of_changeset_apply(done)) { - of_changeset_destroy(done); - kfree(done); - } - } - } + /* Every exit after begin publishes a terminal state, without allocation. */ + apple_j613_display_end(ret ?: (external_gate_applied ? 0 : -ENODEV)); for (i = 0; i < ARRAY_SIZE(nodes); i++) of_node_put(nodes[i]); return ret; diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index e0fd70dbba09b..ae3b7d022d728 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -4,6 +4,14 @@ if ARCH_APPLE || COMPILE_TEST menu "Apple SoC drivers" +config APPLE_J613_DISPLAY_SYNC + bool + default y + depends on OF + help + Built-in arbitration between the J613 display handoff and USB PD. + State survives an absent, failed or later-loaded display module. + config APPLE_DBGVIEW tristate "Apple SoC read-only debug register views" depends on ARCH_APPLE || COMPILE_TEST diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 5065bb7a1324a..5ee5ad049c771 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -1,5 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_APPLE_J613_DISPLAY_SYNC) += j613-display.o + obj-$(CONFIG_APPLE_DBGVIEW) += apple-dbgview.o apple-dbgview-y = dbgview.o diff --git a/drivers/soc/apple/j613-display.c b/drivers/soc/apple/j613-display.c new file mode 100644 index 0000000000000..0070642fa6614 --- /dev/null +++ b/drivers/soc/apple/j613-display.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* Allocation-free, built-in arbitration of J613 coldplug and PD fallback. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include