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: diff --git a/arch/arm64/boot/dts/apple/t8122-j615.dts b/arch/arm64/boot/dts/apple/t8122-j615.dts index 5e52cd3046e713..4aab4022c44f64 100644 --- a/arch/arm64/boot/dts/apple/t8122-j615.dts +++ b/arch/arm64/boot/dts/apple/t8122-j615.dts @@ -262,3 +262,91 @@ }; #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,no-shared-display-dart), 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; + apple,no-shared-display-dart; + + 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>; + }; + }; + }; +}; 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 3eac22a57aa069..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 @@ -231,6 +232,18 @@ struct apple_dcp { struct platform_device *piodma; bool piodma_created; struct iommu_domain *iommu_dom; + /* + * 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 a8571b5446f5c4..ce8293df7717a6 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 @@ -3067,12 +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; if (dcp->piodma) { if (dcp->piodma_created) of_platform_device_destroy(&dcp->piodma->dev, NULL); @@ -3084,6 +3108,107 @@ static void dcp_release_piodma_iommu_dev(struct apple_dcp *dcp) dcp->piodma_created = false; } +/* + * 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; + } + 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. 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 (!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; + ssize_t mapped; + + 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; + 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); + } + } +} + +/* @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) { @@ -3918,6 +4043,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"); @@ -4318,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 baff04ee201e72..284259f2804824 100644 --- a/drivers/gpu/drm/apple/dcp.h +++ b/drivers/gpu/drm/apple/dcp.h @@ -72,6 +72,9 @@ 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); +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); 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) diff --git a/drivers/soc/apple/t6030-display-gate.c b/drivers/soc/apple/t6030-display-gate.c index 7ee02087c969b8..b7fd93350575d4 100644 --- a/drivers/soc/apple/t6030-display-gate.c +++ b/drivers/soc/apple/t6030-display-gate.c @@ -1741,6 +1741,26 @@ static int __init gate_t8122_dcpext_setup(char *arg) } early_param("apple_t8122_display.dcpext", gate_t8122_dcpext_setup); +/* + * 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 int gate_t8122_dcpext_disp_dart __initdata = -1; + +static int __init gate_t8122_dcpext_disp_dart_setup(char *arg) +{ + 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); + /* The most display crossbars a T8122 dcpext's routes switch: ATC0's and ATC1's. */ #define GATE_T8122_XBARS 2 @@ -1789,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; @@ -1798,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; @@ -1911,8 +1950,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 && share_dart) ret = gate_display_add_dart(cs, disp_dart); + else if (!ret) + 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); if (ret) { @@ -1921,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]);