diff --git a/drivers/thunderbolt/apple-dpin-state.c b/drivers/thunderbolt/apple-dpin-state.c index afaa64e3f6ba6..8a54958305ef3 100644 --- a/drivers/thunderbolt/apple-dpin-state.c +++ b/drivers/thunderbolt/apple-dpin-state.c @@ -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 | @@ -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) diff --git a/drivers/thunderbolt/apple-dpin-state.h b/drivers/thunderbolt/apple-dpin-state.h index 38b8ec7eb439e..6ddb128d8b2f0 100644 --- a/drivers/thunderbolt/apple-dpin-state.h +++ b/drivers/thunderbolt/apple-dpin-state.h @@ -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 diff --git a/drivers/thunderbolt/apple-dpin-test.c b/drivers/thunderbolt/apple-dpin-test.c index dd532a2a051b5..94292ef00f8a9 100644 --- a/drivers/thunderbolt/apple-dpin-test.c +++ b/drivers/thunderbolt/apple-dpin-test.c @@ -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)); } @@ -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)); @@ -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); + } } } @@ -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 }, @@ -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)); @@ -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), diff --git a/drivers/thunderbolt/apple.c b/drivers/thunderbolt/apple.c index 70a21ade49c06..0a37cbdd6f55f 100644 --- a/drivers/thunderbolt/apple.c +++ b/drivers/thunderbolt/apple.c @@ -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) { @@ -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]; @@ -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]; @@ -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]; @@ -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; @@ -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;