Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/gpu/drm/apple/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += dcp_external_ready_test.o
appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += dcp_external_announce_test.o
appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += dcp_external_relink_test.o
appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += dcp_dropped_swap_test.o
appledrm-$(CONFIG_DRM_APPLE_KUNIT_TEST) += dcp_panel_hold_test.o
appledrm-y += epic/dpavservep.o
appledrm-y += plane.o

Expand Down
7 changes: 7 additions & 0 deletions drivers/gpu/drm/apple/dcp-internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,12 @@ struct apple_dcp {
unsigned int queued;
bool stopped;
bool presentation_paused;
/*
* The 14.7 panel's display controller is off, or its A472 off
* is queued: presents are held until it is back on. Separate
* from presentation_paused, which system sleep also releases.
*/
bool panel_held;
bool backlight_queued;
atomic_t opaque_x_state;
struct iomfb_scanout_h17p *scanout;
Expand Down Expand Up @@ -684,6 +690,7 @@ void iomfb_queue_stop(struct apple_dcp *dcp);
bool iomfb_queue_drain(struct apple_dcp *dcp, unsigned long timeout);
int iomfb_queue_settle(struct apple_dcp *dcp, unsigned long timeout);
void iomfb_pause_presentation(struct apple_dcp *dcp, bool pause);
void iomfb_hold_panel_presents(struct apple_dcp *dcp, bool hold);
int iomfb_external_power_h17p(struct apple_dcp *dcp, bool on);
int iomfb_queue(struct apple_dcp *dcp, struct iomfb_transaction *transaction);
void iomfb_queue_crc_h17p(struct apple_dcp *dcp, u32 swap_id);
Expand Down
14 changes: 14 additions & 0 deletions drivers/gpu/drm/apple/dcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3742,9 +3742,15 @@ static void dcp_panel_hw_off(struct apple_dcp *dcp)
dev_warn(dcp->dev, "blank still pending; display controller left on\n");
return;
}
/*
* Nothing may present to the stopped controller, e.g. the backlight
* core's level at system sleep, or a brightness retry.
*/
iomfb_hold_panel_presents(dcp, true);
ret = iomfb_v14_7_panel_power(dcp, false);
if (ret) {
dev_err(dcp->dev, "display controller off failed: %d\n", ret);
iomfb_hold_panel_presents(dcp, false);
return;
}
dcp->panel_hw_off = true;
Expand All @@ -3759,10 +3765,12 @@ static void dcp_panel_hw_on(struct apple_dcp *dcp)
return;
ret = iomfb_v14_7_panel_power(dcp, true);
if (ret) {
/* Presents stay held; the next power-on tries again. */
dev_err(dcp->dev, "display controller on failed: %d\n", ret);
return;
}
dcp->panel_hw_off = false;
iomfb_hold_panel_presents(dcp, false);
dev_dbg(dcp->dev, "display controller on\n");
}

Expand Down Expand Up @@ -5340,6 +5348,12 @@ static void dcp_drain_for_sleep(struct apple_dcp *dcp)
*/
if (READ_ONCE(dcp->crashed) || READ_ONCE(dcp->iomfb.stopped))
return;
/*
* Nor does a panel whose display controller is off: the
* backlight restore at power-on presents the pending level.
*/
if (dcp->panel_hw_off)
return;
if (!dcp_backlight_active(dcp) || !dcp_backlight_pending(dcp))
return;
}
Expand Down
181 changes: 181 additions & 0 deletions drivers/gpu/drm/apple/dcp_panel_hold_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-only OR MIT

#include <kunit/test.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>

#include "dcp.h"
#include "dcp-internal.h"

/*
* The 14.7 panel's display controller off (A472) goes through the IOMFB
* queue, and a present that reaches the stopped controller is accepted but
* never completed. Lid-close sleep captured it: a present queued behind the
* A472 off held the queue, so the A472 on at resume timed out and the queue
* timeout declared the coprocessor crashed. These cases check admission
* through the real queue; each transaction completes at start, as one the
* firmware answers at once.
*/

struct hold_transaction {
struct iomfb_transaction transaction;
unsigned int *order;
unsigned int started;
};

struct hold_test {
struct apple_dcp dcp;
struct hold_transaction t[4];
unsigned int order;
};

static void hold_test_noop(struct work_struct *work)
{
}

