Skip to content
Open
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
8 changes: 7 additions & 1 deletion drivers/thunderbolt/apple-dpin-state.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const struct apple_dpin_policy apple_dpin_m1 = {

const struct apple_dpin_policy apple_dpin_m2 = {
.flow = APPLE_DPIN_PRE_POST,
/* Capacity retry remains disabled pending M2 hardware qualification. */
.capacity_retry = true,
.t602x_handshake = true,
.host_policy = TB_HOST_DP_HPD_ON_ACTIVATE |
TB_HOST_DP_ACTIVE_BEFORE_DPRX |
Expand Down Expand Up @@ -152,6 +152,12 @@ bool apple_dpin_awaits_display(const struct apple_dpin_state *s,
return s->alive && (s->waiting || (p->defer_new_bringup && s->deferred_first));
}

/* Retries pause across system sleep wherever a tunnel can wait or be deferred. */
bool apple_dpin_pm_gated(const struct apple_dpin_policy *p)
{
return p->capacity_retry || p->defer_new_bringup;
}

enum apple_dpin_event apple_dpin_request_event(const struct apple_dpin_state *s,
const struct apple_dpin_tokens *t,
u64 generation, bool active)
Expand Down
1 change: 1 addition & 0 deletions drivers/thunderbolt/apple-dpin-state.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,6 @@ bool apple_dpin_admission_blocked(const struct apple_dpin_state *state,
const struct apple_dpin_policy *policy, bool admitted);
bool apple_dpin_awaits_display(const struct apple_dpin_state *state,
const struct apple_dpin_policy *policy);
bool apple_dpin_pm_gated(const struct apple_dpin_policy *policy);

#endif
116 changes: 101 additions & 15 deletions drivers/thunderbolt/apple-dpin-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ static void apple_dpin_capacity_trace(struct kunit *test)
};

run_trace(test, &apple_dpin_m1, trace, ARRAY_SIZE(trace));
run_trace(test, &apple_dpin_m2, trace, ARRAY_SIZE(trace));
run_trace(test, &apple_dpin_m3, trace, ARRAY_SIZE(trace));
}

Expand All @@ -233,8 +234,12 @@ static void apple_dpin_t602x_rearm_trace(struct kunit *test)
{ APPLE_DPIN_DROPPED, false, 0, 0, APPLE_DPIN_ACTIVATING },
{ APPLE_DPIN_WORK, false, 0, APPLE_DPIN_ATTACH, APPLE_DPIN_ACTIVATING },
{ APPLE_DPIN_RESULT, true, -EBUSY,
APPLE_DPIN_CANCEL_RETRY | APPLE_DPIN_WARN, APPLE_DPIN_FAILED },
{ APPLE_DPIN_RETRY, true, 0, 0, APPLE_DPIN_FAILED },
APPLE_DPIN_FIRST_WAIT | APPLE_DPIN_ARM_RETRY, APPLE_DPIN_WAITING_PIPELINE },
{ APPLE_DPIN_RETRY, true, 0, APPLE_DPIN_QUEUE, APPLE_DPIN_WAITING_PIPELINE },
{ APPLE_DPIN_WORK, true, 0, APPLE_DPIN_ATTACH, APPLE_DPIN_ACTIVATING },
{ APPLE_DPIN_RESULT, true, 0,
APPLE_DPIN_RECOVERED | APPLE_DPIN_LOG_CONNECTED | APPLE_DPIN_AGAIN,
APPLE_DPIN_HANDED },
};

run_trace(test, &apple_dpin_m2, trace, ARRAY_SIZE(trace));
Expand Down Expand Up @@ -297,18 +302,24 @@ static void apple_dpin_unplug_during_attach(struct kunit *test)
static void apple_dpin_terminal_ends_wait(struct kunit *test)
{
const int errors[] = { -ENODEV, -EADDRINUSE, -EOPNOTSUPP, -ESHUTDOWN, -ENOMEM };
unsigned int i, actions;

for (i = 0; i < ARRAY_SIZE(errors); i++) {
struct apple_dpin_state s = { .alive = true, .waiting = true };

actions = apple_dpin_step(&s, &apple_dpin_m1, APPLE_DPIN_RESULT,
true, errors[i]);
KUNIT_EXPECT_EQ(test, actions, APPLE_DPIN_CANCEL_RETRY | APPLE_DPIN_WARN);
KUNIT_EXPECT_FALSE(test, s.waiting);
KUNIT_EXPECT_EQ(test, s.phase, APPLE_DPIN_FAILED);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, &apple_dpin_m1,
APPLE_DPIN_RETRY, true, 0), 0U);
const struct apple_dpin_policy * const policies[] = {
&apple_dpin_m1, &apple_dpin_m2, &apple_dpin_m3,
};
unsigned int i, j, actions;

for (j = 0; j < ARRAY_SIZE(policies); j++) {
for (i = 0; i < ARRAY_SIZE(errors); i++) {
struct apple_dpin_state s = { .alive = true, .waiting = true };

actions = apple_dpin_step(&s, policies[j], APPLE_DPIN_RESULT,
true, errors[i]);
KUNIT_EXPECT_EQ(test, actions,
APPLE_DPIN_CANCEL_RETRY | APPLE_DPIN_WARN);
KUNIT_EXPECT_FALSE(test, s.waiting);
KUNIT_EXPECT_EQ(test, s.phase, APPLE_DPIN_FAILED);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, policies[j],
APPLE_DPIN_RETRY, true, 0), 0U);
}
}
}

Expand Down Expand Up @@ -414,7 +425,7 @@ static void apple_dpin_all_policy_fields(struct kunit *test)
const struct apple_dpin_policy expected[] = {
{},
{ APPLE_DPIN_CHANGED, true, true, false, true, TB_HOST_DP_NOTIFY },
{ APPLE_DPIN_PRE_POST, false, false, true, false,
{ APPLE_DPIN_PRE_POST, true, false, true, false,
TB_HOST_DP_HPD_ON_ACTIVATE | TB_HOST_DP_ACTIVE_BEFORE_DPRX |
TB_HOST_DP_KEEP_DPRX_TIMEOUT | TB_HOST_DP_ADAPTER_QUIRKS |
TB_HOST_DP_INITIAL_BW_GRANT },
Expand Down Expand Up @@ -632,6 +643,7 @@ static void apple_dpin_sleep_dprx_retention(struct kunit *test)
s = (struct apple_dpin_state){ .alive = true, .handed = true, .paused = true };
KUNIT_EXPECT_FALSE(test, apple_dpin_awaits_display(&s, &apple_dpin_m1));
s.handed = false;
/* M2 never installs the hook: KEEP_DPRX_TIMEOUT holds its tunnels. */
KUNIT_EXPECT_FALSE(test, apple_dpin_awaits_display(&s, &apple_dpin_m2));
apple_dpin_step(&s, &apple_dpin_m3, APPLE_DPIN_UP, false, 0);
KUNIT_EXPECT_TRUE(test, apple_dpin_awaits_display(&s, &apple_dpin_m3));
Expand Down Expand Up @@ -1185,7 +1197,81 @@ static void apple_dpin_m3_capacity_suspend(struct kunit *test)
KUNIT_EXPECT_FALSE(test, apple_dpin_awaits_display(&s, p));
}

/* M2 pauses retries over sleep without deferring new handoffs. */
static void apple_dpin_m2_capacity_suspend(struct kunit *test)
{
struct apple_dpin_state s = {};
const struct apple_dpin_policy *p = &apple_dpin_m2;

KUNIT_EXPECT_FALSE(test, apple_dpin_pm_gated(&apple_dpin_disabled));
KUNIT_EXPECT_TRUE(test, apple_dpin_pm_gated(&apple_dpin_m1));
KUNIT_EXPECT_TRUE(test, apple_dpin_pm_gated(p));

apple_dpin_step(&s, p, APPLE_DPIN_UP, false, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_WORK, false, 0),
APPLE_DPIN_ATTACH);
apple_dpin_step(&s, p, APPLE_DPIN_ADMITTED, true, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESULT, true, -EBUSY),
APPLE_DPIN_FIRST_WAIT | APPLE_DPIN_ARM_RETRY);
apple_dpin_step(&s, p, APPLE_DPIN_PAUSE, true, 0);
KUNIT_EXPECT_FALSE(test, apple_dpin_admission_blocked(&s, p, false));
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RETRY, true, 0), 0U);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESUME, true, 0),
APPLE_DPIN_ARM_RETRY);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RETRY, true, 0),
APPLE_DPIN_QUEUE);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_WORK, true, 0),
APPLE_DPIN_ATTACH);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESULT, true, 0),
APPLE_DPIN_RECOVERED | APPLE_DPIN_LOG_CONNECTED | APPLE_DPIN_AGAIN);

/* A tunnel refused after an early RESUME arms its own retry. */
s = (struct apple_dpin_state){};
apple_dpin_step(&s, p, APPLE_DPIN_PAUSE, false, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESUME, false, 0), 0U);
apple_dpin_step(&s, p, APPLE_DPIN_UP, false, 0);
apple_dpin_step(&s, p, APPLE_DPIN_WORK, false, 0);
apple_dpin_step(&s, p, APPLE_DPIN_ADMITTED, true, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESULT, true, -EBUSY),
APPLE_DPIN_FIRST_WAIT | APPLE_DPIN_ARM_RETRY);

/* RESUME with no PAUSE before it only rearms a wait. */
s = (struct apple_dpin_state){ .alive = true };
apple_dpin_step(&s, p, APPLE_DPIN_RESULT, true, -EBUSY);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESUME, true, 0),
APPLE_DPIN_ARM_RETRY);
KUNIT_EXPECT_TRUE(test, s.waiting);
KUNIT_EXPECT_FALSE(test, s.paused);
KUNIT_EXPECT_EQ(test, s.phase, APPLE_DPIN_WAITING_PIPELINE);

/* A readiness wait cut short by prepare waits, and asks after complete. */
s = (struct apple_dpin_state){};
apple_dpin_step(&s, p, APPLE_DPIN_UP, false, 0);
apple_dpin_step(&s, p, APPLE_DPIN_WORK, false, 0);
apple_dpin_step(&s, p, APPLE_DPIN_ADMITTED, true, 0);
apple_dpin_step(&s, p, APPLE_DPIN_PAUSE, true, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESULT, true, -EAGAIN),
APPLE_DPIN_FIRST_WAIT);
KUNIT_EXPECT_EQ(test, s.phase, APPLE_DPIN_WAITING_PIPELINE);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESUME, true, 0),
APPLE_DPIN_ARM_RETRY);

/* Unplug while waiting cancels the retry; NHI removal ends the gate. */
apple_dpin_step(&s, p, APPLE_DPIN_PAUSE, true, 0);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_DOWN, true, 0),
APPLE_DPIN_QUEUE);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_WORK, true, 0),
APPLE_DPIN_CANCEL_RETRY | APPLE_DPIN_DROP);
KUNIT_EXPECT_FALSE(test, s.waiting);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RETRY, true, 0), 0U);
apple_dpin_step(&s, p, APPLE_DPIN_DROPPED, false, 0);
apple_dpin_step(&s, p, APPLE_DPIN_END_PM_GATE, false, 0);
KUNIT_EXPECT_FALSE(test, s.paused);
KUNIT_EXPECT_EQ(test, apple_dpin_step(&s, p, APPLE_DPIN_RESUME, false, 0), 0U);
}

static struct kunit_case apple_dpin_cases[] = {
KUNIT_CASE(apple_dpin_m2_capacity_suspend),
KUNIT_CASE(apple_dpin_m3_capacity_suspend),
KUNIT_CASE(apple_dpin_provider_handed_lifetime),
KUNIT_CASE(apple_dpin_provider_failed_retry),
Expand Down
52 changes: 40 additions & 12 deletions drivers/thunderbolt/apple.c
Original file line number Diff line number Diff line change
Expand Up @@ -1060,9 +1060,10 @@ static void apple_dpin_retry_fn(struct work_struct *work)
}

