Skip to content
24 changes: 24 additions & 0 deletions Documentation/devicetree/bindings/display/apple,dcp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
88 changes: 88 additions & 0 deletions arch/arm64/boot/dts/apple/t8122-j615.dts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
};
};
};
};
34 changes: 34 additions & 0 deletions drivers/gpu/drm/apple/apple_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions drivers/gpu/drm/apple/dcp-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <linux/ioport.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/xarray.h>
#include <linux/spinlock.h>
#include <linux/mux/consumer.h>
#include <linux/phy/phy.h>
Expand Down Expand Up @@ -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;
Expand Down
129 changes: 129 additions & 0 deletions drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

#include <drm/drm_edid.h>
#include <drm/drm_fb_dma_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_drv.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
Expand Down Expand Up @@ -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);
Expand All @@ -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)
{
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions drivers/gpu/drm/apple/dcp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/apple/iomfb_template.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading