From 0a1f90b496a220e1e40d35300fea8d89937d6687 Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 4 Nov 2026 21:06:32 +0000 Subject: [PATCH 1/7] soc: apple: t8122 display gate: dcpext_disp_dart switch apple_t8122_display.dcpext_disp_dart=0 enables the T8122 dcpext without appending its display DART to the display subsystem's iommus. On the J615 the shared domain faults the internal DCP's DART (NO PMD/PTE FOR IOVA at 0x1000a000000..) and crashes the internal DCP about 2 s into boot. --- drivers/soc/apple/t6030-display-gate.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index 7ee02087c969b8..e025f713239512 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -1741,6 +1741,19 @@ static int __init gate_t8122_dcpext_setup(char *arg) } early_param("apple_t8122_display.dcpext", gate_t8122_dcpext_setup); +/* + * Experiment (J615): apple_t8122_display.dcpext_disp_dart=0 enables dcpext + * without appending its display DART to the display subsystem's iommus, to + * tell whether that shared domain is what faults the internal DCP's DART. + */ +static bool gate_t8122_dcpext_disp_dart __initdata = true; + +static int __init gate_t8122_dcpext_disp_dart_setup(char *arg) +{ + return kstrtobool(arg, &gate_t8122_dcpext_disp_dart); +} +early_param("apple_t8122_display.dcpext_disp_dart", gate_t8122_dcpext_disp_dart_setup); + /* The most display crossbars a T8122 dcpext's routes switch: ATC0's and ATC1's. */ #define GATE_T8122_XBARS 2 @@ -1911,8 +1924,11 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) ret = of_changeset_update_prop_string(cs, np, "status", "okay"); if (!ret) ret = gate_ext_routes(cs, np, "okay"); - if (!ret) + if (!ret && gate_t8122_dcpext_disp_dart) ret = gate_display_add_dart(cs, disp_dart); + else if (!ret) + gate_info("%pOF: display DART %pOF not added to the display subsystem (dcpext_disp_dart=0)\n", + np, disp_dart); if (!ret) ret = of_changeset_apply(cs); if (ret) { From ff812974eb1a89cdc1759248beed95692f6edf3b Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 4 Nov 2026 21:06:32 +0000 Subject: [PATCH 2/7] drm: apple: dcp: mirror framebuffers into the T8122 dcpext fbdma domain With the dcpext display DART out of the display subsystem's domain, the external pipe scans framebuffers through a DART stream nothing maps. An optional fbdma child of the dcpext (display DART stream 0, the display subsystem's DVA window) gets its own domain, and each swapped framebuffer is mapped read-only there at its display DVA. Imported (GPU) buffers use the importer's sg table. --- drivers/gpu/drm/apple/dcp-internal.h | 7 +++ drivers/gpu/drm/apple/dcp.c | 87 ++++++++++++++++++++++++++ drivers/gpu/drm/apple/dcp.h | 1 + drivers/gpu/drm/apple/iomfb_template.c | 1 + 4 files changed, 96 insertions(+) diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 3eac22a57aa069..8f3da321619673 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -231,6 +231,13 @@ struct apple_dcp { struct platform_device *piodma; bool piodma_created; struct iommu_domain *iommu_dom; + /* + * Experiment (J615 dcpext): a child device on the display DART stream + * the external pipe scans framebuffers through. Framebuffers are + * mirrored into its domain at the display subsystem's addresses. + */ + struct platform_device *fbdma; + struct iommu_domain *fb_dom; /* which of the nine cumulative A031 notify-client states to send next */ unsigned int a031_step; struct apple_rtkit *rtk; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index a8571b5446f5c4..587917a3af8c83 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -3073,6 +3074,10 @@ static void dcp_release_piodma_iommu_dev(struct apple_dcp *dcp) WRITE_ONCE(dcp->quiescing, true); return; } + if (dcp->fbdma) + of_platform_device_destroy(&dcp->fbdma->dev, NULL); + dcp->fbdma = NULL; + dcp->fb_dom = NULL; if (dcp->piodma) { if (dcp->piodma_created) of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -3084,6 +3089,86 @@ static void dcp_release_piodma_iommu_dev(struct apple_dcp *dcp) dcp->piodma_created = false; } +/* + * Experiment (J615 dcpext): the external pipe reads framebuffers through its + * own display DART, not the internal one the display subsystem's domain + * covers. An "fbdma" child, on that DART's stream 0 with the display + * subsystem's DVA window, gets a domain the framebuffers are mirrored into. + * Without the node nothing changes. + */ +static void dcp_create_fbdma_dev(struct apple_dcp *dcp) +{ + struct device_node *node; + struct iommu_domain *dom; + int ret; + + if (!dcp->ext_failsafe || dcp->fbdma) + return; + node = of_get_child_by_name(dcp->dev->of_node, "fbdma"); + if (!node) + return; + dcp->fbdma = of_platform_device_create(node, NULL, dcp->dev); + if (!dcp->fbdma) { + dev_warn(dcp->dev, "fbdma: could not create %pOF\n", node); + goto out; + } + ret = dma_set_mask_and_coherent(&dcp->fbdma->dev, DMA_BIT_MASK(42)); + if (!ret) + ret = of_dma_configure(&dcp->fbdma->dev, node, true); + dom = ret ? NULL : iommu_get_domain_for_dev(&dcp->fbdma->dev); + if (IS_ERR_OR_NULL(dom)) { + dev_warn(dcp->dev, "fbdma: no IOMMU domain (%d)\n", ret); + of_platform_device_destroy(&dcp->fbdma->dev, NULL); + dcp->fbdma = NULL; + goto out; + } + dcp->fb_dom = dom; + dev_info(dcp->dev, "fbdma: framebuffers mirrored through %pOF\n", node); +out: + of_node_put(node); +} + +/* Map @fb's GEM objects into the fbdma domain at their display DVAs. */ +void dcp_fb_mirror(struct apple_dcp *dcp, struct drm_framebuffer *fb) +{ + unsigned int i; + + if (!dcp->fb_dom || !fb) + return; + for (i = 0; i < fb->format->num_planes; i++) { + struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, i); + struct sg_table *sgt; + size_t size; + ssize_t mapped; + phys_addr_t phys; + + if (!obj) + continue; + /* Imported (GPU) buffers carry their pages in obj->sgt. */ + sgt = obj->sgt ? obj->sgt : drm_gem_dma_get_sg_table(obj); + if (IS_ERR_OR_NULL(sgt)) + continue; + size = ALIGN(obj->base.size, SZ_16K); + phys = iommu_iova_to_phys(dcp->fb_dom, obj->dma_addr); + if (phys != sg_phys(sgt->sgl)) { + /* A DVA the display subsystem reused for another buffer. */ + if (phys) + iommu_unmap(dcp->fb_dom, obj->dma_addr, size); + mapped = iommu_map_sgtable(dcp->fb_dom, obj->dma_addr, sgt, + IOMMU_READ); + dev_info_ratelimited(dcp->dev, + "fbdma: %pad (%zu bytes, %s, pa %pa) mapped %zd\n", + &obj->dma_addr, obj->base.size, + obj->sgt ? "imported" : "native", &(phys_addr_t){ sg_phys(sgt->sgl) }, + mapped); + } + if (sgt != obj->sgt) { + sg_free_table(sgt); + kfree(sgt); + } + } +} + void dcp_retain_framebuffer(struct platform_device *pdev, struct dcp_fb_reference *entry) { @@ -3918,6 +4003,8 @@ static int dcp_comp_bind_pipe(struct device *dev, struct device *main, void *dat } ret = dcp_create_piodma_iommu_dev(dcp); + if (!ret) + dcp_create_fbdma_dev(dcp); if (ret) return dev_err_probe(dev, ret, "Failed to create PIODMA iommu child device\n"); diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index baff04ee201e72..92600177244a2f 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -72,6 +72,7 @@ void dcp_retain_framebuffer(struct platform_device *pdev, struct dcp_fb_reference *entry); void dcp_arm_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); void dcp_unarm_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); +void dcp_fb_mirror(struct apple_dcp *dcp, struct drm_framebuffer *fb); void dcp_release_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); void dcp_release_all_retained_framebuffers(struct apple_dcp *dcp); bool dcp_is_initialized(struct platform_device *pdev); diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c index 78162bdb805dba..c879b3b0a8db3d 100644 --- a/drivers/gpu/drm/apple/iomfb_template.c +++ b/drivers/gpu/drm/apple/iomfb_template.c @@ -2502,6 +2502,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru if (dcp->notch_height > 0) req->swap.dst_rect[l].y += dcp->notch_height; + dcp_fb_mirror(dcp, new_state->fb); req->surf_iova[l] = apple_state->iova; req->surf[l].base = apple_state->surf; #if DCP_FW_VER >= DCP_FW_VERSION(26, 0, 0) From 0c97623e5a280932ae5ee38b5ab071b1d163a2d0 Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 4 Nov 2026 21:07:10 +0000 Subject: [PATCH 3/7] arm64: dts: apple: t8122-j615: external display over USB-C (dcpext) The J613 dcpext routes, carried to the J615 (same T8122 Type-C wiring: usb-pd@c Left-back/ATC0, usb-pd@a Left-front/ATC1), plus an fbdma child on dispext0_dart stream 0 for the framebuffer mirror. --- arch/arm64/boot/dts/apple/t8122-j615.dts | 87 ++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8122-j615.dts b/arch/arm64/boot/dts/apple/t8122-j615.dts index 5e52cd3046e713..cf92e7f6349508 100644 --- a/arch/arm64/boot/dts/apple/t8122-j615.dts +++ b/arch/arm64/boot/dts/apple/t8122-j615.dts @@ -262,3 +262,90 @@ }; #include "hwmon-laptop.dtsi" + +/* + * External display on both USB-C ports, wired as on the J613 (see + * t8122-j613.dts): usb-pd@c is USB-C Left-back (typec0, ATC0) and usb-pd@a is + * USB-C Left-front (typec1, ATC1). Everything stays disabled until the boot + * loader marks the dcpext firmware reserved and the display gate enables it. + * + * fbdma is the display DART stream the dcpext scans framebuffers through, + * with the display subsystem's DVA window: on the J615 the gate must not add + * dispext0_dart to the display subsystem (apple_t8122_display.dcpext_disp_dart=0), + * so appledrm mirrors framebuffers there instead. + */ +/ { + aliases { + dcpext = &dcpext; + }; +}; + +&dcpext { + apple,connector-type = "USB-C"; + phys = <&atcphy0 PHY_TYPE_DP>, <&atcphy1 PHY_TYPE_DP>; + phy-names = "typec0", "typec1"; + mux-controls = <&atcphy0_xbar 0>, <&atcphy1_xbar 0>; + mux-control-names = "typec0", "typec1"; + apple,typec-mux-indices = <2 2>; + apple,typec-dptx-phys = <0 1>; + /* RTKit quirks of AsahiLinux/linux#607 (syslog/ioreport acks, kdebug). */ + apple,sync-syslog-ack; + + fbdma { + iommus = <&dispext0_dart 0>; + apple,dma-range = /bits/ 64 <0x10000000000 0x1000000000>; + }; + + typec-routes { + #address-cells = <1>; + #size-cells = <0>; + + route@0 { + reg = <0>; + mode-switch; + status = "disabled"; + + port { + dcpext_typec0: endpoint { + remote-endpoint = <&typec0_dcpext>; + }; + }; + }; + + route@1 { + reg = <1>; + mode-switch; + status = "disabled"; + + port { + dcpext_typec1: endpoint { + remote-endpoint = <&typec1_dcpext>; + }; + }; + }; + }; +}; + +&typec0 { + ports { + port@3 { + reg = <3>; + + typec0_dcpext: endpoint { + remote-endpoint = <&dcpext_typec0>; + }; + }; + }; +}; + +&typec1 { + ports { + port@3 { + reg = <3>; + + typec1_dcpext: endpoint { + remote-endpoint = <&dcpext_typec1>; + }; + }; + }; +}; From b8006e22a79affdd19bad8d6fd70f9fb69f443aa Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 11 Nov 2026 17:09:15 +0000 Subject: [PATCH 4/7] dt-bindings: display: apple,dcp: fbdma child and apple,no-shared-display-dart Describe the fbdma child an external coprocessor scans mirrored framebuffers through, and the marker that keeps its display DART out of the display subsystem's IOMMU domain. --- .../bindings/display/apple,dcp.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Documentation/devicetree/bindings/display/apple,dcp.yaml b/Documentation/devicetree/bindings/display/apple,dcp.yaml index a7c74d948e6e9c..f8fd5696b93310 100644 --- a/Documentation/devicetree/bindings/display/apple,dcp.yaml +++ b/Documentation/devicetree/bindings/display/apple,dcp.yaml @@ -256,6 +256,30 @@ properties: - iommus additionalProperties: false + apple,no-shared-display-dart: + type: boolean + description: + The external coprocessor's display DART must stay out of the display + subsystem's IOMMU domain. Framebuffers are mirrored through the fbdma + child instead, which must be present. + + fbdma: + type: object + description: + The display DART stream an external coprocessor scans framebuffers + through, with the display subsystem's DVA window. + properties: + iommus: + maxItems: 1 + apple,dma-range: + $ref: /schemas/types.yaml#/definitions/uint64-array + minItems: 2 + maxItems: 2 + required: + - iommus + - apple,dma-range + additionalProperties: false + panel: type: object properties: From 44e4b39a31133d2ff94e10e9d12f311ef3eb50a9 Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 11 Nov 2026 17:09:15 +0000 Subject: [PATCH 5/7] drm: apple: dcp: unmap fbdma framebuffer mirrors when their buffer is freed dcp_fb_mirror() mapped each framebuffer read-only into the fbdma domain and never unmapped it, so freed pages stayed readable by the external DCP until their DVA was reused. Track each mirrored GEM object in an xarray and remove its mapping from the GEM free hook, before the pages and the DVA are released, and drop all mirrors when the fbdma device goes away. The xarray also answers "already mapped" before any sg_table is built, so a flush of a mapped native buffer no longer allocates. Only a failed mapping is logged. --- drivers/gpu/drm/apple/apple_drv.c | 34 +++++++++++ drivers/gpu/drm/apple/dcp-internal.h | 12 +++- drivers/gpu/drm/apple/dcp.c | 84 +++++++++++++++++++++------- drivers/gpu/drm/apple/dcp.h | 2 + 4 files changed, 108 insertions(+), 24 deletions(-) diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c index 2324a8acd4c72a..a70fc3a2c38e9f 100644 --- a/drivers/gpu/drm/apple/apple_drv.c +++ b/drivers/gpu/drm/apple/apple_drv.c @@ -148,6 +148,39 @@ static int apple_drm_gem_dumb_create(struct drm_file *file_priv, return drm_gem_dma_dumb_create_internal(file_priv, drm, args); } +/* + * A T8122 dcpext mirrors framebuffers into its own display DART domain. + * Remove a buffer's mirror before its pages and its DVA are released. + */ +static void apple_drm_gem_free(struct drm_gem_object *gem) +{ + struct drm_crtc *crtc; + + drm_for_each_crtc(crtc, gem->dev) + dcp_fb_unmirror(to_apple_crtc(crtc)->dcp, to_drm_gem_dma_obj(gem)); + drm_gem_dma_object_free(gem); +} + +static const struct drm_gem_object_funcs apple_drm_gem_funcs = { + .free = apple_drm_gem_free, + .print_info = drm_gem_dma_object_print_info, + .get_sg_table = drm_gem_dma_object_get_sg_table, + .vmap = drm_gem_dma_object_vmap, + .mmap = drm_gem_dma_object_mmap, + .vm_ops = &drm_gem_dma_vm_ops, +}; + +static struct drm_gem_object *apple_drm_gem_create_object(struct drm_device *drm, + size_t size) +{ + struct drm_gem_dma_object *obj = kzalloc_obj(*obj); + + if (!obj) + return ERR_PTR(-ENOMEM); + obj->base.funcs = &apple_drm_gem_funcs; + return &obj->base; +} + #ifdef CONFIG_DRM_FBDEV_EMULATION static int apple_drm_fbdev_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) @@ -190,6 +223,7 @@ static const struct drm_driver apple_drm_driver = { DRM_FBDEV_DMA_DRIVER_OPS, #endif .postclose = apple_drm_postclose, + .gem_create_object = apple_drm_gem_create_object, .name = DRIVER_NAME, .desc = DRIVER_DESC, .major = 1, diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h index 8f3da321619673..a6784101d4a23a 100644 --- a/drivers/gpu/drm/apple/dcp-internal.h +++ b/drivers/gpu/drm/apple/dcp-internal.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -232,12 +233,17 @@ struct apple_dcp { bool piodma_created; struct iommu_domain *iommu_dom; /* - * Experiment (J615 dcpext): a child device on the display DART stream - * the external pipe scans framebuffers through. Framebuffers are - * mirrored into its domain at the display subsystem's addresses. + * J615 dcpext: a child device on the display DART stream the external + * pipe scans framebuffers through. Framebuffers are mirrored into its + * domain at the display subsystem's addresses. fb_mirrors holds the + * mapped length of every mirrored GEM object, indexed by the object's + * address, until the object is freed; fb_mirror_lock protects it and + * fb_dom. */ struct platform_device *fbdma; struct iommu_domain *fb_dom; + struct xarray fb_mirrors; + struct mutex fb_mirror_lock; /* which of the nine cumulative A031 notify-client states to send next */ unsigned int a031_step; struct apple_rtkit *rtk; diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c index 587917a3af8c83..ce8293df7717a6 100644 --- a/drivers/gpu/drm/apple/dcp.c +++ b/drivers/gpu/drm/apple/dcp.c @@ -3068,16 +3068,35 @@ static void dcp_work_backlight_fallback(struct work_struct *work) dev_warn(dcp->dev, "backlight registration failed: %d\n", ret); } +/* Drop every framebuffer mirror before the fbdma domain goes away. */ +static void dcp_fb_unmirror_all(struct apple_dcp *dcp) +{ + unsigned long index; + void *entry; + + guard(mutex)(&dcp->fb_mirror_lock); + if (dcp->fb_dom) { + xa_for_each(&dcp->fb_mirrors, index, entry) { + /* An object stays in fb_mirrors only while it is alive. */ + struct drm_gem_dma_object *obj = (void *)index; + + iommu_unmap(dcp->fb_dom, obj->dma_addr, xa_to_value(entry)); + } + } + xa_destroy(&dcp->fb_mirrors); + dcp->fb_dom = NULL; +} + static void dcp_release_piodma_iommu_dev(struct apple_dcp *dcp) { if (dcp->retain_dma) { WRITE_ONCE(dcp->quiescing, true); return; } + dcp_fb_unmirror_all(dcp); if (dcp->fbdma) of_platform_device_destroy(&dcp->fbdma->dev, NULL); dcp->fbdma = NULL; - dcp->fb_dom = NULL; if (dcp->piodma) { if (dcp->piodma_created) of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -3090,7 +3109,7 @@ static void dcp_release_piodma_iommu_dev(struct apple_dcp *dcp) } /* - * Experiment (J615 dcpext): the external pipe reads framebuffers through its + * J615 dcpext: the external pipe reads framebuffers through its * own display DART, not the internal one the display subsystem's domain * covers. An "fbdma" child, on that DART's stream 0 with the display * subsystem's DVA window, gets a domain the framebuffers are mirrored into. @@ -3122,46 +3141,52 @@ static void dcp_create_fbdma_dev(struct apple_dcp *dcp) dcp->fbdma = NULL; goto out; } - dcp->fb_dom = dom; + scoped_guard(mutex, &dcp->fb_mirror_lock) + dcp->fb_dom = dom; dev_info(dcp->dev, "fbdma: framebuffers mirrored through %pOF\n", node); out: of_node_put(node); } -/* Map @fb's GEM objects into the fbdma domain at their display DVAs. */ +/* + * Map @fb's GEM objects into the fbdma domain at their display DVAs. A + * mapping lasts as long as its object and its DVA do: dcp_fb_unmirror() + * removes it when the object is freed. + */ void dcp_fb_mirror(struct apple_dcp *dcp, struct drm_framebuffer *fb) { unsigned int i; - if (!dcp->fb_dom || !fb) + if (!fb || !READ_ONCE(dcp->fb_dom)) + return; + guard(mutex)(&dcp->fb_mirror_lock); + if (!dcp->fb_dom) return; for (i = 0; i < fb->format->num_planes; i++) { struct drm_gem_dma_object *obj = drm_fb_dma_get_gem_obj(fb, i); + unsigned long index = (unsigned long)obj; struct sg_table *sgt; - size_t size; ssize_t mapped; - phys_addr_t phys; - if (!obj) + if (!obj || xa_load(&dcp->fb_mirrors, index)) continue; /* Imported (GPU) buffers carry their pages in obj->sgt. */ sgt = obj->sgt ? obj->sgt : drm_gem_dma_get_sg_table(obj); if (IS_ERR_OR_NULL(sgt)) continue; - size = ALIGN(obj->base.size, SZ_16K); - phys = iommu_iova_to_phys(dcp->fb_dom, obj->dma_addr); - if (phys != sg_phys(sgt->sgl)) { - /* A DVA the display subsystem reused for another buffer. */ - if (phys) - iommu_unmap(dcp->fb_dom, obj->dma_addr, size); - mapped = iommu_map_sgtable(dcp->fb_dom, obj->dma_addr, sgt, - IOMMU_READ); - dev_info_ratelimited(dcp->dev, - "fbdma: %pad (%zu bytes, %s, pa %pa) mapped %zd\n", - &obj->dma_addr, obj->base.size, - obj->sgt ? "imported" : "native", &(phys_addr_t){ sg_phys(sgt->sgl) }, - mapped); + mapped = iommu_map_sgtable(dcp->fb_dom, obj->dma_addr, sgt, + IOMMU_READ); + if (mapped > 0 && + xa_err(xa_store(&dcp->fb_mirrors, index, xa_mk_value(mapped), + GFP_KERNEL))) { + iommu_unmap(dcp->fb_dom, obj->dma_addr, mapped); + mapped = -ENOMEM; } + if (mapped <= 0) + dev_warn_ratelimited(dcp->dev, + "fbdma: %pad (%zu bytes, %s) not mirrored: %zd\n", + &obj->dma_addr, obj->base.size, + obj->sgt ? "imported" : "native", mapped); if (sgt != obj->sgt) { sg_free_table(sgt); kfree(sgt); @@ -3169,6 +3194,21 @@ void dcp_fb_mirror(struct apple_dcp *dcp, struct drm_framebuffer *fb) } } +/* @obj is being freed: the external pipe must not read its pages any more. */ +void dcp_fb_unmirror(struct platform_device *pdev, struct drm_gem_dma_object *obj) +{ + struct apple_dcp *dcp = platform_get_drvdata(pdev); + void *entry; + + if (!dcp) + return; + guard(mutex)(&dcp->fb_mirror_lock); + entry = xa_erase(&dcp->fb_mirrors, (unsigned long)obj); + if (entry && dcp->fb_dom) + iommu_unmap(dcp->fb_dom, obj->dma_addr, xa_to_value(entry)); +} +EXPORT_SYMBOL_GPL(dcp_fb_unmirror); + void dcp_retain_framebuffer(struct platform_device *pdev, struct dcp_fb_reference *entry) { @@ -4405,6 +4445,8 @@ static int dcp_platform_probe_pipe(struct platform_device *pdev) INIT_LIST_HEAD(&dcp->swapped_out_fbs); mutex_init(&dcp->swapped_out_fbs_lock); + xa_init(&dcp->fb_mirrors); + mutex_init(&dcp->fb_mirror_lock); ret = dcp_backlight_init(dcp, dev); if (ret) return ret; diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h index 92600177244a2f..284259f2804824 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -73,6 +73,8 @@ void dcp_retain_framebuffer(struct platform_device *pdev, void dcp_arm_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); void dcp_unarm_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); void dcp_fb_mirror(struct apple_dcp *dcp, struct drm_framebuffer *fb); +struct drm_gem_dma_object; +void dcp_fb_unmirror(struct platform_device *pdev, struct drm_gem_dma_object *obj); void dcp_release_retained_framebuffers(struct apple_dcp *dcp, u32 swap_id); void dcp_release_all_retained_framebuffers(struct apple_dcp *dcp); bool dcp_is_initialized(struct platform_device *pdev); From f2b4ab8fa10fdd39f582763b58aff5bcc6ac84c1 Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 11 Nov 2026 17:09:15 +0000 Subject: [PATCH 6/7] soc: apple: t8122 display gate: board default for the dcpext display DART apple,no-shared-display-dart on a dcpext node that has an fbdma child keeps its display DART out of the display subsystem's domain, so a J615 needs no kernel argument. Without the fbdma child the gate warns and shares. apple_t8122_display.dcpext_disp_dart becomes tri-state: unset lets the board decide, =0 and =1 override. --- drivers/soc/apple/t6030-display-gate.c | 44 ++++++++++++++++++++------ 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index e025f713239512..b7fd93350575d4 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -1742,15 +1742,22 @@ static int __init gate_t8122_dcpext_setup(char *arg) early_param("apple_t8122_display.dcpext", gate_t8122_dcpext_setup); /* - * Experiment (J615): apple_t8122_display.dcpext_disp_dart=0 enables dcpext - * without appending its display DART to the display subsystem's iommus, to - * tell whether that shared domain is what faults the internal DCP's DART. + * Whether the dcpext's display DART joins the display subsystem's iommus. + * On the J615 that shared domain faults the internal DCP's DART, so its + * dcpext node carries apple,no-shared-display-dart and an fbdma child the + * display driver mirrors framebuffers through instead. The board decides + * unless apple_t8122_display.dcpext_disp_dart=0 or =1 overrides it. */ -static bool gate_t8122_dcpext_disp_dart __initdata = true; +static int gate_t8122_dcpext_disp_dart __initdata = -1; static int __init gate_t8122_dcpext_disp_dart_setup(char *arg) { - return kstrtobool(arg, &gate_t8122_dcpext_disp_dart); + bool share; + int ret = kstrtobool(arg, &share); + + if (!ret) + gate_t8122_dcpext_disp_dart = share; + return ret; } early_param("apple_t8122_display.dcpext_disp_dart", gate_t8122_dcpext_disp_dart_setup); @@ -1802,6 +1809,24 @@ static bool __init gate_node_in(struct device_node *const *nodes, int n, return false; } +/* Does @np's display DART go into the display subsystem's domain? */ +static bool __init gate_t8122_dcpext_shares_dart(struct device_node *np) +{ + struct device_node *fbdma; + + if (gate_t8122_dcpext_disp_dart >= 0) + return gate_t8122_dcpext_disp_dart; + if (!of_property_read_bool(np, "apple,no-shared-display-dart")) + return true; + fbdma = of_get_child_by_name(np, "fbdma"); + of_node_put(fbdma); + if (fbdma) + return false; + gate_warn("%pOF: apple,no-shared-display-dart without an fbdma child; display DART shared\n", + np); + return true; +} + static void __init gate_t8122_dcpext_one(struct device_node *np) { struct device_node *dart = NULL, *mbox = NULL, *piodma = NULL; @@ -1811,6 +1836,7 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) struct of_phandle_args args; struct of_changeset *cs; const char *label; + bool share_dart = gate_t8122_dcpext_shares_dart(np); int ret, i, nxbar = 0; u32 ready; @@ -1924,10 +1950,10 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) ret = of_changeset_update_prop_string(cs, np, "status", "okay"); if (!ret) ret = gate_ext_routes(cs, np, "okay"); - if (!ret && gate_t8122_dcpext_disp_dart) + if (!ret && share_dart) ret = gate_display_add_dart(cs, disp_dart); else if (!ret) - gate_info("%pOF: display DART %pOF not added to the display subsystem (dcpext_disp_dart=0)\n", + gate_info("%pOF: display DART %pOF kept out of the display subsystem; framebuffers are mirrored\n", np, disp_dart); if (!ret) ret = of_changeset_apply(cs); @@ -1937,8 +1963,8 @@ static void __init gate_t8122_dcpext_one(struct device_node *np) gate_ext_refuse(np, "the gate could not enable it"); goto put; } - gate_info("enabled %pOF with its Type-C routes, DARTs, mailbox, %d display crossbar(s) with ATC common power kept on, display DART %pOF in the display subsystem, CPU power floor and PMP DISPEXT request; started by the display driver\n", - np, nxbar, disp_dart); + gate_info("enabled %pOF with its Type-C routes, DARTs, mailbox, %d display crossbar(s) with ATC common power kept on, display DART %pOF %s the display subsystem, CPU power floor and PMP DISPEXT request; started by the display driver\n", + np, nxbar, disp_dart, share_dart ? "in" : "kept out of"); put: for (i = 0; i < GATE_T8122_XBARS; i++) { of_node_put(common[i]); From f592f134e6980aded83f1a5c95c1c688df7af233 Mon Sep 17 00:00:00 2001 From: Eric Juta Date: Wed, 11 Nov 2026 17:09:15 +0000 Subject: [PATCH 7/7] arm64: dts: apple: t8122-j615: keep the dcpext display DART unshared by default --- arch/arm64/boot/dts/apple/t8122-j615.dts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm64/boot/dts/apple/t8122-j615.dts b/arch/arm64/boot/dts/apple/t8122-j615.dts index cf92e7f6349508..4aab4022c44f64 100644 --- a/arch/arm64/boot/dts/apple/t8122-j615.dts +++ b/arch/arm64/boot/dts/apple/t8122-j615.dts @@ -271,8 +271,8 @@ * * fbdma is the display DART stream the dcpext scans framebuffers through, * with the display subsystem's DVA window: on the J615 the gate must not add - * dispext0_dart to the display subsystem (apple_t8122_display.dcpext_disp_dart=0), - * so appledrm mirrors framebuffers there instead. + * dispext0_dart to the display subsystem (apple,no-shared-display-dart), so + * appledrm mirrors framebuffers there instead. */ / { aliases { @@ -290,6 +290,7 @@ apple,typec-dptx-phys = <0 1>; /* RTKit quirks of AsahiLinux/linux#607 (syslog/ioreport acks, kdebug). */ apple,sync-syslog-ack; + apple,no-shared-display-dart; fbdma { iommus = <&dispext0_dart 0>;