static void hold_test_start(struct apple_dcp *dcp, struct iomfb_transaction *transaction)
{
struct hold_transaction *t = container_of(transaction, struct hold_transaction,
transaction);

t->started = ++*t->order;
}

static void hold_test_release(struct iomfb_transaction *transaction)
{
}

static void hold_test_exit(void *data)
{
struct hold_test *h = data;

iomfb_queue_stop(&h->dcp);
}

static struct hold_test *hold_test_init(struct kunit *test)
{
struct hold_test *h = kunit_kzalloc(test, sizeof(*h), GFP_KERNEL);
unsigned int i;

KUNIT_ASSERT_NOT_NULL(test, h);
h->dcp.fw_compat = DCP_FIRMWARE_V_14_7;
iomfb_queue_init(&h->dcp);
/* Each completion asks the backlight policy for a pending level. */
spin_lock_init(&h->dcp.backlight.lock);
INIT_WORK(&h->dcp.vblank_wq, hold_test_noop);
for (i = 0; i < ARRAY_SIZE(h->t); i++) {
h->t[i].order = &h->order;
h->t[i].transaction.start = hold_test_start;
h->t[i].transaction.release = hold_test_release;
}
KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, hold_test_exit, h), 0);
return h;
}

static void hold_test_present(struct hold_transaction *t, bool brightness_only)
{
t->transaction.presentation = true;
t->transaction.brightness_only = brightness_only;
}

/* Held: presents are refused, a command such as A472 still goes out. */
static void panel_hold_refuses_presents_test(struct kunit *test)
{
struct hold_test *h = hold_test_init(test);
struct apple_dcp *dcp = &h->dcp;

if (!iomfb_is_v14_7(dcp))
kunit_skip(test, "needs CONFIG_DRM_APPLE_DCP_T6030");

iomfb_hold_panel_presents(dcp, true);
hold_test_present(&h->t[0], false);
hold_test_present(&h->t[1], true);
KUNIT_EXPECT_EQ(test, iomfb_queue(dcp, &h->t[0].transaction), -EAGAIN);
KUNIT_EXPECT_EQ(test, iomfb_queue(dcp, &h->t[1].transaction), -EAGAIN);
/* A refused brightness present leaves no queued level behind. */
KUNIT_EXPECT_FALSE(test, dcp->iomfb.backlight_queued);

KUNIT_ASSERT_EQ(test, iomfb_queue(dcp, &h->t[2].transaction), 0);
KUNIT_EXPECT_EQ(test, iomfb_queue_settle(dcp, msecs_to_jiffies(1000)), 0);
KUNIT_EXPECT_EQ(test, h->t[2].started, 1U);
KUNIT_EXPECT_EQ(test, h->t[0].started, 0U);
KUNIT_EXPECT_EQ(test, h->t[1].started, 0U);

iomfb_hold_panel_presents(dcp, false);
KUNIT_ASSERT_EQ(test, iomfb_queue(dcp, &h->t[0].transaction), 0);
KUNIT_EXPECT_EQ(test, iomfb_queue_settle(dcp, msecs_to_jiffies(1000)), 0);
KUNIT_EXPECT_EQ(test, h->t[0].started, 2U);
KUNIT_EXPECT_FALSE(test, READ_ONCE(dcp->crashed));
}

/*
* A present admitted before the hold still goes out, ahead of the A472 off
* queued after it; one admitted after the hold does not. A pending present
* keeps the queue from starting anything until the test lets it go.
*/
static void panel_hold_keeps_order_test(struct kunit *test)
{
struct hold_test *h = hold_test_init(test);
struct apple_dcp *dcp = &h->dcp;

if (!iomfb_is_v14_7(dcp))
kunit_skip(test, "needs CONFIG_DRM_APPLE_DCP_T6030");

scoped_guard(mutex, &dcp->iomfb.lock)
dcp->present_state_h17p.pending = true;
hold_test_present(&h->t[0], false);
KUNIT_ASSERT_EQ(test, iomfb_queue(dcp, &h->t[0].transaction), 0);

iomfb_hold_panel_presents(dcp, true);
/* The A472 off. */
KUNIT_ASSERT_EQ(test, iomfb_queue(dcp, &h->t[1].transaction), 0);
hold_test_present(&h->t[2], true);
KUNIT_EXPECT_EQ(test, iomfb_queue(dcp, &h->t[2].transaction), -EAGAIN);

scoped_guard(mutex, &dcp->iomfb.lock)
dcp->present_state_h17p.pending = false;
schedule_work(&dcp->iomfb.work);
KUNIT_EXPECT_EQ(test, iomfb_queue_settle(dcp, msecs_to_jiffies(1000)), 0);
KUNIT_EXPECT_EQ(test, h->t[0].started, 1U);
KUNIT_EXPECT_EQ(test, h->t[1].started, 2U);
KUNIT_EXPECT_EQ(test, h->t[2].started, 0U);
}

