From 894648ed3b72a90a739a5cdd79131f9c805eee54 Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 15:58:55 -0800 Subject: [PATCH 1/5] drm/apple: Answer the firmware 27 analytics event with its dictionary About an hour after it starts, and periodically after that, the 27.x DCP firmware sends an analytics event as callback D113: an event name and a serialized dictionary in, the dictionary again out. The dictionary is passed by pointer, so when the call returns the firmware deserializes the reply's copy, and if that fails it stops. The firmware 27 callback table answered D113 with zeros. On the M5 Ultra (J775D) the external DCP crashed 0.4 ms after acking one ("OSDictionary deserialization failed"), with nothing else on the link for 20 s, and the display was gone until a reboot. Answer D113 as the desktop 26.6 table already answers the same event (D114 there): return the dictionary unchanged and report the event not sent. The record and the reply move to iomfb.h, shared by both tables, so the two answers cannot drift apart again. Desktop 26.6 behaves as before, except that a reply to an event without a dictionary is now zeroed rather than left as the firmware's buffer held it. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/gpu/drm/apple/iomfb.h | 40 +++++++++++++++++++++ drivers/gpu/drm/apple/iomfb_v26_6_desktop.c | 32 ++--------------- drivers/gpu/drm/apple/iomfb_v27.c | 17 +++++++-- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index 568a6a7b8c529..d8933d1f1dc26 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -4,6 +4,8 @@ #ifndef __APPLE_DCPEP_H__ #define __APPLE_DCPEP_H__ +#include +#include #include #include "version_utils.h" @@ -470,6 +472,44 @@ struct dcp_hotplug_resp_v26_6 { u8 opaque[0x4c]; } __packed; +/* + * A name and a serialized dictionary: the firmware's analytics event + * (D113 on 27.x, D114 on desktop 26.6). + */ +struct dcp_dict_passthrough_req { + u8 key[0x40]; + u8 value[0x1000]; + u8 value_null; + u8 padding[3]; +} __packed; + +struct dcp_dict_passthrough_resp { + u8 value[0x1000]; + u8 ret; + u8 padding[3]; +} __packed; + +static_assert(sizeof(struct dcp_dict_passthrough_req) == 0x1044); +static_assert(sizeof(struct dcp_dict_passthrough_resp) == 0x1004); + +/* + * The analytics event passes its dictionary by pointer, so the firmware + * deserializes the reply's copy when the call returns, and stops if that + * fails. Return the dictionary as it came, and report the event not sent. + * A zeroed reply is not a dictionary. + */ +static inline void +dcp_dict_passthrough_reply(const struct dcp_dict_passthrough_req *req, + struct dcp_dict_passthrough_resp *resp) +{ + if (req->value_null) + memset(resp->value, 0, sizeof(resp->value)); + else + memcpy(resp->value, req->value, sizeof(resp->value)); + resp->ret = false; + memset(resp->padding, 0, sizeof(resp->padding)); +} + /* A boolean argument, padded to four bytes. */ struct dcp_bool_arg { u8 value; diff --git a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c index 05417d2c2b2cb..a773bf68a61cc 100644 --- a/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c +++ b/drivers/gpu/drm/apple/iomfb_v26_6_desktop.c @@ -52,41 +52,15 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { #include "iomfb_template.c" -struct dcp_dict_passthrough_req_v26_6 { - u8 key[0x40]; - u8 value[0x1000]; - u8 value_null; - u8 padding[3]; -} __packed; - -struct dcp_dict_passthrough_resp_v26_6 { - u8 value[0x1000]; - u8 ret; - u8 padding[3]; -} __packed; - -static_assert(sizeof(struct dcp_dict_passthrough_req_v26_6) == 0x1044); -static_assert(sizeof(struct dcp_dict_passthrough_resp_v26_6) == 0x1004); - /* - * D114 periodically publishes dictionary-valued statistics. The value is an - * in/out pointer, which the firmware deserializes again: the firmware's own - * client returns the serialized buffer unchanged and reports false. An - * all-zero reply is not a harmless false. + * D114 periodically publishes dictionary-valued statistics: the analytics + * event, answered by dcp_dict_passthrough_reply(). */ static bool trampoline_passthrough_dict_v26_6(struct apple_dcp *dcp, int tag, void *out, void *in) { - const struct dcp_dict_passthrough_req_v26_6 *req = in; - struct dcp_dict_passthrough_resp_v26_6 *resp = out; - trace_iomfb_callback(dcp, tag, "dcpep_cb_passthrough_dict"); - - if (!req->value_null) - memcpy(resp->value, req->value, sizeof(resp->value)); - - resp->ret = false; - memset(resp->padding, 0, sizeof(resp->padding)); + dcp_dict_passthrough_reply(in, out); return true; } diff --git a/drivers/gpu/drm/apple/iomfb_v27.c b/drivers/gpu/drm/apple/iomfb_v27.c index da0ed8b8f9c24..bd934449b6917 100644 --- a/drivers/gpu/drm/apple/iomfb_v27.c +++ b/drivers/gpu/drm/apple/iomfb_v27.c @@ -329,6 +329,18 @@ static bool trampoline_tiling_state_v27(struct apple_dcp *dcp, int tag, return true; } +/* + * D113, about an hour after the firmware starts and periodically after: the + * analytics event. A zeroed reply crashes the firmware. + */ +static bool trampoline_analytics_event_v27(struct apple_dcp *dcp, int tag, + void *out, void *in) +{ + trace_iomfb_callback(dcp, tag, "dcpep_cb_analytics_event"); + dcp_dict_passthrough_reply(in, out); + return true; +} + /* Native D115: same16-byte input as D114, boolean/padded4 output. */ static bool trampoline_set_tiling_state_v27(struct apple_dcp *dcp, int tag, void *out, void *in) @@ -362,7 +374,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = { [110] = trampoline_true, [111] = trampoline_create_backlight_service, [112] = trampoline_true, - [113] = trampoline_zero, + [113] = trampoline_analytics_event_v27, [116] = trampoline_nop, [117] = trampoline_zero, [118] = trampoline_zero, @@ -449,7 +461,8 @@ static const struct { [110] = { 0x0, 0x4 }, [111] = { 0x0, 0x4 }, [112] = { 0x0, 0x4 }, - [113] = { 0x1044, 0x1004 }, + [113] = { sizeof(struct dcp_dict_passthrough_req), + sizeof(struct dcp_dict_passthrough_resp) }, [116] = { 0xc, 0x0 }, [117] = { 0x14, 0x4 }, [118] = { 0x3e88, 0x4 }, From fd2362b925e05109175af5ca130266df5e96af9c Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 14:27:24 -0800 Subject: [PATCH 2/5] drm/apple: dptx: Key the T8140 external port rules on its profile The T8140 external DPTX rules (the one port named by the DCP index, the J700 training and lane checks, and its max-drive reply) are keyed on the AFK v2 codec flag. Firmware 27 sets that flag for every DCP, so on T6050 the rules refused its ports 0 and 1 (-EINVAL) and no DisplayPort link came up. Key them on the T8140 external profile (dcp_is_external_h17p()) instead. T8140's external DCP takes the same paths as before; firmware 27 DCPs name their ports by unit, as earlier firmware does, and use the common DPTX calls. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/gpu/drm/apple/dptx-service.h | 8 ++++++-- drivers/gpu/drm/apple/dptxep.c | 17 +++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/drivers/gpu/drm/apple/dptx-service.h b/drivers/gpu/drm/apple/dptx-service.h index 3d990a9ca1ea1..2cb8f68210eb8 100644 --- a/drivers/gpu/drm/apple/dptx-service.h +++ b/drivers/gpu/drm/apple/dptx-service.h @@ -5,14 +5,18 @@ #include "afk.h" #include "dptxep.h" +/* + * T8140 external firmware names its one port by the DCP index; the others, + * firmware 27 among them, by port unit 0 or 1. + */ static inline int dptx_service_port(const char *name, const char *class, - s64 unit, bool wire_v2, u32 index, + s64 unit, bool unit_is_index, u32 index, u32 num_ports) { if (!name || !class || strcmp(name, "dcpdptx-port-epic") || strcmp(class, "AppleDCPDPTXRemotePort")) return -EINVAL; - if (wire_v2) + if (unit_is_index) return num_ports == 1 && unit == index ? 0 : -EINVAL; return unit >= 0 && unit < 2 ? unit : -EINVAL; } diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 3796aef4ac128..553b2df5dae2c 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -272,7 +272,7 @@ dptxport_call_get_max_drive_settings(struct apple_epic_service *service, return -EINVAL; reply->retcode = cpu_to_le32(0); - if (service->ep->wire_v2) { + if (dcp_is_external_h17p(service->ep->dcp)) { memset(reply, 0, reply_size); put_unaligned_le32(3, reply_ + 20); put_unaligned_le32(3, reply_ + 24); @@ -425,7 +425,7 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic u64 lane_count = le64_to_cpu(request->lane_count); - if (service->ep->wire_v2) { + if (dcp_is_external_h17p(dcp)) { ret = dptx_j700_validate_lanes(dptx, lane_count); if (ret) return ret; @@ -439,7 +439,8 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic dptx->phy_ops.dp.lanes = lane_count; /* USB4 DP IN has no ATC PHY; still accept the lane count. */ dptx->phy_ops.dp.set_lanes = - service->ep->wire_v2 || dcp_is_usb4_output(dcp) || (dcp->dptx_phy > 3); + dcp_is_external_h17p(dcp) || dcp_is_usb4_output(dcp) || + (dcp->dptx_phy > 3); break; default: dev_err(dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count); @@ -983,7 +984,7 @@ static int dptxport_prepare(struct apple_epic_service *service, const char *name (!class || !class[0] || !strcmp(class, "AppleDCPDPTXRemotePort"))) port = unit >= 0 && unit < 2 ? unit : -EINVAL; else - port = dptx_service_port(name, class, unit, service->ep->wire_v2, + port = dptx_service_port(name, class, unit, dcp_is_external_h17p(dcp), dcp->index, dcp->hw.num_dptx_ports); if (port < 0) @@ -1080,8 +1081,12 @@ static int dptxport_group_call(struct apple_epic_service *service, u16 group, { struct dptx_port *dptx = service->cookie; - /* Keep native AFK-v2 training and its refusal policy distinct. */ - if (service->ep->wire_v2) + /* + * Keep the T8140 external (J700) training and its refusal policy + * distinct. It keys on that profile, not on the AFK v2 codec, which + * firmware 27 speaks with the common DPTX calls. + */ + if (dcp_is_external_h17p(service->ep->dcp)) return dptxport_v2_group_call(service, group, command, data, size, reply, room); From 6728f8c61c5978370b012739d0ba921abcd934db Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 14:27:25 -0800 Subject: [PATCH 3/5] drm/apple: dcp: Choose the firmware 27 method table by firmware build Firmware 27.2 renumbered the IOMFB AP methods: the IOMobileFramebufferAP methods moved up by 50, and set_create_DFB, vi_set_temperature_hint and dfb_ready_query by one. The UPPipe methods are unchanged. A DCP running 27.2 that is sent a 27.0 tag panics ("link_rpc_lookup callback not found"); on T6050 the first one sent was A427. Keep a method table per 27.x build and choose it at probe from apple,firmware-compat ("27.0.0" or "27.2.0"). A firmware-compat with no table is refused at probe with a message naming it, where a 27.x string other than 27.0.0 was refused silently before. The 27.2 position of dfb_ready_query (A385) follows the renumbering and has not been observed in a call. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/gpu/drm/apple/dcp-internal.h | 2 + drivers/gpu/drm/apple/dcp.c | 12 +++- .../gpu/drm/apple/iomfb-peer-runtime-v27.inc | 4 +- drivers/gpu/drm/apple/iomfb.h | 3 + drivers/gpu/drm/apple/iomfb_v27.c | 69 ++++++++++++++++++- 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index a9d977033c6c9..42e325d9ebbc3 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -345,6 +345,8 @@ struct apple_dcp { /* firmware version and compatible firmware version */ enum dcp_firmware_version fw_compat; + /* Firmware 27.x: the method IDs of this build (iomfb_v27_methods()). */ + const struct dcp_method_entry *v27_methods; bool external; /* * T8122 dcpext: the panel's DRM device never waits on this processor. diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index d80e2a1914135..a4b7ab9c1c6f9 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -4341,8 +4341,11 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev) if (hw->firmware_compat == DCP_FIRMWARE_V_27_0) { ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); - if (ret < 0 || strcmp(compat_str, "27.0.0")) + if (ret < 0 || !iomfb_v27_methods(compat_str)) { + dev_err(dev, "DCP firmware-compat %s has no 27.x method table\n", + ret < 0 ? "missing" : compat_str); return DCP_FIRMWARE_UNKNOWN; + } return DCP_FIRMWARE_V_27_0; } @@ -5371,6 +5374,13 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) iomfb_queue_init(dcp); dcp->fw_compat = fw_compat; + if (fw_compat == DCP_FIRMWARE_V_27_0) { + char compat_str[DCP_FW_VERSION_STR_LEN]; + + /* Checked by dcp_check_firmware_version(). */ + dcp_read_fw_version(dev, "apple,firmware-compat", compat_str); + dcp->v27_methods = iomfb_v27_methods(compat_str); + } if (fw_compat == DCP_FIRMWARE_V_26_6_DESKTOP) { dcp->desktop = devm_kzalloc(dev, sizeof(*dcp->desktop), GFP_KERNEL); if (!dcp->desktop) diff --git a/drivers/gpu/drm/apple/iomfb-peer-runtime-v27.inc b/drivers/gpu/drm/apple/iomfb-peer-runtime-v27.inc index c2a681328c102..0c0089e1ce979 100644 --- a/drivers/gpu/drm/apple/iomfb-peer-runtime-v27.inc +++ b/drivers/gpu/drm/apple/iomfb-peer-runtime-v27.inc @@ -83,12 +83,12 @@ static void dcp_peer_started_on_owner(struct apple_dcp *owner, void *data, void dcp_peer_swap_armed_v27(owner, id); trace_iomfb_swap_submit(owner, id); trace_iomfb_swap_submit(peer, id); - dcp_push(owner, false, &dcp_methods[dcpep_swap_submit], sizeof(owner->present_v27), + dcp_push(owner, false, &owner->v27_methods[dcpep_swap_submit], sizeof(owner->present_v27), sizeof(struct dcp_swap_submit_resp_v27), &owner->present_v27, dcp_peer_submitted, owner); if (READ_ONCE(owner->crashed)) return; - dcp_push(peer, false, &dcp_methods[dcpep_swap_submit], sizeof(owner->peer_present_v27), + dcp_push(peer, false, &peer->v27_methods[dcpep_swap_submit], sizeof(owner->peer_present_v27), sizeof(struct dcp_swap_submit_resp_v27), &owner->peer_present_v27, dcp_peer_submitted, owner); if (READ_ONCE(peer->crashed)) diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h index d8933d1f1dc26..c8455e4f3578c 100644 --- a/drivers/gpu/drm/apple/iomfb.h +++ b/drivers/gpu/drm/apple/iomfb.h @@ -212,6 +212,9 @@ struct dcp_method_entry { char tag_h17g[4]; }; +/* The IOMFB method table of a firmware 27.x build, by its compat string; NULL if unknown. */ +const struct dcp_method_entry *iomfb_v27_methods(const char *compat); + #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_v27.c b/drivers/gpu/drm/apple/iomfb_v27.c index bd934449b6917..b52f51666411c 100644 --- a/drivers/gpu/drm/apple/iomfb_v27.c +++ b/drivers/gpu/drm/apple/iomfb_v27.c @@ -51,7 +51,8 @@ static_assert(offsetof(struct dcp_swap_v27, brightness_update) == 0x354); static_assert(offsetof(struct dcp_swap_v27, brightness_nits) == 0x35e); static_assert(sizeof(struct dcp_swap_v27) == 0x6e0); -static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { +/* Firmware 27.0, the build Aurora's t6050-gpu table was written for. */ +static const struct dcp_method_entry dcp_methods_27_0[dcpep_num_methods] = { IOMFB_METHOD("A000", dcpep_late_init_signal), IOMFB_METHOD("A027", dcpep_setup_video_limits), IOMFB_METHOD("A136", iomfbep_a131_pmu_service_matched), @@ -95,6 +96,72 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = { IOMFB_METHOD("A483", dcpep_register_dfb_surface), }; +/* + * Firmware 27.2 renumbered the AP methods: UnifiedPipeline2 grew into + * A400/A401, so IOMobileFramebufferAP moved up by 50 (start_signal A451, + * get_color_remap_mode A477), and set_create_DFB and vi_set_temperature_hint + * up by one (dfb_ready_query is moved with them, inferred, not yet observed). + * The UPPipe A0xx and A1xx IDs are unchanged. These are firmware 27.2's + * method tags: each IOMobileFramebufferAP / UnifiedPipeline2 method has its + * own constant tag. The firmware rejects an unknown tag with a panic + * ("link_rpc_lookup callback not found"). + */ +static const struct dcp_method_entry dcp_methods_27_2[dcpep_num_methods] = { + IOMFB_METHOD("A000", dcpep_late_init_signal), + IOMFB_METHOD("A027", dcpep_setup_video_limits), + IOMFB_METHOD("A136", iomfbep_a131_pmu_service_matched), + IOMFB_METHOD("A137", iomfbep_a132_backlight_service_matched), + IOMFB_METHOD("A390", dcpep_set_create_dfb), + IOMFB_METHOD("A391", iomfbep_a358_vi_set_temperature_hint), + IOMFB_METHOD("A451", dcpep_start_signal), + IOMFB_METHOD("A456", dcpep_swap_start), + IOMFB_METHOD("A458", dcpep_swap_submit), + IOMFB_METHOD("A461", dcpep_set_display_device), + IOMFB_METHOD("A462", dcpep_is_main_display), + IOMFB_METHOD("A463", dcpep_set_digital_out_mode), + IOMFB_METHOD("A473", iomfbep_set_matrix), + IOMFB_METHOD("A477", iomfbep_get_color_remap_mode), + IOMFB_METHOD("A500", dcpep_set_parameter_dcp), + IOMFB_METHOD("A504", dcpep_create_default_fb), + IOMFB_METHOD("A037", dcpep_get_dfb_compression_info), + IOMFB_METHOD("A036", dcpep_get_dfb_info), + IOMFB_METHOD("A103", dcpep_get_dfb_layout), + IOMFB_METHOD("A503", dcpep_get_dfb_state), + IOMFB_METHOD("A104", dcpep_set_dfb_dimensions), + IOMFB_METHOD("A105", dcpep_commit_dfb_info), + IOMFB_METHOD("A035", dcpep_dfb_query), + IOMFB_METHOD("A385", dcpep_dfb_ready_query), + IOMFB_METHOD("A353", dcpep_pipe_query_353), + IOMFB_METHOD("A352", dcpep_pipe_set_352), + IOMFB_METHOD("A465", dcpep_pipe_cfg_415), + IOMFB_METHOD("A033", dcpep_pipe_cfg_031), + IOMFB_METHOD("A464", dcpep_pipe_cfg_414), + IOMFB_METHOD("A539", dcpep_pipe_query_478), + IOMFB_METHOD("A535", dcpep_pipe_query_474), + IOMFB_METHOD("A537", dcpep_pipe_set_476), + IOMFB_METHOD("A478", dcpep_pipe_set_428), + IOMFB_METHOD("A508", dcpep_enable_disable_video_power_savings), + IOMFB_METHOD("A516", dcpep_first_client_open), + IOMFB_METHOD("A517", iomfbep_last_client_close), + IOMFB_METHOD("A525", dcpep_set_display_refresh_properties), + IOMFB_METHOD("A528", dcpep_flush_supports_power), + IOMFB_METHOD("A529", iomfbep_abort_swaps_dcp), + IOMFB_METHOD("A534", dcpep_set_power_state), + IOMFB_METHOD("A533", dcpep_register_dfb_surface), +}; + +const struct dcp_method_entry *iomfb_v27_methods(const char *compat) +{ + if (!strcmp(compat, "27.0.0")) + return dcp_methods_27_0; + if (!strcmp(compat, "27.2.0")) + return dcp_methods_27_2; + return NULL; +} + +/* The template's calls resolve through the table probe chose for this build. */ +#define dcp_methods (dcp->v27_methods) + #define iomfb_modeset_h17p iomfb_modeset_v27 #define iomfb_flush_h17p iomfb_flush_v27 #define iomfb_poweron_h17p iomfb_poweron_v27 From 0269fbf1cae8441240bbd5cf7ce66329cafdf3db Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 14:27:27 -0800 Subject: [PATCH 4/5] drm/apple: Send firmware 27 surface element sizes in bytes The DCP reads a surface's pix_size and each plane's tile_size as bytes per element. Linear planes sent 1 (block width times height) and the DFB sent 1, where XRGB8888 is 4 and the DFB's own format says 4 or 8. has_compr_info is a presence flag, set only when compression_info describes a compressed (Interchange) plane, and was 1 for linear surfaces too. On firmware 27 (DCP_FIRMWARE_V_27_0), send bytes per element, the element's width and height in pixels, and has_compr_info only for Interchange planes. The DFB's format and bytes per element now come from one helper, apple_dcp_dfb_format(), which its allocation also uses. Earlier firmware keeps the values it has run with: before widening this, check a linear XRGB8888 and an NV12 swap on 12.x to 26.x firmware. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/gpu/drm/apple/dcp-internal.h | 15 ++++++++ drivers/gpu/drm/apple/iomfb-dfb.h | 49 +++++++++++++++++--------- drivers/gpu/drm/apple/iomfb_template.c | 7 ++-- drivers/gpu/drm/apple/plane.c | 31 +++++++++++++--- 4 files changed, 80 insertions(+), 22 deletions(-) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 42e325d9ebbc3..023ca78f47469 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -896,6 +896,21 @@ static inline bool dcp_ext_dptx_607(const struct apple_dcp *dcp) dcp->hw.fw_14_7_endpoints; } +/* + * Whether this DCP reads a surface's element geometry as the surface record + * defines it: bytes per element (pix_size, a linear plane's tile_size), the + * element's width and height in pixels, and compression information only for + * a compressed plane. Only firmware 27 (DCP_FIRMWARE_V_27_0, which only + * T6050's DCP hw data selects) is tested with it. Earlier firmware keeps the + * fixed 1/1/1 and has_compr_info 1 it has run on: before widening this, + * check a linear XRGB8888 and an NV12 swap, and the default framebuffer, on + * 12.x-26.x firmware. + */ +static inline bool dcp_surface_element_geometry(const struct apple_dcp *dcp) +{ + return dcp->fw_compat == DCP_FIRMWARE_V_27_0; +} + #define DCP_AUDIO_MAX_CHANS 15 #endif /* __APPLE_DCP_INTERNAL_H__ */ diff --git a/drivers/gpu/drm/apple/iomfb-dfb.h b/drivers/gpu/drm/apple/iomfb-dfb.h index 30e84ab8366c3..8175cb2d3c922 100644 --- a/drivers/gpu/drm/apple/iomfb-dfb.h +++ b/drivers/gpu/drm/apple/iomfb-dfb.h @@ -54,39 +54,56 @@ static inline u64 apple_dcp_dfb_plane_size(u32 width, u32 height, u32 bpe, return data + ALIGN(2 * roundup_pow_of_two(cols) * roundup_pow_of_two(rows), 128); } -static inline int apple_dcp_dfb_allocation(u32 width, u32 height, u32 format, - u32 compression, u64 region_size, - u32 *surface_format, u32 *allocation) +/* + * The DFB's surface format and bytes per element (the surface record's pix_size) + * for the firmware's format and compression; *alpha_plane for a separate alpha plane. + */ +static inline int apple_dcp_dfb_format(u32 format, u32 compression, + u32 *surface_format, u32 *bpe, + bool *alpha_plane) { - u32 bpe; - u64 size; - bool alpha_plane = false; - - if (!width || !height || width > 16384 || height > 16384 || !region_size) - return -EINVAL; - if (compression != 0 && compression != 2 && compression != 3) - return -EOPNOTSUPP; + *alpha_plane = false; switch (format) { case 0x62336138: if (compression == 2) { format = 0x77343061; - bpe = 8; + *bpe = 8; } else { - bpe = 4; - alpha_plane = true; + *bpe = 4; + *alpha_plane = true; } break; case 0x77343061: - bpe = 8; + *bpe = 8; break; case 0x42475241: case 0x77333072: case 0x6c313072: - bpe = 4; + *bpe = 4; break; default: return -EOPNOTSUPP; } + *surface_format = format; + return 0; +} + +static inline int apple_dcp_dfb_allocation(u32 width, u32 height, u32 format, + u32 compression, u64 region_size, + u32 *surface_format, u32 *allocation) +{ + u32 bpe; + u64 size; + bool alpha_plane; + int ret; + + if (!width || !height || width > 16384 || height > 16384 || !region_size) + return -EINVAL; + if (compression != 0 && compression != 2 && compression != 3) + return -EOPNOTSUPP; + ret = apple_dcp_dfb_format(format, compression, &format, &bpe, &alpha_plane); + if (ret) + return ret; size = apple_dcp_dfb_plane_size(width, height, bpe, compression); if (alpha_plane) size += apple_dcp_dfb_plane_size(width, height, 1, compression); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index d1fb3226547f8..af1f9dd61e39b 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -636,7 +636,8 @@ static bool iomfbep_cb_create_dfb_surface(struct apple_dcp *dcp, int tag, { struct dcp_dfb_dimensions_h17p dim; struct dcp_surface *surface = (void *)req->surface; - u32 format, allocation; + u32 format, allocation, bpe; + bool alpha_plane; int ret = -EINVAL; memcpy(&dim, in, sizeof(dim)); @@ -653,9 +654,11 @@ static bool iomfbep_cb_create_dfb_surface(struct apple_dcp *dcp, int tag, return true; } + apple_dcp_dfb_format(dcp->dfb_format, dcp->dfb_compression, + &format, &bpe, &alpha_plane); surface->is_premultiplied = 1; surface->format = format; - surface->pix_size = 1; + surface->pix_size = dcp_surface_element_geometry(dcp) ? bpe : 1; surface->pel_w = 1; surface->pel_h = 1; surface->buf_size = allocation; diff --git a/drivers/gpu/drm/apple/plane.c b/drivers/gpu/drm/apple/plane.c index a0343aa5ec5ec..ec408460ef03a 100644 --- a/drivers/gpu/drm/apple/plane.c +++ b/drivers/gpu/drm/apple/plane.c @@ -144,8 +144,16 @@ static int apple_get_ix_layout(u32 width, u32 height, return 0; } +static bool apple_plane_element_geometry(struct drm_crtc *crtc) +{ + struct apple_dcp *dcp = platform_get_drvdata(to_apple_crtc(crtc)->dcp); + + return dcp_surface_element_geometry(dcp); +} + /* Use exactly the same geometry for admission and surface construction. */ static int apple_plane_get_fb_layout(struct drm_framebuffer *fb, + bool element_geometry, struct apple_plane_layout *planes, u32 *buf_size) { @@ -194,7 +202,9 @@ static int apple_plane_get_fb_layout(struct drm_framebuffer *fb, return -EINVAL; p->image.tile_w = drm_format_info_block_width(fmt, i); p->image.tile_h = drm_format_info_block_height(fmt, i); - p->tile_size = p->image.tile_w * p->image.tile_h; + /* tile_size is bytes per element, not pixels. */ + p->tile_size = element_geometry ? fmt->char_per_block[i] : + p->image.tile_w * p->image.tile_h; p->stride = fb->pitches[i]; break; default: @@ -250,7 +260,9 @@ static int apple_plane_atomic_check(struct drm_plane *plane, if (ret < 0) return ret; - ret = apple_plane_get_fb_layout(new_plane_state->fb, layouts, &buf_size); + ret = apple_plane_get_fb_layout(new_plane_state->fb, + apple_plane_element_geometry(new_plane_state->crtc), + layouts, &buf_size); if (ret) return ret; @@ -416,8 +428,9 @@ static void apple_plane_atomic_update(struct drm_plane *plane, struct drm_framebuffer *fb = base->fb; const struct drm_format_info *fmt = fb->format; + bool element_geometry = apple_plane_element_geometry(base->crtc); - if (WARN_ON_ONCE(apple_plane_get_fb_layout(fb, layouts, &buf_size))) { + if (WARN_ON_ONCE(apple_plane_get_fb_layout(fb, element_geometry, layouts, &buf_size))) { memset(&new_state->surf, 0, sizeof(new_state->surf)); return; } @@ -448,7 +461,7 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .surface_id = plane->base.id, .buf_size = buf_size, - /* Only used for compressed or multiplanar surfaces */ + /* Only used for compressed or multiplanar surfaces before firmware 27 */ .pix_size = 1, .pel_w = 1, .pel_h = 1, @@ -457,6 +470,16 @@ static void apple_plane_atomic_update(struct drm_plane *plane, .has_compr_info = 1, }; + if (element_geometry) { + /* Bytes per element, and the element's width and height in pixels */ + new_state->surf.pix_size = fmt->char_per_block[0]; + new_state->surf.pel_w = drm_format_info_block_width(fmt, 0); + new_state->surf.pel_h = drm_format_info_block_height(fmt, 0); + /* Present only when compression_info describes a compressed plane */ + new_state->surf.has_compr_info = + fb->modifier == DRM_FORMAT_MOD_APPLE_INTERCHANGE; + } + /* Populate plane information for planar formats */ struct dcp_surface *surf = &new_state->surf; for (int i = 0; fb->format->num_planes && i < fb->format->num_planes; i++) { From e668f77ccf3eb424e143c0d3caa7dbf7fb437a2c Mon Sep 17 00:00:00 2001 From: Jeff Queisser Date: Mon, 2 Nov 2026 16:01:10 -0800 Subject: [PATCH 5/5] drm/apple: dcp: Print the T6050 AP-link traffic that led to a crash A DCP crash's log names the failing check, but not what the host had asked for just before it. The one natural crash on J775D left no trace of its calls, and a crashed DCP needs a reboot before the display works again, so whatever is kept has to reach the kernel log. Each T6050 DCP now keeps its last 256 IOMFB calls, callbacks and swaps and its mailbox messages on every endpoint in a ring of its own (8 KiB), and prints it once when it crashes, before the crash handling sends anything. The ring is fed by the driver's existing tracepoints (iomfb_push, iomfb_callback, iomfb_swap_submit, iomfb_swap_complete, dcp_send_msg, dcp_recv_msg) through probes registered with the DCP as their data, so each DCP records only its own traffic and no call site changes. The probes are registered before the bind and removed when the DCP is unbound. call_history=0 (module parameter) turns it off. T6050 only (apple,t6050-dcp and apple,t6050-dcpext). Without CONFIG_TRACING the tracepoints are not built and the history is compiled out. Signed-off-by: Jeff Queisser Co-Authored-By: Claude Opus 5.5 --- drivers/gpu/drm/apple/dcp-internal.h | 5 + drivers/gpu/drm/apple/dcp.c | 261 +++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 023ca78f47469..bfd095637f879 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -290,6 +290,8 @@ struct dcp_desktop_state { struct dcpext_scanout; struct dcp_hdmi; +struct dcp_call_history; + struct apple_dcp { /* Generation-specific wire/transaction state, sharing the common KMS owner. */ struct iomfb_m4 *v26_6; @@ -405,6 +407,9 @@ struct apple_dcp { /* DCP has crashed */ bool crashed; + /* T6050: the last AP-link messages, printed at a crash (dcp_call_history_start()) */ + struct dcp_call_history *call_history; + DECLARE_BITMAP(iomfb_surfaces, DCP_MAX_PLANES); /************* IOMFB ************************************************** diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index a4b7ab9c1c6f9..bda1f55b1adb5 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -998,6 +999,261 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message) afk_receive_message(ep, message); } +/* + * T6050: the AP-link traffic that led to a DCP crash. A crashed DCP needs a + * reboot to show anything again, so the record has to reach the kernel log, + * beside the firmware's own crash log. Each T6050 DCP keeps its last + * DCP_CALL_HISTORY_LEN IOMFB calls, callbacks, swaps and mailbox messages in + * a small ring of its own, fed by this driver's tracepoints (a probe per DCP + * that takes only that DCP's events), and prints it once when it crashes. + */ +#define DCP_CALL_HISTORY_LEN 256U + +static bool call_history = true; +module_param(call_history, bool, 0444); +MODULE_PARM_DESC(call_history, + "T6050: print each DCP's last AP-link messages when it crashes (default: on)"); + +enum dcp_call_kind { + DCP_CALL_SEND, + DCP_CALL_RECV, + DCP_CALL_PUSH, + DCP_CALL_CALLBACK, + DCP_CALL_SWAP_SUBMIT, + DCP_CALL_SWAP_COMPLETE, +}; + +struct dcp_call_record { + u64 time; /* local_clock(), as the kernel log's stamps */ + u64 value; /* the message, or the swap ID */ + const char *name; /* method or callback name, static */ + u16 number; /* callback number, or method context */ + u8 kind; + u8 endpoint; + char tag[4]; /* method tag */ +}; + +struct dcp_call_history { + raw_spinlock_t lock; + bool frozen; + unsigned int count; + struct dcp_call_record rec[DCP_CALL_HISTORY_LEN]; +}; + +static bool dcp_is_t6050(struct device *dev) +{ + return of_device_is_compatible(dev->of_node, "apple,t6050-dcpext") || + of_device_is_compatible(dev->of_node, "apple,t6050-dcp"); +} + +static void dcp_call_record(struct apple_dcp *dcp, const struct dcp_call_record *in) +{ + struct dcp_call_history *h = dcp->call_history; + unsigned long flags; + + raw_spin_lock_irqsave(&h->lock, flags); + if (!h->frozen) { + struct dcp_call_record *r = &h->rec[h->count++ % DCP_CALL_HISTORY_LEN]; + + *r = *in; + r->time = local_clock(); + } + raw_spin_unlock_irqrestore(&h->lock, flags); +} + +/* Each probe is registered with its DCP as data and drops the others' events. */ +static void dcp_call_history_send(void *data, struct apple_dcp *dcp, + u8 endpoint, u64 message) +{ + if (dcp == data) + dcp_call_record(dcp, &(struct dcp_call_record){ + .kind = DCP_CALL_SEND, .endpoint = endpoint, + .value = message }); +} + +static void dcp_call_history_recv(void *data, struct apple_dcp *dcp, + u8 endpoint, u64 message) +{ + if (dcp == data) + dcp_call_record(dcp, &(struct dcp_call_record){ + .kind = DCP_CALL_RECV, .endpoint = endpoint, + .value = message }); +} + +static void dcp_call_history_push(void *data, struct apple_dcp *dcp, + const struct dcp_method_entry *method, + int context, int offset, int depth) +{ + struct dcp_call_record r = { + .kind = DCP_CALL_PUSH, .name = method->name, .number = context, + }; + + if (dcp != data) + return; + memcpy(r.tag, method->tag, sizeof(r.tag)); + dcp_call_record(dcp, &r); +} + +static void dcp_call_history_callback(void *data, struct apple_dcp *dcp, + int tag, const char *name) +{ + if (dcp == data) + dcp_call_record(dcp, &(struct dcp_call_record){ + .kind = DCP_CALL_CALLBACK, .number = tag, .name = name }); +} + +static void dcp_call_history_submit(void *data, struct apple_dcp *dcp, + u32 swap_id) +{ + if (dcp == data) + dcp_call_record(dcp, &(struct dcp_call_record){ + .kind = DCP_CALL_SWAP_SUBMIT, .value = swap_id }); +} + +static void dcp_call_history_complete(void *data, struct apple_dcp *dcp, + u32 swap_id) +{ + if (dcp == data) + dcp_call_record(dcp, &(struct dcp_call_record){ + .kind = DCP_CALL_SWAP_COMPLETE, .value = swap_id }); +} + +static void dcp_call_history_release(void *data) +{ + struct apple_dcp *dcp = data; + + unregister_trace_iomfb_swap_complete(dcp_call_history_complete, dcp); + unregister_trace_iomfb_swap_submit(dcp_call_history_submit, dcp); + unregister_trace_iomfb_callback(dcp_call_history_callback, dcp); + unregister_trace_iomfb_push(dcp_call_history_push, dcp); + unregister_trace_dcp_recv_msg(dcp_call_history_recv, dcp); + unregister_trace_dcp_send_msg(dcp_call_history_send, dcp); + tracepoint_synchronize_unregister(); + dcp->call_history = NULL; +} + +static int dcp_call_history_register(struct apple_dcp *dcp) +{ + int ret; + + ret = register_trace_dcp_send_msg(dcp_call_history_send, dcp); + if (ret) + return ret; + ret = register_trace_dcp_recv_msg(dcp_call_history_recv, dcp); + if (ret) + goto err_send; + ret = register_trace_iomfb_push(dcp_call_history_push, dcp); + if (ret) + goto err_recv; + ret = register_trace_iomfb_callback(dcp_call_history_callback, dcp); + if (ret) + goto err_push; + ret = register_trace_iomfb_swap_submit(dcp_call_history_submit, dcp); + if (ret) + goto err_callback; + ret = register_trace_iomfb_swap_complete(dcp_call_history_complete, dcp); + if (ret) + goto err_submit; + return 0; + +err_submit: + unregister_trace_iomfb_swap_submit(dcp_call_history_submit, dcp); +err_callback: + unregister_trace_iomfb_callback(dcp_call_history_callback, dcp); +err_push: + unregister_trace_iomfb_push(dcp_call_history_push, dcp); +err_recv: + unregister_trace_dcp_recv_msg(dcp_call_history_recv, dcp); +err_send: + unregister_trace_dcp_send_msg(dcp_call_history_send, dcp); + tracepoint_synchronize_unregister(); + return ret; +} + +static void dcp_call_history_start(struct device *dev) +{ + struct apple_dcp *dcp = dev_get_drvdata(dev); + struct dcp_call_history *h; + int ret; + + /* The tracepoints are built with CONFIG_TRACING (trace.o). */ + if (!IS_ENABLED(CONFIG_TRACING) || !call_history || !dcp || + !dcp_is_t6050(dev)) + return; + h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); + if (!h) + return; + raw_spin_lock_init(&h->lock); + dcp->call_history = h; + ret = dcp_call_history_register(dcp); + if (ret) { + dcp->call_history = NULL; + dev_warn(dev, "call history: tracepoint probes not registered: %d\n", + ret); + return; + } + if (devm_add_action_or_reset(dev, dcp_call_history_release, dcp)) + return; + dev_info(dev, "call history: keeping the last %u AP-link messages\n", + DCP_CALL_HISTORY_LEN); +} + +static void dcp_call_record_print(struct apple_dcp *dcp, + const struct dcp_call_record *r) +{ + u64 sec = r->time; + u32 usec = do_div(sec, NSEC_PER_SEC) / NSEC_PER_USEC; + + switch (r->kind) { + case DCP_CALL_SEND: + case DCP_CALL_RECV: + dev_info(dcp->dev, "call history: [%5llu.%06u] %s ep 0x%02x 0x%016llx\n", + sec, usec, r->kind == DCP_CALL_SEND ? "send" : "recv", + r->endpoint, r->value); + break; + case DCP_CALL_PUSH: + dev_info(dcp->dev, "call history: [%5llu.%06u] call %.4s %s (context %u)\n", + sec, usec, r->tag, r->name, r->number); + break; + case DCP_CALL_CALLBACK: + dev_info(dcp->dev, "call history: [%5llu.%06u] callback D%03u %s\n", + sec, usec, r->number, r->name); + break; + case DCP_CALL_SWAP_SUBMIT: + case DCP_CALL_SWAP_COMPLETE: + dev_info(dcp->dev, "call history: [%5llu.%06u] swap %llu %s\n", + sec, usec, r->value, + r->kind == DCP_CALL_SWAP_SUBMIT ? "submitted" : "completed"); + break; + } +} + +/* Once, at the crash, before the crash handling adds traffic of its own. */ +static void dcp_call_history_freeze(struct apple_dcp *dcp) +{ + struct dcp_call_history *h = dcp->call_history; + unsigned int count, n, i; + unsigned long flags; + + if (!h) + return; + raw_spin_lock_irqsave(&h->lock, flags); + if (h->frozen) { + raw_spin_unlock_irqrestore(&h->lock, flags); + return; + } + h->frozen = true; + count = h->count; + raw_spin_unlock_irqrestore(&h->lock, flags); + + /* Frozen: nothing writes the ring any more. */ + n = min(count, DCP_CALL_HISTORY_LEN); + dev_err(dcp->dev, "call history: the last %u of %u AP-link messages before the crash:\n", + n, count); + for (i = count - n; i != count; i++) + dcp_call_record_print(dcp, &h->rec[i % DCP_CALL_HISTORY_LEN]); +} + static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_size) { struct apple_dcp *dcp = cookie; @@ -1005,6 +1261,9 @@ static void dcp_rtk_crashed(void *cookie, const void *crashlog, size_t crashlog_ if (READ_ONCE(dcp->quiescing)) return; + /* Before the crash handling adds traffic of its own. */ + dcp_call_history_freeze(dcp); + /* * A failed T8122 dcpext: its start never ran (start_done is not set * up) and it has no connector. Its firmware may still report a crash. @@ -5730,6 +5989,8 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) iomfb_v14_7_external_refuse(dcp, "its Type-C display routes did not register"); } + /* Before the bind, which can start the firmware session. */ + dcp_call_history_start(&pdev->dev); ret = component_add(&pdev->dev, &dcp_comp_ops); if (ret && dcp->external_prepared) { dcp_disable_typec_work(dcp, true);