/*
* Only where both the display allocator and the connection manager can hold
* a tunnel for a pipeline (capacity-retry hosts, see
* apple_nhi_dp_tunnel_awaits_display()) does a refused tunnel wait for one.
* Only where the policy has capacity_retry does a refused tunnel wait for a
* pipeline. The connection manager keeps it meanwhile: through
* apple_nhi_dp_tunnel_awaits_display() on the APPLE_DPIN_CHANGED flow, and
* through TB_HOST_DP_KEEP_DPRX_TIMEOUT on the APPLE_DPIN_PRE_POST one.
*/
static void apple_dpin_work_fn(struct work_struct *work)
{
Expand Down Expand Up @@ -2606,9 +2607,20 @@ static int apple_nhi_probe(struct platform_device *pdev)

static void apple_dpin_end_pm_gate(struct apple_cio *acio)
{
if (!acio->dp_wq || !acio->dp->defer_new_bringup)
if (!acio->dp_wq || !apple_dpin_pm_gated(acio->dp))
return;
/* Domain removal stopped producers. No DP-IN/domain/fabric lock held. */
/*
* Domain removal stopped producers. No DP-IN/domain/fabric lock held.
* Pause first, as apple_dpin_pause_retries() does, so that work still
* in flight cannot rearm the retry once it is cancelled.
*/
for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) {
struct apple_dpin_ctx *c = &acio->dpin[i];

scoped_guard(mutex, &c->lock)
apple_dpin_step(&c->state, acio->dp, APPLE_DPIN_PAUSE,
!!c->regs, 0);
}
for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) {
struct apple_dpin_ctx *c = &acio->dpin[i];

Expand Down Expand Up @@ -2644,13 +2656,14 @@ static void apple_nhi_remove(struct platform_device *pdev)
* seconds. Not while devices suspend and resume: stop asking before any of
* them suspends, let an attempt in flight finish (a wait for appledrm to
* load gives way, see apple_dpin_connect()), and ask again once all of them
* have resumed. The tunnel keeps waiting meanwhile. Suspend-gated handoffs
* are deferred until complete, including after an aborted suspend. Established
* handed callbacks and synchronous tunnel teardown remain permitted.
* have resumed. The tunnel keeps waiting meanwhile. Where the policy has
* defer_new_bringup, new handoffs are deferred until complete, including
* after an aborted suspend. Established handed callbacks and synchronous
* tunnel teardown remain permitted.
*/
static void apple_dpin_pause_retries(struct apple_cio *acio)
{
if (!acio->dp_wq || !acio->dp->defer_new_bringup)
if (!acio->dp_wq || !apple_dpin_pm_gated(acio->dp))
return;
for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) {
struct apple_dpin_ctx *c = &acio->dpin[i];
Expand All @@ -2668,7 +2681,7 @@ static void apple_dpin_pause_retries(struct apple_cio *acio)

static void apple_dpin_resume_retries(struct apple_cio *acio)
{
if (!acio->dp_wq || !acio->dp->defer_new_bringup)
if (!acio->dp_wq || !apple_dpin_pm_gated(acio->dp))
return;
for (unsigned int i = 0; i < ARRAY_SIZE(acio->dpin); i++) {
struct apple_dpin_ctx *c = &acio->dpin[i];
Expand All @@ -2692,8 +2705,15 @@ static int apple_nhi_prepare(struct device *dev)

acio->resume_had_peer = false;
/* Only resume recovery reads it: don't take the domain lock otherwise. */
if (!READ_ONCE(resume_recovery))
if (!READ_ONCE(resume_recovery)) {
/*
* Policies that defer new handoffs keep that to resume recovery;
* capacity retries alone pause across every sleep.
*/
if (!acio->dp->defer_new_bringup)
apple_dpin_pause_retries(acio);
return 0;
}
scoped_guard(mutex, &anhi->tb->lock) {
struct tb_port *port;

Expand Down Expand Up @@ -2821,8 +2841,16 @@ static void apple_nhi_complete(struct device *dev)
struct apple_nhi *anhi = dev_get_drvdata(dev);
struct apple_cio *acio = anhi->acio;

/*
* Every device has resumed. Without deferred handoffs, a tunnel that
* the domain's complete brings up makes its first attempt right away;
* let its retries run from then on too.
*/
if (!acio->dp->defer_new_bringup)
apple_dpin_resume_retries(acio);
tb_domain_complete(anhi->tb);
apple_dpin_resume_retries(acio);
if (acio->dp->defer_new_bringup)
apple_dpin_resume_retries(acio);

guard(mutex)(&acio->pcie_tunnel_lock);
acio->pcie_pm_prepared = false;
Expand Down
Loading