/*
* System sleep pauses and resumes presentation around the mailbox hold. The
* resume must not release presents for a controller that is still off: the
* backlight core restores its level before DRM powers the panel on.
*/
static void panel_hold_outlasts_pause_test(struct kunit *test)
{
struct hold_test *h = hold_test_init(test);
struct apple_dcp *dcp = &h->dcp;

if (!iomfb_is_v14_7(dcp))
kunit_skip(test, "needs CONFIG_DRM_APPLE_DCP_T6030");

iomfb_hold_panel_presents(dcp, true);
iomfb_pause_presentation(dcp, true);
iomfb_pause_presentation(dcp, false);
hold_test_present(&h->t[0], true);
KUNIT_EXPECT_EQ(test, iomfb_queue(dcp, &h->t[0].transaction), -EAGAIN);

iomfb_hold_panel_presents(dcp, false);
KUNIT_EXPECT_EQ(test, iomfb_queue(dcp, &h->t[0].transaction), 0);
KUNIT_EXPECT_EQ(test, iomfb_queue_settle(dcp, msecs_to_jiffies(1000)), 0);
KUNIT_EXPECT_EQ(test, h->t[0].started, 1U);
}

static struct kunit_case panel_hold_cases[] = {
KUNIT_CASE(panel_hold_refuses_presents_test),
KUNIT_CASE(panel_hold_keeps_order_test),
KUNIT_CASE(panel_hold_outlasts_pause_test),
{}
};

static struct kunit_suite panel_hold_suite = {
.name = "apple-dcp-panel-hold",
.test_cases = panel_hold_cases,
};

kunit_test_suite(panel_hold_suite);
23 changes: 21 additions & 2 deletions drivers/gpu/drm/apple/iomfb.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,23 @@ void iomfb_pause_presentation(struct apple_dcp *dcp, bool pause)
}
}

/*
* Hold the 14.7 panel's presents across its display controller's A472 off and
* on. The controller accepts a present while it is off but never completes
* it, and the queue then waits for it: the A472 that would turn the controller
* back on queues behind it until the queue timeout declares the coprocessor
* crashed. Holding before the A472 off is queued keeps the queue order: a
* present admitted earlier still goes out first, to a running controller.
* Admission refuses a held present as a paused one: an atomic commit completes
* with a synthetic vblank, and a brightness level stays pending for the
* backlight restore at power-on.
*/
void iomfb_hold_panel_presents(struct apple_dcp *dcp, bool hold)
{
guard(mutex)(&dcp->iomfb.lock);
dcp->iomfb.panel_held = hold;
}

/* A dead transport is not proof that DMA or a pending present has stopped. */
int iomfb_queue_settle(struct apple_dcp *dcp, unsigned long timeout)
{
Expand Down Expand Up @@ -493,7 +510,8 @@ int iomfb_queue(struct apple_dcp *dcp, struct iomfb_transaction *transaction)
mutex_lock(&dcp->iomfb.lock);
if (READ_ONCE(dcp->crashed) || dcp->iomfb.stopped) {
ret = -EIO;
} else if (transaction->presentation && dcp->iomfb.presentation_paused) {
} else if (transaction->presentation &&
(dcp->iomfb.presentation_paused || dcp->iomfb.panel_held)) {
ret = -EAGAIN;
} else if (transaction->brightness_only && dcp->iomfb.backlight_queued) {
ret = -EALREADY;
Expand Down Expand Up @@ -2240,7 +2258,8 @@ static void iomfb_queue_atomic(struct apple_dcp *dcp, struct drm_crtc *crtc,
struct iomfb_atomic_transaction *atomic;
int ret;

if (READ_ONCE(dcp->iomfb.presentation_paused)) {
if (READ_ONCE(dcp->iomfb.presentation_paused) ||
READ_ONCE(dcp->iomfb.panel_held)) {
schedule_work(&dcp->vblank_wq);
return;
}
Expand Down