From 39391bb8b596aface4d5126b92c78c594b5cbd11 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 4 Nov 2026 23:47:25 -0500 Subject: [PATCH 1/2] thunderbolt: let a lone host DP tunnel run at HBR3, two share HBR2 TB_HOST_DP_HBR2_MAX caps every DP tunnel from the host router's DP IN adapters at HBR2, because both DP IN streams of the PHY must run at one link rate. A single display does not need that cap: alone on the host router a DP tunnel can run at HBR3 x4 (25920 Mb/s), which carries modes that HBR2 x4 (17280 Mb/s) cannot. Rename the flag to TB_HOST_DP_SHARED_RATE and decide the cap where DP IN and DP OUT adapters are paired, under the domain lock: - when one DP tunnel will be up on the host router, it may offer up to HBR3; when more will, each new one offers HBR2 at most; - when another DP tunnel is wanted (a second DP OUT adapter became available) while one is up above HBR2, that one is torn down and set up again between the same adapters at HBR2 first; the new one then takes its rate. Its display sees an unplug and a plug. If it cannot be set up again, pairing runs once more, and at the next event; - a tunnel alone that has to be reduced takes the combination with the most bandwidth (HBR2 x4 rather than HBR3 x2). Held at HBR2, or next to another tunnel, it keeps the rate and gives up lanes as before. A host DP tunnel is also counted at its cap at most in the bandwidth left for the others. Once its DPRX is done, the bandwidth it takes is worked out from what its DP IN adapter reports, which the cap does not bound: held at HBR2 between adapters that take HBR3, it could be counted at HBR3 x4, and a second display next to it would then get HBR2 x2 rather than x4. Nothing is set up again during a system sleep transition: from its PREPARE event (a PM notifier ahead of all others) until it is over, POST callbacks and the rollback of a refused PREPARE included (the gate is reopened by a work item that takes the system sleep lock after the POST event); a re-link admitted before holds the transition back until it is done. The re-link, and the pairing that needs it, then wait: a work item on the freezable workqueue hands them to the domain's ordered workqueue once the transition is over, behind any DPRX or teardown work queued before. The request also stays pending for the resume and thaw replays, and a failed runtime resume of the domain is retried a few times. The KUnit tests reach the connection manager's DP bandwidth accounting on a domain set up without hardware, reading adapter registers through a static stub on tb_cfg_read(); tb_cm_init() is split out of tb_probe() for that. The DP repair host test models the connection manager's private data: add the new work item there, and take the work initializers from tb_cm_init(). Signed-off-by: Erik Bourget --- drivers/thunderbolt/apple-dpin-state.c | 6 +- drivers/thunderbolt/apple-dpin-test.c | 12 +- drivers/thunderbolt/ctl.c | 11 +- drivers/thunderbolt/domain.c | 2 + drivers/thunderbolt/nhi.h | 9 +- drivers/thunderbolt/tb.c | 491 ++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 13 + drivers/thunderbolt/test.c | 330 ++++++++++++++++- drivers/thunderbolt/tunnel.c | 62 +++- drivers/thunderbolt/tunnel.h | 55 ++- tools/asahi/tb-dp-repair-tests.py | 6 +- 11 files changed, 947 insertions(+), 50 deletions(-) diff --git a/drivers/thunderbolt/apple-dpin-state.c b/drivers/thunderbolt/apple-dpin-state.c index 2c3eadc4ad0f1..1a2721d6e84a4 100644 --- a/drivers/thunderbolt/apple-dpin-state.c +++ b/drivers/thunderbolt/apple-dpin-state.c @@ -38,14 +38,14 @@ const struct apple_dpin_policy apple_dpin_m3 = { * writes only DPTX_INACTIVE (+0x0c bit 0) and polls its ACK (+0x10 bit 0), * gated on HPD. The DP IN interrupts are not set up (nothing handles them), * and a tunnel that DCP never activated is torn down without touching the - * block at all. Both DP IN streams of an ATC PHY need one link rate, so - * every tunnel gets HBR2 at most. + * block at all. Both DP IN streams of an ATC PHY need one link rate: a + * tunnel alone gets up to HBR3, two get HBR2 (TB_HOST_DP_SHARED_RATE). */ const struct apple_dpin_policy apple_dpin_m4 = { .flow = APPLE_DPIN_CHANGED, .capacity_retry = true, .defer_new_bringup = true, - .host_policy = TB_HOST_DP_NOTIFY | TB_HOST_DP_HBR2_MAX, + .host_policy = TB_HOST_DP_NOTIFY | TB_HOST_DP_SHARED_RATE, .lazy_idle = true, }; diff --git a/drivers/thunderbolt/apple-dpin-test.c b/drivers/thunderbolt/apple-dpin-test.c index c9664ffb5e9a6..0d4891ad74e14 100644 --- a/drivers/thunderbolt/apple-dpin-test.c +++ b/drivers/thunderbolt/apple-dpin-test.c @@ -381,7 +381,7 @@ static void apple_dpin_qualification_profiles(struct kunit *test) /* * M4 (T8132): the M3 flow without DP IN interrupts, a lazy teardown, and - * DP tunnels capped at HBR2. + * DP tunnels sharing one link rate. */ static void apple_dpin_m4_policy(struct kunit *test) { @@ -393,15 +393,15 @@ static void apple_dpin_m4_policy(struct kunit *test) KUNIT_EXPECT_FALSE(test, p->t602x_handshake); KUNIT_EXPECT_TRUE(test, p->defer_new_bringup); KUNIT_EXPECT_TRUE(test, p->lazy_idle); - KUNIT_EXPECT_EQ(test, p->host_policy, TB_HOST_DP_NOTIFY | TB_HOST_DP_HBR2_MAX); + KUNIT_EXPECT_EQ(test, p->host_policy, TB_HOST_DP_NOTIFY | TB_HOST_DP_SHARED_RATE); /* The hooks of the M3 flow: a queue and a display pipeline both. */ KUNIT_EXPECT_EQ(test, apple_dpin_hooks(p, true, true), APPLE_DPIN_CHANGED); KUNIT_EXPECT_EQ(test, apple_dpin_hooks(p, true, false), APPLE_DPIN_DISABLED); KUNIT_EXPECT_EQ(test, apple_dpin_hooks(p, false, true), APPLE_DPIN_DISABLED); - /* No other Apple host is capped. */ - KUNIT_EXPECT_FALSE(test, apple_dpin_m3.host_policy & TB_HOST_DP_HBR2_MAX); - KUNIT_EXPECT_FALSE(test, apple_dpin_m2.host_policy & TB_HOST_DP_HBR2_MAX); - KUNIT_EXPECT_FALSE(test, apple_dpin_m1.host_policy & TB_HOST_DP_HBR2_MAX); + /* No other Apple host shares one rate. */ + KUNIT_EXPECT_FALSE(test, apple_dpin_m3.host_policy & TB_HOST_DP_SHARED_RATE); + KUNIT_EXPECT_FALSE(test, apple_dpin_m2.host_policy & TB_HOST_DP_SHARED_RATE); + KUNIT_EXPECT_FALSE(test, apple_dpin_m1.host_policy & TB_HOST_DP_SHARED_RATE); } static void apple_dpin_all_policy_fields(struct kunit *test) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 95896401571cc..24d01880fdb3e 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "ctl.h" @@ -1111,8 +1112,14 @@ static int tb_cfg_get_error(struct tb_ctl *ctl, enum tb_cfg_space space, int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, enum tb_cfg_space space, u32 offset, u32 length) { - struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port, - space, offset, length, ctl->timeout_msec); + struct tb_cfg_result res; + + /* The KUnit tests read adapter registers without hardware */ + KUNIT_STATIC_STUB_REDIRECT(tb_cfg_read, ctl, buffer, route, port, space, offset, + length); + + res = tb_cfg_read_raw(ctl, buffer, route, port, space, offset, length, + ctl->timeout_msec); switch (res.err) { case 0: /* Success */ diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 17020bf90a92a..56ffea80c5981 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -927,6 +927,7 @@ int tb_domain_init(void) ret = bus_register(&tb_bus_type); if (ret) goto err_xdomain; + tb_dp_pm_gate_init(); return 0; @@ -941,6 +942,7 @@ int tb_domain_init(void) void tb_domain_exit(void) { + tb_dp_pm_gate_exit(); bus_unregister(&tb_bus_type); ida_destroy(&tb_domain_ida); tb_nvm_exit(); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index 69aecbd97277c..7201e0c279cc3 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -225,11 +225,12 @@ struct tb_nhi_ops { #define TB_HOST_DP_ADAPTER_QUIRKS BIT(4) #define TB_HOST_DP_INITIAL_BW_GRANT BIT(5) /* - * Both DP IN streams of a host PHY must run at one link rate: DP tunnels - * from the host's DP IN adapters offer at most HBR2, at which two 4K60 - * streams still fit. + * Both DP IN streams of a host PHY must run at one link rate. A DP tunnel + * alone on the host router offers up to HBR3; when more are needed, all of + * them offer HBR2 at most (two 4K60 streams fit), a tunnel up above that + * being set up again at HBR2 first. */ -#define TB_HOST_DP_HBR2_MAX BIT(6) +#define TB_HOST_DP_SHARED_RATE BIT(6) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 3816b12422064..4f7c55ccb241d 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #include "tb.h" #include "tb_regs.h" @@ -64,6 +67,14 @@ MODULE_PARM_DESC(asym_threshold, * runtime resume * @dp_tunnel_work: Re-pair DP resources after retired DPRX work releases its paths * @dp_tunnel_pending: A pairing request not yet serviced, retained across sleep + * @dp_relink_work: Waits on the freezable queue until no system sleep + * transition is in progress, then queues @dp_relink_run + * @dp_relink_run: The host DP tunnel re-link work proper, on the domain + * queue: after any DPRX or teardown work queued before it + * @dp_relink_deferred: The pairing that needs a host DP tunnel's re-link + * waits for system sleep to end (@dp_tunnel_pending is + * set with it, for the resume and thaw replays) + * @dp_relink_tries: Runtime resume failures of @dp_relink_run in a row * @groups: Bandwidth groups used in this domain. */ struct tb_cm { @@ -74,6 +85,10 @@ struct tb_cm { struct delayed_work remove_work; struct work_struct dp_tunnel_work; bool dp_tunnel_pending; + struct delayed_work dp_relink_work; + struct work_struct dp_relink_run; + bool dp_relink_deferred; + unsigned int dp_relink_tries; struct tb_bandwidth_group groups[MAX_GROUPS]; }; @@ -98,6 +113,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, static void tb_queue_dp_bandwidth_request(struct tb *tb, u64 route, u8 port, int retry, unsigned long delay); static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data); +static void tb_queue_dp_tunnels(struct tb *tb); static void tb_queue_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) { @@ -612,12 +628,9 @@ static int tb_consumed_usb3_pcie_bandwidth(struct tb *tb, * * Return: %0 on success, negative errno otherwise. */ -static int tb_consumed_dp_bandwidth(struct tb *tb, - struct tb_port *src_port, - struct tb_port *dst_port, - struct tb_port *port, - int *consumed_up, - int *consumed_down) +VISIBLE_IF_KUNIT int tb_consumed_dp_bandwidth(struct tb *tb, struct tb_port *src_port, + struct tb_port *dst_port, struct tb_port *port, + int *consumed_up, int *consumed_down) { int group_reserved[MAX_GROUPS + 1] = {}; struct tb_cm *tcm = tb_priv(tb); @@ -2054,8 +2067,13 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) mutex_unlock(&tb->lock); } -static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, - struct tb_port *out) +/* + * Set up a DP tunnel from @in to @out. @rate_limit: the highest link rate + * (Mb/s per lane) under TB_HOST_DP_SHARED_RATE. The tunnel if it is up or + * being activated, NULL if not. + */ +static struct tb_tunnel *tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, + struct tb_port *out, u32 rate_limit) { int available_up, available_down, ret, link_nr; struct tb_cm *tcm = tb_priv(tb); @@ -2117,6 +2135,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim_usb; } + tunnel->host_rate_limit = rate_limit; list_add_tail(&tunnel->list, &tcm->tunnel_list); @@ -2133,7 +2152,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, list_del(&tunnel->list); goto err_free; } - return; + return tunnel; err_free: tb_tunnel_put(tunnel); @@ -2148,11 +2167,12 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, pm_runtime_put_autosuspend(&out->sw->dev); pm_runtime_mark_last_busy(&in->sw->dev); pm_runtime_put_autosuspend(&in->sw->dev); + return NULL; } /* * The link rate (Mb/s per lane) another DP tunnel from @tunnel's host router - * was given under TB_HOST_DP_HBR2_MAX, 0 if there is none. Called with the + * was given under TB_HOST_DP_SHARED_RATE, 0 if there is none. Called with the * domain lock held, from the tunnel's activation. */ u32 tb_dp_host_shared_rate(const struct tb_tunnel *tunnel) @@ -2167,16 +2187,329 @@ u32 tb_dp_host_shared_rate(const struct tb_tunnel *tunnel) return 0; } +/* + * TB_HOST_DP_SHARED_RATE: the highest link rate (Mb/s per lane) each of + * @tunnels DP tunnels from the host router's DP IN adapters may offer: HBR3 + * for one, HBR2 for more (two 4K60 streams fit at HBR2 x4). + */ +u32 tb_dp_host_rate_for(unsigned int tunnels) +{ + return tunnels > 1 ? TB_DP_HOST_RATE_SHARED : TB_DP_HOST_RATE_ALONE; +} + +/* + * TB_HOST_DP_SHARED_RATE: whether a host DP tunnel up at @rate (Mb/s per + * lane) is set up again at HBR2 when @tunnels are wanted on its host router + * in all. Above HBR2 next to another it is, so that both streams of the PHY + * run at one rate; while system sleep is in progress (@sleeping) only once + * it is over. + */ +enum tb_dp_relink tb_dp_relink_down(u32 rate, unsigned int tunnels, bool sleeping) +{ + if (tunnels < 2 || rate <= TB_DP_HOST_RATE_SHARED) + return TB_DP_RELINK_NONE; + return sleeping ? TB_DP_RELINK_WAIT : TB_DP_RELINK_NOW; +} + +/* + * TB_HOST_DP_SHARED_RATE re-links tear a display's DP tunnel down and set it + * up again. None runs during a system sleep transition: from its PREPARE + * event, before any other PM notifier hears of it, until the transition is + * over, its POST callbacks and the rollback of a refused PREPARE included. + * Every PM notifier call is made with the system sleep lock held, so the + * gate is reopened by a work item that takes that lock after the POST event. + * Admission holds tb_dp_pm_gate for reading while it re-links; the notifier + * takes it for writing, so a re-link in progress is over before any other + * notifier hears of the transition. + */ +static DECLARE_RWSEM(tb_dp_pm_gate); +static struct tb_dp_pm_state tb_dp_pm; + +/* + * PREPARE closes the gate, and drops a reopen asked for by an earlier POST + * that has not run yet (the snapshot device lets go of the system sleep lock + * between its PREPARE and POST). The notifier comes first and refuses + * nothing, so it gets the POST event of every PREPARE it got, also when the + * PREPARE is refused after it: POST asks for the reopen (true). Other + * events: nothing. + */ +bool tb_dp_pm_event(struct tb_dp_pm_state *s, unsigned long event) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + case PM_HIBERNATION_PREPARE: + case PM_RESTORE_PREPARE: + s->sleeping = true; + s->reopen = false; + return false; + case PM_POST_SUSPEND: + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + s->reopen = true; + return true; + } + return false; +} + +/* No transition in progress (system sleep lock held): reopen if one ended. */ +void tb_dp_pm_reopen(struct tb_dp_pm_state *s) +{ + if (s->reopen) + s->sleeping = false; + s->reopen = false; +} + +static void tb_dp_pm_reopen_fn(struct work_struct *work) +{ + unsigned int flags = lock_system_sleep(); + + down_write(&tb_dp_pm_gate); + tb_dp_pm_reopen(&tb_dp_pm); + up_write(&tb_dp_pm_gate); + unlock_system_sleep(flags); +} + +static DECLARE_WORK(tb_dp_pm_reopen_work, tb_dp_pm_reopen_fn); + +static int tb_dp_pm_notify(struct notifier_block *nb, unsigned long event, void *unused) +{ + bool reopen; + + down_write(&tb_dp_pm_gate); + reopen = tb_dp_pm_event(&tb_dp_pm, event); + up_write(&tb_dp_pm_gate); + if (reopen) + queue_work(system_unbound_wq, &tb_dp_pm_reopen_work); + return NOTIFY_DONE; +} + +static struct notifier_block tb_dp_pm_nb = { + .notifier_call = tb_dp_pm_notify, + .priority = INT_MAX, +}; + +void tb_dp_pm_gate_init(void) +{ + register_pm_notifier(&tb_dp_pm_nb); +} + +void tb_dp_pm_gate_exit(void) +{ + unregister_pm_notifier(&tb_dp_pm_nb); + flush_work(&tb_dp_pm_reopen_work); +} + +/* + * Whether a host DP tunnel may be re-linked now; if so, tb_dp_relink_done() + * once it is. Never waits for a transition to start or end. + */ +static bool tb_dp_relink_admit(void) +{ + if (!down_read_trylock(&tb_dp_pm_gate)) + return false; + if (!tb_dp_pm.sleeping) + return true; + up_read(&tb_dp_pm_gate); + return false; +} + +static void tb_dp_relink_done(void) +{ + up_read(&tb_dp_pm_gate); +} + +/* No system sleep transition in progress, as far as can be told now. */ +static bool tb_dp_pm_open(void) +{ + if (!tb_dp_relink_admit()) + return false; + tb_dp_relink_done(); + return true; +} + +/* + * The delay (ms) before the re-link work is tried again after its @tries-th + * failure to runtime resume the domain in a row, 0 for not any more: the + * request then waits for the next resume, thaw or plug event. + */ +unsigned int tb_dp_relink_retry_delay(unsigned int tries) +{ + return tries < TB_DP_RELINK_RETRIES ? TB_DP_RELINK_DELAY_MS << tries : 0; +} + +static bool tb_dp_host_shares_rate(const struct tb *tb) +{ + return tb->nhi && (tb->nhi->host_dp_policy & TB_HOST_DP_SHARED_RATE); +} + +/* A DP tunnel from a DP IN adapter of the (Apple) host router. */ +static bool tb_tunnel_is_host_dp(const struct tb_tunnel *tunnel) +{ + return tb_tunnel_is_dp(tunnel) && tb_port_is_apple_host_dpin(tunnel->src_port); +} + +/* The DP tunnels from the host router's DP IN adapters; @last: the last one. */ +static unsigned int tb_dp_host_tunnels(struct tb *tb, struct tb_tunnel **last) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + unsigned int n = 0; + + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (!tb_tunnel_is_host_dp(tunnel)) + continue; + n++; + if (last) + *last = tunnel; + } + return n; +} + +/* + * How many more DP tunnels from the host router's DP IN adapters pairing + * can set up now: its free DP IN adapters against the free DP OUT adapters + * of the other routers (see tb_find_dp_out()). + */ +static unsigned int tb_dp_host_free_pairs(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + unsigned int ins = 0, outs = 0; + struct tb_port *port; + + list_for_each_entry(port, &tcm->dp_resources, list) { + if (tb_port_is_dpin(port)) { + if (tb_port_is_apple_host_dpin(port) && !tb_port_is_enabled(port)) + ins++; + } else if (tb_port_is_dpout(port) && tb_route(port->sw) && + !tb_port_is_enabled(port)) { + outs++; + } + } + return min(ins, outs); +} + +/* + * Run the re-link work (dp_relink_run) @delay_ms from now, and only once no + * system sleep transition is in progress. One domain reference for each + * queued execution of either work, as for dp_tunnel_work. Domain lock held. + */ +static void tb_dp_relink_arm(struct tb *tb, unsigned int delay_ms) +{ + struct tb_cm *tcm = tb_priv(tb); + + lockdep_assert_held(&tb->lock); + if (tcm->stopping) + return; + tb_domain_get(tb); + if (mod_delayed_work(system_freezable_wq, &tcm->dp_relink_work, + msecs_to_jiffies(delay_ms))) + tb_domain_put(tb); +} + +/* + * Tear @tunnel down and set it up again between the same adapters, at + * @rate_limit (Mb/s per lane) at most, because of @why. Its display sees an + * unplug and a plug. Only on the domain queue, so that the DPRX work of + * @tunnel is not running (the queue is ordered) and releases its paths when + * canceled; with the domain lock held and the re-link admitted + * (tb_dp_relink_admit()). The new tunnel, NULL if there is none. + */ +static struct tb_tunnel *tb_dp_host_relink(struct tb *tb, struct tb_tunnel *tunnel, + u32 rate_limit, const char *why) +{ + struct tb_tunnel *relinked; + struct tb_port *in = tunnel->src_port; + struct tb_port *out = tunnel->dst_port; + + tb_tunnel_info(tunnel, "%s: re-linking at up to %u Mb/s per lane (was %u)\n", + why, rate_limit, tunnel->host_rate); + tb_deactivate_and_free_tunnel(tunnel); + tb_recalc_estimated_bandwidth(tb); + relinked = tb_tunnel_one_dp(tb, in, out, rate_limit); + if (relinked) + return relinked; + /* The adapters stay listed: pairing tries again, at the next event too. */ + tb_port_warn(in, "DP tunnel not set up again, pairing again\n"); + tb_queue_dp_tunnels(tb); + return NULL; +} + +/* + * TB_HOST_DP_SHARED_RATE: the link rate limit (Mb/s per lane) for the DP + * tunnels pairing sets up now from the host router's DP IN adapters: HBR3 + * when one tunnel will be up, HBR2 when more will. A tunnel already up + * above HBR2 when more are wanted is set up again at HBR2 first, between + * the same adapters, so that the streams of the PHY share one rate. Not + * while system sleep is in progress: then 0, and the host router's DP IN + * adapters are paired once it is over (dp_relink_work), or at the next + * resume or thaw. 0 without the policy. Domain lock held. + */ +static u32 tb_dp_host_rate_limit(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel, *above; + enum tb_dp_relink action; + unsigned int up, want; + bool admitted; + + if (!tb_dp_host_shares_rate(tb)) + return 0; + up = tb_dp_host_tunnels(tb, NULL); + want = up + tb_dp_host_free_pairs(tb); + + /* Each round sets one tunnel up again at HBR2 at most. */ + for (;;) { + above = NULL; + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (tb_tunnel_is_host_dp(tunnel) && + tunnel->host_rate > TB_DP_HOST_RATE_SHARED) { + above = tunnel; + break; + } + } + if (!above) + break; + + admitted = tb_dp_relink_admit(); + action = tb_dp_relink_down(above->host_rate, want, !admitted); + if (action == TB_DP_RELINK_NOW) + tb_dp_host_relink(tb, above, TB_DP_HOST_RATE_SHARED, + "another DP tunnel is wanted"); + if (admitted) { + tb_dp_relink_done(); + tcm->dp_relink_deferred = false; + } + if (action == TB_DP_RELINK_NONE) + break; + if (action == TB_DP_RELINK_WAIT) { + if (!tcm->dp_relink_deferred) + tb_tunnel_info(above, "another DP tunnel is wanted during system sleep, re-linking after it\n"); + /* Replayed by dp_relink_work, and at resume or thaw. */ + tcm->dp_relink_deferred = true; + tcm->dp_tunnel_pending = true; + tb_dp_relink_arm(tb, 0); + return 0; + } + } + return tb_dp_host_rate_for(want); +} + static void tb_tunnel_dp(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); struct tb_port *port, *in, *out; + bool host_waits; + u32 rate_limit; if (!tb_acpi_may_tunnel_dp()) { tb_dbg(tb, "DP tunneling disabled, not creating tunnel\n"); return; } + /* Under TB_HOST_DP_SHARED_RATE: may set a tunnel up again first. */ + rate_limit = tb_dp_host_rate_limit(tb); + host_waits = tb_dp_host_shares_rate(tb) && !rate_limit; + /* * Find pair of inactive DP IN and DP OUT adapters and then * establish a DP tunnel between them. @@ -2197,9 +2530,14 @@ static void tb_tunnel_dp(struct tb *tb) in = port; tb_port_dbg(in, "DP IN available\n"); + if (host_waits && tb_port_is_apple_host_dpin(in)) { + tb_port_dbg(in, "waiting for system sleep to end\n"); + continue; + } + out = tb_find_dp_out(tb, port); if (out) - tb_tunnel_one_dp(tb, in, out); + tb_tunnel_one_dp(tb, in, out, rate_limit); else tb_port_dbg(in, "no suitable DP OUT adapter available, not tunneling\n"); } @@ -2316,6 +2654,76 @@ static void tb_dp_tunnel_work(struct work_struct *work) tb_domain_put(tb); } +/* + * The timer and gate of the re-link work, on the freezable queue: it does + * not run while tasks are frozen, and hands dp_relink_run to the domain + * queue only while no system sleep transition is in progress. + */ +static void tb_dp_relink_work(struct work_struct *work) +{ + struct tb_cm *tcm = container_of(to_delayed_work(work), struct tb_cm, + dp_relink_work); + struct tb *tb = tcm_to_tb(tcm); + + mutex_lock(&tb->lock); + if (!tcm->stopping) { + if (!tb_dp_pm_open()) { + tb_dp_relink_arm(tb, TB_DP_RELINK_POLL_MS); + } else { + tb_domain_get(tb); + if (!queue_work(tb->wq, &tcm->dp_relink_run)) + tb_domain_put(tb); + } + } + mutex_unlock(&tb->lock); + tb_domain_put(tb); +} + +/* + * Pairing that waited for system sleep to end, see tb_dp_host_rate_limit(). + * On the domain queue: behind DPRX and teardown work queued before it, so a + * tunnel torn down there has released its paths. + */ +static void tb_dp_relink_run(struct work_struct *work) +{ + struct tb_cm *tcm = container_of(work, struct tb_cm, dp_relink_run); + struct tb *tb = tcm_to_tb(tcm); + unsigned int delay; + int ret; + + /* Runtime resume can acquire the domain lock, so do it first. */ + ret = pm_runtime_resume_and_get(&tb->dev); + + mutex_lock(&tb->lock); + if (tcm->stopping || !tb->root_switch) + goto unlock; + if (ret < 0) { + /* A few more tries; then the next resume, thaw or plug event. */ + delay = tb_dp_relink_retry_delay(tcm->dp_relink_tries++); + if (delay) + tb_dp_relink_arm(tb, delay); + else + tb_dbg(tb, "DP re-link waits for the next event: runtime resume failed: %d\n", + ret); + goto unlock; + } + tcm->dp_relink_tries = 0; + if (tcm->hotplug_active && tcm->dp_relink_deferred) { + tb_dbg(tb, "system sleep over, pairing DP resources\n"); + tcm->dp_tunnel_pending = false; + /* Checks the gate again, and defers again if it is closed. */ + tb_tunnel_dp(tb); + } +unlock: + mutex_unlock(&tb->lock); + + if (ret >= 0) { + pm_runtime_mark_last_busy(&tb->dev); + pm_runtime_put_autosuspend(&tb->dev); + } + tb_domain_put(tb); +} + static void tb_queue_dp_tunnels(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); @@ -3110,6 +3518,10 @@ static void tb_stop(struct tb *tb) tcm->stopping = true; tcm->hotplug_active = false; cancel_delayed_work(&tcm->remove_work); + if (cancel_delayed_work(&tcm->dp_relink_work)) + tb_domain_put(tb); + if (cancel_work(&tcm->dp_relink_run)) + tb_domain_put(tb); list_for_each_entry(tunnel, &tcm->tunnel_list, list) { if (tb_tunnel_is_dp(tunnel)) tb_dp_tunnel_cancel_dprx(tunnel); @@ -3169,6 +3581,11 @@ static void tb_deinit(struct tb *tb) /* Cancel all the release bandwidth workers */ for (i = 0; i < ARRAY_SIZE(tcm->groups); i++) cancel_delayed_work_sync(&tcm->groups[i].release_work); + /* Stopped: nothing queues them again. */ + if (cancel_delayed_work_sync(&tcm->dp_relink_work)) + tb_domain_put(tb); + if (cancel_work_sync(&tcm->dp_relink_run)) + tb_domain_put(tb); } static int tb_scan_finalize_switch(struct device *dev, void *data) @@ -3694,12 +4111,22 @@ static bool tb_apple_add_links(struct tb_nhi *nhi) return ret; } +static void tb_cm_init(struct tb_cm *tcm) +{ + INIT_LIST_HEAD(&tcm->tunnel_list); + INIT_LIST_HEAD(&tcm->dp_resources); + INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); + INIT_WORK(&tcm->dp_tunnel_work, tb_dp_tunnel_work); + INIT_DELAYED_WORK(&tcm->dp_relink_work, tb_dp_relink_work); + INIT_WORK(&tcm->dp_relink_run, tb_dp_relink_run); + tb_init_bandwidth_groups(tcm); +} + struct tb *tb_probe(struct tb_nhi *nhi) { - struct tb_cm *tcm; struct tb *tb; - tb = tb_domain_alloc(nhi, TB_TIMEOUT, sizeof(*tcm)); + tb = tb_domain_alloc(nhi, TB_TIMEOUT, sizeof(struct tb_cm)); if (!tb) return NULL; @@ -3710,12 +4137,7 @@ struct tb *tb_probe(struct tb_nhi *nhi) tb->cm_ops = &tb_cm_ops; - tcm = tb_priv(tb); - INIT_LIST_HEAD(&tcm->tunnel_list); - INIT_LIST_HEAD(&tcm->dp_resources); - INIT_DELAYED_WORK(&tcm->remove_work, tb_remove_work); - INIT_WORK(&tcm->dp_tunnel_work, tb_dp_tunnel_work); - tb_init_bandwidth_groups(tcm); + tb_cm_init(tb_priv(tb)); tb_dbg(tb, "using software connection manager\n"); @@ -3730,3 +4152,34 @@ struct tb *tb_probe(struct tb_nhi *nhi) return tb; } EXPORT_SYMBOL_FOR_MODULES(tb_probe, "thunderbolt_apple"); + +#if IS_ENABLED(CONFIG_USB4_KUNIT_TEST) +/* + * For the KUnit tests: the software connection manager of a domain the test + * sets up itself, with no hardware behind it. The domain is allocated with + * tb_test_cm_size() bytes of private data. + */ +size_t tb_test_cm_size(void) +{ + return sizeof(struct tb_cm); +} + +void tb_test_cm_init(struct tb *tb) +{ + tb_cm_init(tb_priv(tb)); +} + +struct list_head *tb_test_cm_tunnels(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + return &tcm->tunnel_list; +} + +struct list_head *tb_test_cm_dp_resources(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + return &tcm->dp_resources; +} +#endif diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 8bcba8e8434d5..ac1fbdc87ee43 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -764,6 +764,17 @@ static inline int tb_port_write(struct tb_port *port, const void *buffer, struct tb *icm_probe(struct tb_nhi *nhi); struct tb *tb_probe(struct tb_nhi *nhi); +#if IS_ENABLED(CONFIG_KUNIT) +int tb_consumed_dp_bandwidth(struct tb *tb, struct tb_port *src_port, + struct tb_port *dst_port, struct tb_port *port, + int *consumed_up, int *consumed_down); +#endif +#if IS_ENABLED(CONFIG_USB4_KUNIT_TEST) +size_t tb_test_cm_size(void); +void tb_test_cm_init(struct tb *tb); +struct list_head *tb_test_cm_tunnels(struct tb *tb); +struct list_head *tb_test_cm_dp_resources(struct tb *tb); +#endif extern const struct device_type tb_domain_type; extern const struct device_type tb_retimer_type; @@ -772,6 +783,8 @@ extern const struct device_type usb4_port_device_type; int tb_domain_init(void); void tb_domain_exit(void); +void tb_dp_pm_gate_init(void); +void tb_dp_pm_gate_exit(void); int tb_xdomain_init(void); void tb_xdomain_exit(void); diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 745489aef1941..6dcaa4aecbc19 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -7,8 +7,10 @@ */ #include +#include #include #include +#include #include "tb.h" #include "tunnel.h" @@ -3605,8 +3607,8 @@ static void tb_test_apple_right_dpin_wiring(struct kunit *test) } /* - * TB_HOST_DP_HBR2_MAX: a second DP tunnel keeps the rate of the first and - * gives up lanes instead; alone, lanes go before the rate. + * TB_HOST_DP_SHARED_RATE: a second DP tunnel keeps the rate of the first and + * gives up lanes instead; alone at HBR2, lanes go before the rate. */ static void tb_test_dp_reduce_bandwidth_shared(struct kunit *test) { @@ -3646,9 +3648,333 @@ static void tb_test_dp_reduce_bandwidth_shared(struct kunit *test) &rate, &lanes), -ENOSR); } +/* + * TB_HOST_DP_SHARED_RATE: one DP tunnel on the host router may offer HBR3, + * more offer HBR2. Alone, a reduction keeps the most bandwidth; held at + * HBR2 or next to another tunnel, the lanes go first. + */ +static void tb_test_dp_shared_rate_single(struct kunit *test) +{ + u32 rate, lanes; + + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_for(0), 8100); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_for(1), 8100); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_for(2), 5400); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_for(3), 5400); + + /* Alone, the dock's 34650 Mb/s: HBR3 x4 (25920 Mb/s) */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(34650, 8100, 4, 8100, 4, 0, 8100, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 8100); + KUNIT_EXPECT_EQ(test, lanes, 4); + /* Alone, 20000 Mb/s: HBR2 x4 (17280), not HBR3 x2 (12960, too little for 4K60) */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(20000, 8100, 4, 8100, 4, 0, 8100, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 4); + /* Alone, a sink side that takes HBR2 at most: HBR2 x4 */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(34650, 8100, 4, 5400, 4, 0, 8100, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 4); + /* Held at HBR2 for a second tunnel, 10000 Mb/s: HBR2 x2, lanes first */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(10000, 5400, 4, 5400, 4, 0, 5400, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 2); +} + +/* + * TB_HOST_DP_SHARED_RATE: with two DP tunnels the second takes the first + * one's rate, lanes first; a first one up above HBR2 is set up again at HBR2. + */ +static void tb_test_dp_shared_rate_second(struct kunit *test) +{ + u32 rate, lanes; + + /* A second one wanted next to one at HBR3: re-link it now */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(8100, 2, false), TB_DP_RELINK_NOW); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(8100, 3, false), TB_DP_RELINK_NOW); + /* Alone: kept */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(8100, 1, false), TB_DP_RELINK_NONE); + /* At HBR2 or below: kept, the second one takes its rate */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(5400, 2, false), TB_DP_RELINK_NONE); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(2700, 2, false), TB_DP_RELINK_NONE); + + /* + * The first one, held at HBR2 between adapters that take HBR3, is + * counted at HBR2 whatever its DP IN adapter reports: 34650 - 17280 + * left for the second, not 34650 - 25920 (HBR2 x2 at most) + */ + KUNIT_EXPECT_EQ(test, tb_dp_host_counted_bandwidth(5400, 8100, 4), 17280); + KUNIT_EXPECT_EQ(test, tb_dp_host_counted_bandwidth(5400, 5400, 2), 8640); + KUNIT_EXPECT_EQ(test, tb_dp_host_counted_bandwidth(8100, 8100, 4), 25920); + KUNIT_EXPECT_EQ(test, tb_dp_host_counted_bandwidth(0, 8100, 4), 25920); + + /* Two HBR2 x4 tunnels fit the dock: 34650 - 17280 left for the second */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(17370, 5400, 4, 5400, 4, 5400, 5400, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 4); + /* Less room: the second gives up lanes, never its rate */ + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(12000, 5400, 4, 8100, 4, 5400, 5400, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 2); + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(4000, 5400, 4, 5400, 4, 5400, 5400, + &rate, &lanes), -ENOSR); +} + +/* + * TB_HOST_DP_SHARED_RATE: no re-link while system sleep is in progress, from + * the PREPARE event until the transition is over, also when it is refused. + */ +static void tb_test_dp_shared_rate_sleep(struct kunit *test) +{ + struct tb_dp_pm_state s = {}; + + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(8100, 2, true), TB_DP_RELINK_WAIT); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(5400, 2, true), TB_DP_RELINK_NONE); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(8100, 1, true), TB_DP_RELINK_NONE); + + /* Suspend: closed at PREPARE, open only once the transition is over */ + KUNIT_EXPECT_FALSE(test, tb_dp_pm_event(&s, PM_SUSPEND_PREPARE)); + KUNIT_EXPECT_TRUE(test, s.sleeping); + KUNIT_EXPECT_TRUE(test, tb_dp_pm_event(&s, PM_POST_SUSPEND)); + KUNIT_EXPECT_TRUE(test, s.sleeping); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + /* Refused: still closed during the POST replay, until the reopen */ + tb_dp_pm_event(&s, PM_SUSPEND_PREPARE); + KUNIT_EXPECT_TRUE(test, tb_dp_pm_event(&s, PM_POST_SUSPEND)); + KUNIT_EXPECT_TRUE(test, s.sleeping); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + /* No reopen without a POST event: closed until it comes */ + tb_dp_pm_event(&s, PM_SUSPEND_PREPARE); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_TRUE(test, s.sleeping); + tb_dp_pm_event(&s, PM_POST_SUSPEND); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + /* + * The reopen asked for by one transition runs after the next one began + * (the snapshot device drops the lock in between): closed until that + * transition's POST and the reopen after it + */ + tb_dp_pm_event(&s, PM_HIBERNATION_PREPARE); + tb_dp_pm_event(&s, PM_POST_HIBERNATION); + tb_dp_pm_event(&s, PM_HIBERNATION_PREPARE); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_TRUE(test, s.sleeping); + KUNIT_EXPECT_TRUE(test, tb_dp_pm_event(&s, PM_POST_HIBERNATION)); + KUNIT_EXPECT_TRUE(test, s.sleeping); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + /* Two transitions before the reopen runs: one reopen, at the end */ + tb_dp_pm_event(&s, PM_HIBERNATION_PREPARE); + tb_dp_pm_event(&s, PM_POST_HIBERNATION); + tb_dp_pm_event(&s, PM_RESTORE_PREPARE); + tb_dp_pm_event(&s, PM_POST_RESTORE); + KUNIT_EXPECT_TRUE(test, s.sleeping); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + tb_dp_pm_reopen(&s); + KUNIT_EXPECT_FALSE(test, s.sleeping); + /* Other events change nothing */ + tb_dp_pm_event(&s, PM_SUSPEND_PREPARE); + KUNIT_EXPECT_FALSE(test, tb_dp_pm_event(&s, 0)); + KUNIT_EXPECT_TRUE(test, s.sleeping); +} + +/* + * TB_HOST_DP_SHARED_RATE: re-link work that cannot runtime resume the domain + * tries again, a few times and later each time; then the request waits for + * the next resume, thaw or plug event. + */ +static void tb_test_dp_shared_rate_retry(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(0), 2500); + KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(1), 5000); + KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(4), 40000); + KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(5), 0); + KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(100), 0); +} + +/* + * A domain with the software connection manager, a host router whose DP IN + * adapters share one link rate (TB_HOST_DP_SHARED_RATE) and a USB4 device + * router with two DP OUT adapters. Adapter registers (TB_CFG_PORT, from the + * adapter capability) are read from @regs: [0] host, [1] device router. + */ +struct tb_test_cm { + struct tb_nhi nhi; + struct tb *tb; + struct tb_switch *host, *dev; + u32 regs[2][20][16]; +}; + +#define TB_TEST_CM_CAP 0x10 +#define TB_TEST_CM_HBR3_X4 (DP_COMMON_CAP_RATE_HBR3 << DP_COMMON_CAP_RATE_SHIFT | \ + DP_COMMON_CAP_4_LANES << DP_COMMON_CAP_LANES_SHIFT) + +static int tb_test_cm_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, + enum tb_cfg_space space, u32 offset, u32 length) +{ + struct tb_test_cm *cm = kunit_get_current_test()->priv; + u32 (*regs)[16] = cm->regs[route ? 1 : 0]; + + if (space != TB_CFG_PORT || port >= ARRAY_SIZE(cm->regs[0]) || + offset < TB_TEST_CM_CAP || offset - TB_TEST_CM_CAP + length > ARRAY_SIZE(regs[0])) + return -EIO; + memcpy(buffer, ®s[port][offset - TB_TEST_CM_CAP], length * sizeof(u32)); + return 0; +} + +static struct tb_test_cm *tb_test_cm_alloc(struct kunit *test) +{ + struct tb_test_cm *cm; + struct tb *tb; + + cm = kunit_kzalloc(test, sizeof(*cm), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, cm); + tb = kunit_kzalloc(test, sizeof(*tb) + tb_test_cm_size(), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, tb); + cm->nhi.host_dp_policy = TB_HOST_DP_NOTIFY | TB_HOST_DP_SHARED_RATE; + tb->nhi = &cm->nhi; + device_initialize(&tb->dev); + tb->dev.release = tb_test_dprx_release; + mutex_init(&tb->lock); + tb_test_cm_init(tb); + cm->tb = tb; + + cm->host = alloc_host_usb4(test); + KUNIT_ASSERT_NOT_NULL(test, cm->host); + cm->dev = alloc_dev_usb4(test, cm->host, 0x1, true); + KUNIT_ASSERT_NOT_NULL(test, cm->dev); + cm->host->tb = tb; + cm->dev->tb = tb; + cm->host->config.thunderbolt_version = FIELD_PREP(USB4_VERSION_MAJOR_MASK, 1); + cm->dev->config.thunderbolt_version = FIELD_PREP(USB4_VERSION_MAJOR_MASK, 1); + cm->host->ports[5].cap_adap = TB_TEST_CM_CAP; + cm->host->ports[6].cap_adap = TB_TEST_CM_CAP; + cm->dev->ports[13].cap_adap = TB_TEST_CM_CAP; + cm->dev->ports[14].cap_adap = TB_TEST_CM_CAP; + + test->priv = cm; + kunit_activate_static_stub(test, tb_cfg_read, tb_test_cm_cfg_read); + return cm; +} + +/* + * What the DP IN adapter @in reports once its DPRX is done: HBR3 x4, the + * rate both adapters take, in its caps or (@bw_mode) as its allocated + * bandwidth (26000 Mb/s at a granularity of 250 Mb/s). + */ +static void tb_test_cm_reports(struct tb_test_cm *cm, struct tb_port *in, bool bw_mode) +{ + u32 *regs = cm->regs[0][in->port]; + + regs[DP_LOCAL_CAP] = TB_TEST_CM_HBR3_X4; + regs[DP_COMMON_CAP] = TB_TEST_CM_HBR3_X4 | DP_COMMON_CAP_DPRX_DONE; + regs[ADP_DP_CS_2] = 0; + regs[ADP_DP_CS_8] = bw_mode ? ADP_DP_CS_8_DPME : 0; + regs[DP_STATUS] = bw_mode ? 104 << DP_STATUS_ALLOCATED_BW_SHIFT : 0; +} + +/* + * A host DP tunnel from @in to @out as the capabilities exchange leaves it: + * capped at @rate_limit (Mb/s per lane), up at @rate. Listed by the + * connection manager, its adapters enabled. + */ +static struct tb_tunnel *tb_test_cm_dp_tunnel(struct kunit *test, struct tb_test_cm *cm, + struct tb_port *in, struct tb_port *out, + u32 rate_limit, u32 rate) +{ + struct tb_tunnel *tunnel; + int i; + + tunnel = tb_tunnel_alloc_dp(cm->tb, in, out, 1, 0, 0, tb_test_dp_tunnel_active, NULL); + KUNIT_ASSERT_NOT_NULL(test, tunnel); + tunnel->host_rate_limit = rate_limit; + tunnel->max_rate = min(rate_limit, rate); + tunnel->host_rate = rate; + tunnel->state = TB_TUNNEL_ACTIVE; + for (i = 0; i < tunnel->npaths; i++) + tunnel->paths[i]->activated = true; + list_add_tail(&tunnel->list, tb_test_cm_tunnels(cm->tb)); + cm->regs[0][in->port][ADP_DP_CS_0] = ADP_DP_CS_0_VE | ADP_DP_CS_0_AE; + cm->regs[1][out->port][ADP_DP_CS_0] = ADP_DP_CS_0_VE | ADP_DP_CS_0_AE; + return tunnel; +} + +static void tb_test_cm_dp_tunnel_put(struct tb_tunnel *tunnel) +{ + int i; + + list_del(&tunnel->list); + for (i = 0; i < tunnel->npaths; i++) + tunnel->paths[i]->activated = false; + tunnel->state = TB_TUNNEL_INACTIVE; + tb_tunnel_put(tunnel); +} + +/* + * The DP bandwidth the connection manager counts on the host router's lane + * adapter for a new tunnel from host DP IN 6 to device DP OUT 14. + */ +static int tb_test_cm_counted(struct kunit *test, struct tb_test_cm *cm) +{ + int up, down; + + KUNIT_EXPECT_EQ(test, tb_consumed_dp_bandwidth(cm->tb, &cm->host->ports[6], + &cm->dev->ports[14], &cm->host->ports[1], + &up, &down), 0); + KUNIT_EXPECT_EQ(test, up, 0); + return down; +} + +/* + * TB_HOST_DP_SHARED_RATE, through the connection manager's DP bandwidth + * accounting: a tunnel capped at HBR2 whose DP IN adapter reports HBR3 x4 + * once its DPRX is done is counted at HBR2 x4, from its caps and from its + * allocated bandwidth; capped at HBR3, at HBR3 x4. + */ +static void tb_test_dp_shared_rate_counted(struct kunit *test) +{ + struct tb_test_cm *cm = tb_test_cm_alloc(test); + struct tb_port *in = &cm->host->ports[5]; + struct tb_tunnel *a; + + a = tb_test_cm_dp_tunnel(test, cm, in, &cm->dev->ports[13], 5400, 5400); + KUNIT_ASSERT_TRUE(test, tb_tunnel_port_on_path(a, &cm->host->ports[1])); + + /* Caps: HBR2 x4, not the HBR3 x4 reported */ + tb_test_cm_reports(cm, in, false); + KUNIT_EXPECT_EQ(test, tb_test_cm_counted(test, cm), 17280); + a->max_rate = 8100; + KUNIT_EXPECT_EQ(test, tb_test_cm_counted(test, cm), 25920); + + /* Allocated bandwidth: HBR2 x4, not the 26000 Mb/s reported */ + tb_test_cm_reports(cm, in, true); + a->bw_mode = true; + a->max_rate = 5400; + KUNIT_EXPECT_EQ(test, tb_test_cm_counted(test, cm), 17280); + a->max_rate = 8100; + KUNIT_EXPECT_EQ(test, tb_test_cm_counted(test, cm), 25920); + + tb_test_cm_dp_tunnel_put(a); + put_device(&cm->tb->dev); +} + static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_apple_right_dpin_wiring), KUNIT_CASE(tb_test_dp_reduce_bandwidth_shared), + KUNIT_CASE(tb_test_dp_shared_rate_single), + KUNIT_CASE(tb_test_dp_shared_rate_second), + KUNIT_CASE(tb_test_dp_shared_rate_sleep), + KUNIT_CASE(tb_test_dp_shared_rate_retry), + KUNIT_CASE(tb_test_dp_shared_rate_counted), KUNIT_CASE(tb_test_dp_generation), KUNIT_CASE(tb_test_dp_dprx_deferred_first), KUNIT_CASE(tb_test_dp_dprx_cancel), diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index f20b9d4d84a5e..9de9f717f66ae 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -919,7 +919,7 @@ static int tb_dp_reduce_bandwidth(int max_bw, u32 in_rate, u32 in_lanes, /* * As tb_dp_reduce_bandwidth(), for a host whose DP IN streams of a PHY share - * one link rate (TB_HOST_DP_HBR2_MAX): the rate is kept and the lanes are + * one link rate (TB_HOST_DP_SHARED_RATE): the rate is kept and the lanes are * reduced (x4, x2, x1). @shared_rate, the rate of a DP tunnel already up on * the same host router, is the only rate allowed (0: none). Without one, the * highest rate both sides take comes first, then the usual combinations. @@ -948,17 +948,38 @@ int tb_dp_reduce_bandwidth_shared(int max_bw, u32 in_rate, u32 in_lanes, u32 out new_rate, new_lanes); } +/* + * A DP tunnel under TB_HOST_DP_SHARED_RATE that has to be reduced, to fit + * @max_bw or to take @shared_rate. Next to another DP tunnel (@shared_rate), + * or alone but held at HBR2 for one on its way (@rate_limit), the rate is + * kept and the lanes go first, see tb_dp_reduce_bandwidth_shared(). Alone at + * up to HBR3, the combination with the most bandwidth is taken, as without + * the policy: HBR2 x4 rather than HBR3 x2. + */ +int tb_dp_host_reduce_bandwidth(int max_bw, u32 in_rate, u32 in_lanes, u32 out_rate, + u32 out_lanes, u32 shared_rate, u32 rate_limit, + u32 *new_rate, u32 *new_lanes) +{ + if (!shared_rate && rate_limit > TB_DP_HOST_RATE_SHARED) + return tb_dp_reduce_bandwidth(max_bw, in_rate, in_lanes, out_rate, + out_lanes, new_rate, new_lanes); + return tb_dp_reduce_bandwidth_shared(max_bw, in_rate, in_lanes, out_rate, + out_lanes, shared_rate, new_rate, new_lanes); +} + /* * The highest DP link rate (Mb/s per lane) a tunnel from @in offers its - * DPTX, 0 for no cap: HBR2 from the DP IN adapters of a host that needs - * one link rate for both DP IN streams of a PHY (TB_HOST_DP_HBR2_MAX). + * DPTX, 0 for no cap. From the DP IN adapters of a host that needs one link + * rate for both DP IN streams of a PHY (TB_HOST_DP_SHARED_RATE), the one the + * connection manager set: HBR3 alone on the host router, HBR2 next to + * another DP tunnel; HBR2 if it set none. */ static u32 tb_dp_host_rate_cap(struct tb_tunnel *tunnel, struct tb_port *in) { if (!tb_port_is_apple_host_dpin(in) || - !tb_host_dp_policy(tunnel->tb->nhi, TB_HOST_DP_HBR2_MAX)) + !tb_host_dp_policy(tunnel->tb->nhi, TB_HOST_DP_SHARED_RATE)) return 0; - return 5400; + return tunnel->host_rate_limit ?: TB_DP_HOST_RATE_SHARED; } /* @val (a DP_LOCAL_CAP) with its rate capped at the tunnel's cap, if any. */ @@ -982,6 +1003,21 @@ int tb_dp_cap_bandwidth(struct tb_tunnel *tunnel, int bw) return tunnel->max_bw ? min(bw, tunnel->max_bw) : bw; } +/* + * The bandwidth (Mb/s) a DP tunnel capped at @max_rate (Mb/s per lane, 0 + * for no cap) is counted at, from the @rate and @lanes read from its DP IN + * adapter. Under TB_HOST_DP_SHARED_RATE the tunnel never runs above its + * cap, but once its DPRX is done what the adapter reports is not bounded by + * it: a tunnel held at HBR2 between adapters that take HBR3 is counted at + * HBR2 all the same, so that a second one next to it gets what HBR2 leaves. + */ +unsigned int tb_dp_host_counted_bandwidth(u32 max_rate, u32 rate, u32 lanes) +{ + if (max_rate && rate > max_rate) + rate = max_rate; + return tb_dp_bandwidth(rate, lanes); +} + static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) { u32 out_dp_cap, out_rate, out_lanes, in_dp_cap, in_rate, in_lanes, bw; @@ -1065,7 +1101,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) max_bw = tunnel->max_up; /* - * Under TB_HOST_DP_HBR2_MAX a second DP tunnel from the host router + * Under TB_HOST_DP_SHARED_RATE a second DP tunnel from the host router * takes the link rate of the one already up, whatever bandwidth there * is: both DP IN streams of the PHY run at one rate. */ @@ -1077,9 +1113,10 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) u32 new_rate, new_lanes, new_bw; if (tunnel->max_rate) - ret = tb_dp_reduce_bandwidth_shared(max_bw, in_rate, in_lanes, - out_rate, out_lanes, shared_rate, - &new_rate, &new_lanes); + ret = tb_dp_host_reduce_bandwidth(max_bw, in_rate, in_lanes, + out_rate, out_lanes, shared_rate, + tunnel->max_rate, &new_rate, + &new_lanes); else ret = tb_dp_reduce_bandwidth(max_bw, in_rate, in_lanes, out_rate, out_lanes, &new_rate, @@ -1684,6 +1721,9 @@ static int tb_dp_bandwidth_mode_consumed_bandwidth(struct tb_tunnel *tunnel, return ret; if (allocated_bw == max_bw_rounded) allocated_bw = ret; + /* Capped: no more than the capped rate takes, see tb_dp_host_counted_bandwidth(). */ + if (tunnel->max_rate && allocated_bw > ret) + allocated_bw = ret; if (tb_tunnel_direction_downstream(tunnel)) { *consumed_up = 0; @@ -1878,9 +1918,9 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel, int *consumed_up, if (tb_tunnel_direction_downstream(tunnel)) { *consumed_up = 0; - *consumed_down = tb_dp_bandwidth(rate, lanes); + *consumed_down = tb_dp_host_counted_bandwidth(tunnel->max_rate, rate, lanes); } else { - *consumed_up = tb_dp_bandwidth(rate, lanes); + *consumed_up = tb_dp_host_counted_bandwidth(tunnel->max_rate, rate, lanes); *consumed_down = 0; } diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 4cf4e12b77e74..59ecde7decc71 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -109,7 +109,11 @@ struct tb_tunnel { /* host cap of a DP tunnel's link rate (Mb/s per lane, 0 none), and its bandwidth */ u32 max_rate; int max_bw; - /* under TB_HOST_DP_HBR2_MAX: the link rate the DPTX was offered (Mb/s per lane) */ + /* + * Under TB_HOST_DP_SHARED_RATE (Mb/s per lane): the highest link rate + * the connection manager allows, and the one the DPTX was offered. + */ + u32 host_rate_limit; u32 host_rate; bool dprx_started; bool dprx_canceled; @@ -140,9 +144,58 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, void tb_dp_tunnel_cancel_dprx(struct tb_tunnel *tunnel); bool tb_apple_nhi_needs_bw_grant(struct tb_nhi *nhi); int tb_dp_cap_bandwidth(struct tb_tunnel *tunnel, int bw); +unsigned int tb_dp_host_counted_bandwidth(u32 max_rate, u32 rate, u32 lanes); int tb_dp_reduce_bandwidth_shared(int max_bw, u32 in_rate, u32 in_lanes, u32 out_rate, u32 out_lanes, u32 shared_rate, u32 *new_rate, u32 *new_lanes); +int tb_dp_host_reduce_bandwidth(int max_bw, u32 in_rate, u32 in_lanes, u32 out_rate, + u32 out_lanes, u32 shared_rate, u32 rate_limit, + u32 *new_rate, u32 *new_lanes); u32 tb_dp_host_shared_rate(const struct tb_tunnel *tunnel); + +/* TB_HOST_DP_SHARED_RATE link rates (Mb/s per lane) */ +#define TB_DP_HOST_RATE_ALONE 8100 /* HBR3: one DP tunnel on the host router */ +#define TB_DP_HOST_RATE_SHARED 5400 /* HBR2: more than one */ + +/** + * enum tb_dp_relink - Whether a host DP tunnel is set up again at another rate + * @TB_DP_RELINK_NONE: It is not + * @TB_DP_RELINK_WAIT: Not now, it is decided again later + * @TB_DP_RELINK_NOW: It is, now + */ +enum tb_dp_relink { + TB_DP_RELINK_NONE, + TB_DP_RELINK_WAIT, + TB_DP_RELINK_NOW, +}; + +/* + * TB_HOST_DP_SHARED_RATE re-link work (ms): how long it waits to be decided + * again (also while system sleep is in progress), and the first of its + * TB_DP_RELINK_RETRIES retries, doubled each time, when the domain does not + * runtime resume. + */ +#define TB_DP_RELINK_DELAY_MS 2500 +#define TB_DP_RELINK_POLL_MS 500 +#define TB_DP_RELINK_RETRIES 5 + +/** + * struct tb_dp_pm_state - System sleep transitions, for host DP re-links + * @sleeping: From the start of a transition (its PREPARE event, before any + * other notifier) until it is over, its POST callbacks and any + * rollback included (tb_dp_pm_reopen()) + * @reopen: The POST event came: reopen once the transition is over + */ +struct tb_dp_pm_state { + bool sleeping; + bool reopen; +}; + +u32 tb_dp_host_rate_for(unsigned int tunnels); +enum tb_dp_relink tb_dp_relink_down(u32 rate, unsigned int tunnels, bool sleeping); +bool tb_dp_pm_event(struct tb_dp_pm_state *s, unsigned long event); +void tb_dp_pm_reopen(struct tb_dp_pm_state *s); +unsigned int tb_dp_relink_retry_delay(unsigned int tries); + struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, int max_up, int max_down, diff --git a/tools/asahi/tb-dp-repair-tests.py b/tools/asahi/tb-dp-repair-tests.py index 396fdefb5ac04..b6f88796ab275 100644 --- a/tools/asahi/tb-dp-repair-tests.py +++ b/tools/asahi/tb-dp-repair-tests.py @@ -45,7 +45,7 @@ def function(text, name): #define BIT(n) (1UL<<(n)) #define QUIRK_NO_SYSTEM_SLEEP BIT(5) struct tb {int lock;struct device dev;void*wq;struct tb_switch*root_switch;struct tb_nhi*nhi;}; -struct tb_cm {bool hotplug_active;struct work_struct dp_tunnel_work;bool dp_tunnel_pending;}; +struct tb_cm {bool hotplug_active;struct work_struct dp_tunnel_work;bool dp_tunnel_pending;struct work_struct dp_relink_run;}; struct domain {struct tb tb;struct tb_cm cm;}; _Static_assert(offsetof(struct domain,cm)==sizeof(struct tb),"mock private storage adjacency"); #define INIT_WORK(w,handler) ((w)->fn=(handler)) @@ -114,6 +114,8 @@ def function(text, name): static void tb_domain_put(struct tb*t){CHECK(domain_refs>1);domain_refs--;} static bool queue_work(void*wq,struct work_struct*w){if(w->pending)return false;w->pending=true;return true;} static void tb_queue_dp_tunnels(struct tb*tb); +/* The TB_HOST_DP_SHARED_RATE re-link work is not queued in these cases. */ +static void tb_dp_relink_run(struct work_struct*w){CHECK(0);} static void tb_tunnel_dp(struct tb*t){ CHECK(lock_depth==1 && domain.cm.hotplug_active && t->root_switch); alloc_calls++;if(occupied)alloc_failures++;else alloc_successes++; @@ -298,7 +300,7 @@ def main(): raise ValueError('DPRX reference-release order changed; review extraction') production = '\n'.join(parts.values()) probe = (ROOT / 'drivers/thunderbolt/tb.c').read_text() - probe = probe[probe.index('struct tb *tb_probe(struct tb_nhi *nhi)'):] + probe = probe[probe.index('static void tb_cm_init(struct tb_cm *tcm)'):] initializers = re.findall(r'INIT_WORK\(&tcm->\w+,\s*\w+\);', probe) production += '\nstatic void init_fixture(struct tb_cm*tcm){\n' + '\n'.join(initializers) + '\n}\n' results = {} From d279799da84554923af7b643ef7aa5f5dc35db2b Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Thu, 5 Nov 2026 00:08:49 -0500 Subject: [PATCH 2/2] thunderbolt: re-link the host DP tunnel left alone at HBR3 Under TB_HOST_DP_SHARED_RATE two DP tunnels from the host router run at HBR2. When one of them goes because its display or its router was unplugged, the one left stays at HBR2 although it could now run at HBR3. Set it up again at up to HBR3, between the same adapters, once a debounce of 2.5 s is over, from the re-link work on the domain's ordered workqueue (so that DPRX or teardown work queued before it has released its paths): - only after a hot unplug of a DP OUT adapter or a router that took DP tunnels from the host router, and only for the one left alone below the rate both its adapters take. DPRX failures, failed activations and teardown for system sleep never lead to it; - once per unplug: the tunnel set up again is not marked; - dropped when another DP tunnel is up or can be paired by then, so an unplug and replug, or a display that drops and asserts HPD again while it retrains, causes no re-link. Until it is set up again the tunnel keeps its HBR2 cap and is counted at HBR2. When another one is wanted during the debounce, the tunnel is held at HBR2, its estimated bandwidth is worked out again and the debounce is stopped, all before the new one is given any bandwidth, so the new one gets HBR2 x4 next to it; - when a second DP tunnel takes the rate back down within a minute of such a re-link, the next debounce is doubled, up to 40 s; - not during a system sleep transition: tunnels that go then mark nothing, and a re-link that is due waits for the transition to end; a marked tunnel is looked at again at resume and runtime resume; - if the tunnel cannot be set up at the higher rate, it is set up at HBR2 again at once; if it is set up but does not come up (DPRX), it is set up at HBR2 again once its DPRX work is over, with its adapters kept for pairing and no new re-link up. If neither works, pairing runs again, at HBR2, and at the next event. The KUnit tests run the marking and the pairing on such a domain too. The DP repair host test models the connection manager: add the new fields there, with no tunnel re-linked at a higher rate. Signed-off-by: Erik Bourget --- drivers/thunderbolt/tb.c | 325 ++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 5 + drivers/thunderbolt/test.c | 357 ++++++++++++++++++++++++++++++ drivers/thunderbolt/tunnel.c | 6 + drivers/thunderbolt/tunnel.h | 25 ++- tools/asahi/tb-dp-repair-tests.py | 9 +- 6 files changed, 714 insertions(+), 13 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 4f7c55ccb241d..2f512323e44c9 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -75,6 +75,12 @@ MODULE_PARM_DESC(asym_threshold, * waits for system sleep to end (@dp_tunnel_pending is * set with it, for the resume and thaw replays) * @dp_relink_tries: Runtime resume failures of @dp_relink_run in a row + * @dp_relink_due: When the host DP tunnel left alone may be re-linked at a + * higher rate + * @dp_relink_delay: The debounce before that (ms), longer while it flaps + * @dp_relinked_at: When the last such re-link was made + * @dp_host_hold_shared: The next host DP tunnels are set up at HBR2 at + * most: one re-linked at a higher rate did not come up * @groups: Bandwidth groups used in this domain. */ struct tb_cm { @@ -89,6 +95,10 @@ struct tb_cm { struct work_struct dp_relink_run; bool dp_relink_deferred; unsigned int dp_relink_tries; + unsigned long dp_relink_due; + unsigned int dp_relink_delay; + unsigned long dp_relinked_at; + bool dp_host_hold_shared; struct tb_bandwidth_group groups[MAX_GROUPS]; }; @@ -2004,6 +2014,7 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) struct tb_port *in = tunnel->src_port; struct tb_port *out = tunnel->dst_port; struct tb *tb = data; + struct tb_cm *tcm = tb_priv(tb); mutex_lock(&tb->lock); @@ -2060,9 +2071,25 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) * loaded or not all DP cables where connected to the * discrete router. */ - tb_tunnel_warn(tunnel, "not active, tearing down\n"); - tb_dp_resource_unavailable(tb, in, - "DPRX negotiation failed"); + if (tb_dp_relink_activation(tunnel->host_relinked_up, false) == + TB_DP_RELINK_NOW) { + /* + * Set up at a higher rate when it was left alone: set + * it up at HBR2 again, once, from the domain queue + * once this DPRX work has released its paths. The + * adapters stay listed. + */ + tb_tunnel_warn(tunnel, "not active at the higher rate, setting it up again at up to %u Mb/s per lane\n", + TB_DP_HOST_RATE_SHARED); + tb_deactivate_and_free_tunnel(tunnel); + tcm->dp_host_hold_shared = true; + tb_recalc_estimated_bandwidth(tb); + tb_queue_dp_tunnels(tb); + } else { + tb_tunnel_warn(tunnel, "not active, tearing down\n"); + tb_dp_resource_unavailable(tb, in, + "DPRX negotiation failed"); + } } mutex_unlock(&tb->lock); } @@ -2337,6 +2364,84 @@ unsigned int tb_dp_relink_retry_delay(unsigned int tries) return tries < TB_DP_RELINK_RETRIES ? TB_DP_RELINK_DELAY_MS << tries : 0; } +/* + * TB_HOST_DP_SHARED_RATE: as tb_dp_host_rate_for(), HBR2 at most while a + * tunnel re-linked at a higher rate that did not come up is set up again + * (@recovering). + */ +u32 tb_dp_host_rate_after(unsigned int tunnels, bool recovering) +{ + return recovering ? TB_DP_HOST_RATE_SHARED : tb_dp_host_rate_for(tunnels); +} + +/* + * TB_HOST_DP_SHARED_RATE: whether, after a hot unplug took DP tunnels from + * the host router, the one left (@tunnels) up at @rate is marked to be set up + * again at the higher rate both its adapters take (@max_rate, HBR3 at most). + * Nothing is decided while system sleep is in progress (@sleeping). + */ +bool tb_dp_relink_up_wanted(unsigned int tunnels, u32 rate, u32 max_rate, bool sleeping) +{ + return !sleeping && tunnels == 1 && rate < min(max_rate, TB_DP_HOST_RATE_ALONE); +} + +/* + * TB_HOST_DP_SHARED_RATE: whether the host DP tunnel @marked by + * tb_dp_relink_up_wanted() is set up again at the higher rate. Not if + * another one is up or could be paired (@pairable) by now, nor if it got + * that rate already; only after @due, the end of the debounce, and not + * while system sleep is in progress. + */ +enum tb_dp_relink tb_dp_relink_up(bool marked, unsigned int tunnels, u32 rate, u32 max_rate, + bool pairable, bool sleeping, unsigned long now, + unsigned long due) +{ + if (!marked || pairable || !tb_dp_relink_up_wanted(tunnels, rate, max_rate, false)) + return TB_DP_RELINK_NONE; + if (sleeping || time_before(now, due)) + return TB_DP_RELINK_WAIT; + return TB_DP_RELINK_NOW; +} + +/* + * The debounce (ms) before the next re-link to a higher rate: doubled, up + * to TB_DP_RELINK_DELAY_MAX_MS, each time a second DP tunnel takes the rate + * back down soon after such a re-link (@flapping), the base one otherwise. + */ +unsigned int tb_dp_relink_backoff(unsigned int delay_ms, bool flapping) +{ + if (!flapping) + return TB_DP_RELINK_DELAY_MS; + return min(delay_ms * 2, TB_DP_RELINK_DELAY_MAX_MS); +} + +/* + * A host DP tunnel's activation ended, @active or not: NOW if it is set up + * at HBR2 again because it was re-linked at a higher rate (@relinked_up) + * and did not come up. Only once: that tunnel is not re-linked up. + */ +enum tb_dp_relink tb_dp_relink_activation(bool relinked_up, bool active) +{ + return relinked_up && !active ? TB_DP_RELINK_NOW : TB_DP_RELINK_NONE; +} + +/* + * TB_HOST_DP_SHARED_RATE: another host DP tunnel is wanted while @tunnel, + * marked by tb_dp_host_tunnel_gone(), waits for its re-link at a higher + * rate. The re-link is dropped and @tunnel stays capped at HBR2, the rate + * the new one takes next to it, so that it is counted at HBR2 when the + * bandwidth for the new one is worked out. True if a re-link was dropped. + */ +bool tb_dp_host_drop_relink_up(struct tb_tunnel *tunnel) +{ + if (!tunnel->host_relink) + return false; + tunnel->host_relink = false; + tunnel->host_rate_limit = TB_DP_HOST_RATE_SHARED; + tunnel->max_rate = min(tunnel->max_rate, TB_DP_HOST_RATE_SHARED); + return true; +} + static bool tb_dp_host_shares_rate(const struct tb *tb) { return tb->nhi && (tb->nhi->host_dp_policy & TB_HOST_DP_SHARED_RATE); @@ -2388,6 +2493,44 @@ static unsigned int tb_dp_host_free_pairs(struct tb *tb) return min(ins, outs); } +/* A new DP tunnel comes: none is re-linked to a higher rate any more. */ +static void tb_dp_host_clear_relink(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + + list_for_each_entry(tunnel, &tcm->tunnel_list, list) + if (tb_tunnel_is_host_dp(tunnel)) + tunnel->host_relink = false; +} + +/* + * Another host DP tunnel is wanted: a tunnel waiting for its re-link at a + * higher rate is held at HBR2 instead (tb_dp_host_drop_relink_up()) and its + * estimated bandwidth worked out again, before any bandwidth is given to + * the new one. The debounce is stopped unless deferred pairing needs the + * re-link work. Domain lock held. + */ +static void tb_dp_host_cancel_relink_up(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + bool dropped = false; + + list_for_each_entry(tunnel, &tcm->tunnel_list, list) { + if (!tb_tunnel_is_host_dp(tunnel) || !tb_dp_host_drop_relink_up(tunnel)) + continue; + tb_tunnel_info(tunnel, "another DP tunnel is wanted: kept at up to %u Mb/s per lane\n", + TB_DP_HOST_RATE_SHARED); + dropped = true; + } + if (!dropped) + return; + if (!tcm->dp_relink_deferred && cancel_delayed_work(&tcm->dp_relink_work)) + tb_domain_put(tb); + tb_recalc_estimated_bandwidth(tb); +} + /* * Run the re-link work (dp_relink_run) @delay_ms from now, and only once no * system sleep transition is in progress. One domain reference for each @@ -2417,6 +2560,7 @@ static void tb_dp_relink_arm(struct tb *tb, unsigned int delay_ms) static struct tb_tunnel *tb_dp_host_relink(struct tb *tb, struct tb_tunnel *tunnel, u32 rate_limit, const char *why) { + struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *relinked; struct tb_port *in = tunnel->src_port; struct tb_port *out = tunnel->dst_port; @@ -2428,6 +2572,16 @@ static struct tb_tunnel *tb_dp_host_relink(struct tb *tb, struct tb_tunnel *tunn relinked = tb_tunnel_one_dp(tb, in, out, rate_limit); if (relinked) return relinked; + /* Not at the higher rate: at the shared one, as before. */ + if (rate_limit > TB_DP_HOST_RATE_SHARED) { + relinked = tb_tunnel_one_dp(tb, in, out, TB_DP_HOST_RATE_SHARED); + if (relinked) { + tb_port_warn(in, "DP tunnel set up again at up to %u Mb/s per lane\n", + TB_DP_HOST_RATE_SHARED); + return relinked; + } + tcm->dp_host_hold_shared = true; + } /* The adapters stay listed: pairing tries again, at the next event too. */ tb_port_warn(in, "DP tunnel not set up again, pairing again\n"); tb_queue_dp_tunnels(tb); @@ -2444,18 +2598,28 @@ static struct tb_tunnel *tb_dp_host_relink(struct tb *tb, struct tb_tunnel *tunn * adapters are paired once it is over (dp_relink_work), or at the next * resume or thaw. 0 without the policy. Domain lock held. */ -static u32 tb_dp_host_rate_limit(struct tb *tb) +VISIBLE_IF_KUNIT u32 tb_dp_host_rate_limit(struct tb *tb) { struct tb_cm *tcm = tb_priv(tb); struct tb_tunnel *tunnel, *above; + bool admitted, recovering = false; enum tb_dp_relink action; unsigned int up, want; - bool admitted; + unsigned long calm; if (!tb_dp_host_shares_rate(tb)) return 0; up = tb_dp_host_tunnels(tb, NULL); want = up + tb_dp_host_free_pairs(tb); + /* + * Another one comes: the one alone is not re-linked to a higher rate, + * and counts at HBR2 for the new one's bandwidth. + */ + if (want > up) { + tb_dp_host_cancel_relink_up(tb); + recovering = tcm->dp_host_hold_shared; + tcm->dp_host_hold_shared = false; + } /* Each round sets one tunnel up again at HBR2 at most. */ for (;;) { @@ -2472,9 +2636,14 @@ static u32 tb_dp_host_rate_limit(struct tb *tb) admitted = tb_dp_relink_admit(); action = tb_dp_relink_down(above->host_rate, want, !admitted); - if (action == TB_DP_RELINK_NOW) + if (action == TB_DP_RELINK_NOW) { + /* Soon after a re-link up: back off the next one. */ + calm = tcm->dp_relinked_at + msecs_to_jiffies(TB_DP_RELINK_CALM_MS); + tcm->dp_relink_delay = tb_dp_relink_backoff(tcm->dp_relink_delay, + time_before(jiffies, calm)); tb_dp_host_relink(tb, above, TB_DP_HOST_RATE_SHARED, "another DP tunnel is wanted"); + } if (admitted) { tb_dp_relink_done(); tcm->dp_relink_deferred = false; @@ -2487,11 +2656,108 @@ static u32 tb_dp_host_rate_limit(struct tb *tb) /* Replayed by dp_relink_work, and at resume or thaw. */ tcm->dp_relink_deferred = true; tcm->dp_tunnel_pending = true; + tcm->dp_host_hold_shared |= recovering; tb_dp_relink_arm(tb, 0); return 0; } } - return tb_dp_host_rate_for(want); + return tb_dp_host_rate_after(want, recovering); +} + +/* + * TB_HOST_DP_SHARED_RATE: a hot unplug took DP tunnels from the host + * router's DP IN adapters (@before were up). The one left alone below the + * rate it could get is set up again at that rate once the debounce + * (dp_relink_delay) is over, unless another one comes by then, so that an + * unplug and replug or a retraining does not cause a re-link. Until it is + * set up again, it keeps its HBR2 cap and is counted at HBR2. Nothing is + * marked during a system sleep transition. Domain lock held. + */ +VISIBLE_IF_KUNIT void tb_dp_host_tunnel_gone(struct tb *tb, unsigned int before) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel = NULL; + unsigned int up; + + if (!tb_dp_host_shares_rate(tb)) + return; + up = tb_dp_host_tunnels(tb, &tunnel); + if (!tunnel || up >= before || + !tb_dp_relink_up_wanted(up, tunnel->host_rate, tunnel->host_max_rate, + !tb_dp_pm_open())) + return; + + tunnel->host_relink = true; + tcm->dp_relink_due = jiffies + msecs_to_jiffies(tcm->dp_relink_delay); + tb_tunnel_info(tunnel, "alone at %u Mb/s per lane: re-linking at up to %u in %u ms if no other DP tunnel comes\n", + tunnel->host_rate, min(tunnel->host_max_rate, TB_DP_HOST_RATE_ALONE), + tcm->dp_relink_delay); + tb_dp_relink_arm(tb, tcm->dp_relink_delay); +} + +/* A host DP tunnel waits for its re-link to a higher rate. */ +static bool tb_dp_host_relink_marked(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel; + + list_for_each_entry(tunnel, &tcm->tunnel_list, list) + if (tb_tunnel_is_host_dp(tunnel) && tunnel->host_relink) + return true; + return false; +} + +/* + * The debounced part of tb_dp_host_tunnel_gone(), from dp_relink_run on the + * domain queue. Domain lock held. + */ +static void tb_dp_host_relink_up(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + struct tb_tunnel *tunnel = NULL, *relinked; + enum tb_dp_relink action; + unsigned int up; + bool admitted; + long left; + + if (!tb_dp_host_relink_marked(tb)) + return; + up = tb_dp_host_tunnels(tb, &tunnel); + + admitted = tb_dp_relink_admit(); + action = tb_dp_relink_up(tunnel->host_relink, up, tunnel->host_rate, + tunnel->host_max_rate, tb_dp_host_free_pairs(tb) > 0, + !admitted, jiffies, tcm->dp_relink_due); + switch (action) { + case TB_DP_RELINK_NONE: + tb_dbg(tb, "DP tunnel re-link to a higher rate not needed any more\n"); + tb_dp_host_clear_relink(tb); + break; + case TB_DP_RELINK_WAIT: + left = (long)(tcm->dp_relink_due - jiffies); + tb_dp_relink_arm(tb, left > 0 ? jiffies_to_msecs(left) : TB_DP_RELINK_POLL_MS); + break; + case TB_DP_RELINK_NOW: + tunnel->host_relink = false; + tcm->dp_relinked_at = jiffies; + relinked = tb_dp_host_relink(tb, tunnel, TB_DP_HOST_RATE_ALONE, + "alone on the host router"); + /* Set up at HBR2 again if it does not come up at this rate. */ + if (relinked && relinked->host_rate_limit > TB_DP_HOST_RATE_SHARED) + relinked->host_relinked_up = true; + break; + } + if (admitted) + tb_dp_relink_done(); +} + +/* Replays a re-link that waited, at resume and runtime resume. Domain lock held. */ +static void tb_dp_relink_replay(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + if (tcm->dp_relink_deferred || tb_dp_host_relink_marked(tb)) + tb_dp_relink_arm(tb, 0); } static void tb_tunnel_dp(struct tb *tb) @@ -2680,9 +2946,10 @@ static void tb_dp_relink_work(struct work_struct *work) } /* - * Pairing that waited for system sleep to end, see tb_dp_host_rate_limit(). - * On the domain queue: behind DPRX and teardown work queued before it, so a - * tunnel torn down there has released its paths. + * Pairing that waited for system sleep to end, see tb_dp_host_rate_limit(), + * and the debounced re-link of tb_dp_host_tunnel_gone(). On the domain + * queue: behind DPRX and teardown work queued before it, so a tunnel torn + * down there has released its paths. */ static void tb_dp_relink_run(struct work_struct *work) { @@ -2714,6 +2981,8 @@ static void tb_dp_relink_run(struct work_struct *work) /* Checks the gate again, and defers again if it is closed. */ tb_tunnel_dp(tb); } + if (tcm->hotplug_active) + tb_dp_host_relink_up(tb); unlock: mutex_unlock(&tb->lock); @@ -3022,6 +3291,8 @@ static void tb_handle_hotplug(struct work_struct *work) pm_runtime_get_sync(&sw->dev); if (ev->unplug) { + unsigned int dp_up = tb_dp_host_tunnels(tb, NULL); + tb_retimer_remove_all(port); if (tb_port_has_remote(port)) { @@ -3040,6 +3311,7 @@ static void tb_handle_hotplug(struct work_struct *work) /* Maybe we can create another DP tunnel */ tb_recalc_estimated_bandwidth(tb); tb_tunnel_dp(tb); + tb_dp_host_tunnel_gone(tb, dp_up); } else if (port->xdomain) { struct tb_xdomain *xd = tb_xdomain_get(port->xdomain); @@ -3059,6 +3331,9 @@ static void tb_handle_hotplug(struct work_struct *work) tb_port_unconfigure_xdomain(port); } else if (tb_port_is_dpout(port) || tb_port_is_dpin(port)) { tb_dp_resource_unavailable(tb, port, "adapter unplug"); + /* A display went: the DP tunnel left may take a higher rate. */ + if (tb_port_is_dpout(port)) + tb_dp_host_tunnel_gone(tb, dp_up); } else if (!port->port) { tb_sw_dbg(sw, "xHCI disconnect request\n"); tb_switch_xhci_disconnect(sw); @@ -3957,6 +4232,7 @@ static void tb_complete(struct tb *tb) /* A noirq replay may have failed while runtime PM was disabled. */ if (tcm->dp_tunnel_pending) tb_queue_dp_tunnels(tb); + tb_dp_relink_replay(tb); } static int tb_runtime_suspend(struct tb *tb) @@ -4013,6 +4289,7 @@ static int tb_runtime_resume(struct tb *tb) tcm->hotplug_active = true; if (tcm->dp_tunnel_pending) tb_queue_dp_tunnels(tb); + tb_dp_relink_replay(tb); mutex_unlock(&tb->lock); /* @@ -4119,6 +4396,8 @@ static void tb_cm_init(struct tb_cm *tcm) INIT_WORK(&tcm->dp_tunnel_work, tb_dp_tunnel_work); INIT_DELAYED_WORK(&tcm->dp_relink_work, tb_dp_relink_work); INIT_WORK(&tcm->dp_relink_run, tb_dp_relink_run); + tcm->dp_relink_delay = TB_DP_RELINK_DELAY_MS; + tcm->dp_relinked_at = jiffies - msecs_to_jiffies(TB_DP_RELINK_CALM_MS); tb_init_bandwidth_groups(tcm); } @@ -4182,4 +4461,30 @@ struct list_head *tb_test_cm_dp_resources(struct tb *tb) return &tcm->dp_resources; } + +void tb_test_cm_set_relink_deferred(struct tb *tb, bool deferred) +{ + struct tb_cm *tcm = tb_priv(tb); + + tcm->dp_relink_deferred = deferred; +} + +/* The re-link work's timer is armed. */ +bool tb_test_cm_relink_armed(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + return delayed_work_pending(&tcm->dp_relink_work); +} + +/* Stop the re-link work as tb_deinit() does, dropping its domain references. */ +void tb_test_cm_stop(struct tb *tb) +{ + struct tb_cm *tcm = tb_priv(tb); + + if (cancel_delayed_work_sync(&tcm->dp_relink_work)) + tb_domain_put(tb); + if (cancel_work_sync(&tcm->dp_relink_run)) + tb_domain_put(tb); +} #endif diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index ac1fbdc87ee43..841d1ccb27042 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -768,12 +768,17 @@ struct tb *tb_probe(struct tb_nhi *nhi); int tb_consumed_dp_bandwidth(struct tb *tb, struct tb_port *src_port, struct tb_port *dst_port, struct tb_port *port, int *consumed_up, int *consumed_down); +u32 tb_dp_host_rate_limit(struct tb *tb); +void tb_dp_host_tunnel_gone(struct tb *tb, unsigned int before); #endif #if IS_ENABLED(CONFIG_USB4_KUNIT_TEST) size_t tb_test_cm_size(void); void tb_test_cm_init(struct tb *tb); struct list_head *tb_test_cm_tunnels(struct tb *tb); struct list_head *tb_test_cm_dp_resources(struct tb *tb); +void tb_test_cm_set_relink_deferred(struct tb *tb, bool deferred); +bool tb_test_cm_relink_armed(struct tb *tb); +void tb_test_cm_stop(struct tb *tb); #endif extern const struct device_type tb_domain_type; diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 6dcaa4aecbc19..f8418132064af 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -3725,6 +3725,67 @@ static void tb_test_dp_shared_rate_second(struct kunit *test) &rate, &lanes), -ENOSR); } +/* + * TB_HOST_DP_SHARED_RATE: when the other DP tunnel goes, the one left at + * HBR2 is re-linked at HBR3 once the debounce is over, unless another one + * comes by then; once per unplug, and the debounce backs off while it flaps. + */ +static void tb_test_dp_shared_rate_relink_up(struct kunit *test) +{ + const unsigned long due = 1000; + + /* Alone at HBR2, HBR3 possible: marked */ + KUNIT_EXPECT_TRUE(test, tb_dp_relink_up_wanted(1, 5400, 8100, false)); + /* Still two, none left, already at its best, or an HBR2-only sink side: not */ + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(2, 5400, 8100, false)); + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(0, 0, 0, false)); + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(1, 8100, 8100, false)); + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(1, 5400, 5400, false)); + + /* Within the debounce: wait; after it: re-link */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 1, 5400, 8100, false, false, due - 1, due), + TB_DP_RELINK_WAIT); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 1, 5400, 8100, false, false, due, due), + TB_DP_RELINK_NOW); + /* Not marked (the re-linked tunnel itself): once only */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(false, 1, 5400, 8100, false, false, due, due), + TB_DP_RELINK_NONE); + /* Another one came, or could be paired: dropped */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 2, 5400, 8100, false, false, due, due), + TB_DP_RELINK_NONE); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 1, 5400, 8100, true, false, due, due), + TB_DP_RELINK_NONE); + /* At HBR3 by now: nothing to do */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 1, 8100, 8100, false, false, due, due), + TB_DP_RELINK_NONE); + + /* Back-off: doubled while it flaps, at most 40 s; reset when calm */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_backoff(2500, false), 2500); + KUNIT_EXPECT_EQ(test, tb_dp_relink_backoff(2500, true), 5000); + KUNIT_EXPECT_EQ(test, tb_dp_relink_backoff(20000, true), 40000); + KUNIT_EXPECT_EQ(test, tb_dp_relink_backoff(40000, true), 40000); + KUNIT_EXPECT_EQ(test, tb_dp_relink_backoff(40000, false), 2500); +} + +/* + * TB_HOST_DP_SHARED_RATE: a tunnel re-linked at HBR3 that does not come up + * is set up again at HBR2, once and without a new re-link up; any other + * tunnel that does not come up is torn down as before. + */ +static void tb_test_dp_shared_rate_recover(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, tb_dp_relink_activation(true, false), TB_DP_RELINK_NOW); + KUNIT_EXPECT_EQ(test, tb_dp_relink_activation(true, true), TB_DP_RELINK_NONE); + KUNIT_EXPECT_EQ(test, tb_dp_relink_activation(false, false), TB_DP_RELINK_NONE); + /* The recovery: HBR2 at most even alone; afterwards HBR3 alone again */ + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_after(1, true), 5400); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_after(1, false), 8100); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_after(2, false), 5400); + /* The tunnel set up so (no re-link mark) is never re-linked up again */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(false, 1, 5400, 8100, false, false, 1000, 1000), + TB_DP_RELINK_NONE); +} + /* * TB_HOST_DP_SHARED_RATE: no re-link while system sleep is in progress, from * the PREPARE event until the transition is over, also when it is refused. @@ -3785,6 +3846,12 @@ static void tb_test_dp_shared_rate_sleep(struct kunit *test) tb_dp_pm_event(&s, PM_SUSPEND_PREPARE); KUNIT_EXPECT_FALSE(test, tb_dp_pm_event(&s, 0)); KUNIT_EXPECT_TRUE(test, s.sleeping); + + /* Tunnels going during the sleep mark nothing */ + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(1, 5400, 8100, true)); + /* A re-link to HBR3 due during the sleep waits for its end */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(true, 1, 5400, 8100, false, true, 1001, 1000), + TB_DP_RELINK_WAIT); } /* @@ -3801,6 +3868,199 @@ static void tb_test_dp_shared_rate_retry(struct kunit *test) KUNIT_EXPECT_EQ(test, tb_dp_relink_retry_delay(100), 0); } +/* The dock's link: what is left there for DP tunnels (Mb/s) */ +#define TB_TEST_DOCK_DP_BW 34650 + +/* + * Set @t up as pairing and the capabilities exchange do under + * TB_HOST_DP_SHARED_RATE, between adapters that take HBR3 x4: capped at + * @rate_limit, next to a tunnel at @shared_rate (0: alone), with @room Mb/s + * left on the link. The lanes it takes in @lanes. + */ +static void tb_test_host_dp_up(struct kunit *test, struct tb_tunnel *t, u32 rate_limit, + u32 shared_rate, int room, u32 *lanes) +{ + u32 rate; + int ret; + + memset(t, 0, sizeof(*t)); + t->host_rate_limit = rate_limit; + t->max_rate = rate_limit; + t->host_max_rate = TB_DP_HOST_RATE_ALONE; + ret = tb_dp_host_reduce_bandwidth(room, rate_limit, 4, rate_limit, 4, shared_rate, + rate_limit, &rate, lanes); + KUNIT_ASSERT_EQ(test, ret, 0); + t->max_rate = min(t->max_rate, rate); + t->host_rate = rate; +} + +/* + * What @t, up at @lanes, leaves on the dock's link for another DP tunnel, + * its DP IN adapter reporting the rate both adapters take (HBR3). + */ +static int tb_test_host_dp_room(const struct tb_tunnel *t, u32 lanes) +{ + return TB_TEST_DOCK_DP_BW - + tb_dp_host_counted_bandwidth(t->max_rate, TB_DP_HOST_RATE_ALONE, lanes); +} + +/* + * A at HBR2 x4 next to B: B is wanted while A is up at HBR3, so A is set up + * again at HBR2 first, alone on the link, then B takes its rate. + */ +static void tb_test_host_dp_two_up(struct kunit *test, struct tb_tunnel *a, u32 *a_lanes, + struct tb_tunnel *b, u32 *b_lanes) +{ + tb_test_host_dp_up(test, a, tb_dp_host_rate_for(1), 0, TB_TEST_DOCK_DP_BW, a_lanes); + KUNIT_ASSERT_EQ(test, tb_dp_relink_down(a->host_rate, 2, false), TB_DP_RELINK_NOW); + tb_test_host_dp_up(test, a, tb_dp_host_rate_for(2), 0, TB_TEST_DOCK_DP_BW, a_lanes); + tb_test_host_dp_up(test, b, tb_dp_host_rate_for(2), a->host_rate, + tb_test_host_dp_room(a, *a_lanes), b_lanes); +} + +/* TB_HOST_DP_SHARED_RATE, one display: HBR3 x4, counted so, nothing to re-link. */ +static void tb_test_dp_shared_rate_lone(struct kunit *test) +{ + struct tb_tunnel *a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + u32 lanes; + + KUNIT_ASSERT_NOT_NULL(test, a); + tb_test_host_dp_up(test, a, tb_dp_host_rate_for(1), 0, TB_TEST_DOCK_DP_BW, &lanes); + KUNIT_EXPECT_EQ(test, a->host_rate, 8100); + KUNIT_EXPECT_EQ(test, lanes, 4); + KUNIT_EXPECT_EQ(test, tb_test_host_dp_room(a, lanes), 34650 - 25920); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(a->host_rate, 1, false), TB_DP_RELINK_NONE); + KUNIT_EXPECT_FALSE(test, tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false)); +} + +/* TB_HOST_DP_SHARED_RATE, a second display comes: both HBR2 x4. */ +static void tb_test_dp_shared_rate_two(struct kunit *test) +{ + struct tb_tunnel *a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + struct tb_tunnel *b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + u32 a_lanes, b_lanes; + + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); + tb_test_host_dp_two_up(test, a, &a_lanes, b, &b_lanes); + KUNIT_EXPECT_EQ(test, a->host_rate, 5400); + KUNIT_EXPECT_EQ(test, a_lanes, 4); + KUNIT_EXPECT_EQ(test, b->host_rate, 5400); + KUNIT_EXPECT_EQ(test, b_lanes, 4); + /* At HBR2 both: neither is set up again */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(a->host_rate, 2, false), TB_DP_RELINK_NONE); + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(b->host_rate, 2, false), TB_DP_RELINK_NONE); +} + +/* + * TB_HOST_DP_SHARED_RATE, the second display goes: the one left is marked, + * stays capped and counted at HBR2 through the debounce, and is set up again + * at HBR3 x4 once it is over. + */ +static void tb_test_dp_shared_rate_leave(struct kunit *test) +{ + struct tb_tunnel *a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + struct tb_tunnel *b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + const unsigned long due = 1000 + TB_DP_RELINK_DELAY_MS; + u32 a_lanes, b_lanes; + + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); + tb_test_host_dp_two_up(test, a, &a_lanes, b, &b_lanes); + + /* B goes at 1000 */ + KUNIT_ASSERT_TRUE(test, tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false)); + a->host_relink = true; + /* Waiting: still capped at HBR2 and counted so */ + KUNIT_EXPECT_EQ(test, a->max_rate, 5400); + KUNIT_EXPECT_EQ(test, tb_test_host_dp_room(a, a_lanes), 34650 - 17280); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due - 1, due), TB_DP_RELINK_WAIT); + + /* Debounce over: set up again, alone, at HBR3 x4 and counted so */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due, due), TB_DP_RELINK_NOW); + tb_test_host_dp_up(test, a, TB_DP_HOST_RATE_ALONE, 0, TB_TEST_DOCK_DP_BW, &a_lanes); + KUNIT_EXPECT_EQ(test, a->host_rate, 8100); + KUNIT_EXPECT_EQ(test, a_lanes, 4); + KUNIT_EXPECT_EQ(test, tb_test_host_dp_room(a, a_lanes), 34650 - 25920); + /* Once: the tunnel set up again is not marked */ + KUNIT_EXPECT_FALSE(test, a->host_relink); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due + 1, due), TB_DP_RELINK_NONE); + KUNIT_EXPECT_FALSE(test, tb_dp_host_drop_relink_up(a)); +} + +/* + * TB_HOST_DP_SHARED_RATE, a second display that comes and goes quickly: no + * re-link while it flaps within the debounce, the debounce starts again at + * each unplug, and a second display soon after a re-link up doubles it. + */ +static void tb_test_dp_shared_rate_flap(struct kunit *test) +{ + struct tb_tunnel *a = kunit_kzalloc(test, sizeof(*a), GFP_KERNEL); + struct tb_tunnel *b = kunit_kzalloc(test, sizeof(*b), GFP_KERNEL); + unsigned int delay = TB_DP_RELINK_DELAY_MS; + u32 a_lanes, b_lanes; + unsigned long due; + + KUNIT_ASSERT_NOT_NULL(test, a); + KUNIT_ASSERT_NOT_NULL(test, b); + tb_test_host_dp_two_up(test, a, &a_lanes, b, &b_lanes); + + /* B goes at 1000 */ + a->host_relink = tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false); + KUNIT_ASSERT_TRUE(test, a->host_relink); + due = 1000 + delay; + /* ... is back at 1500: A held at HBR2, B HBR2 x4 */ + KUNIT_EXPECT_TRUE(test, tb_dp_host_drop_relink_up(a)); + tb_test_host_dp_up(test, b, tb_dp_host_rate_for(2), a->host_rate, + tb_test_host_dp_room(a, a_lanes), &b_lanes); + KUNIT_EXPECT_EQ(test, b->host_rate, 5400); + KUNIT_EXPECT_EQ(test, b_lanes, 4); + /* ... goes again at 2000: a new debounce, the first one's end changes nothing */ + a->host_relink = tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false); + KUNIT_ASSERT_TRUE(test, a->host_relink); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due, 2000 + delay), TB_DP_RELINK_WAIT); + due = 2000 + delay; + /* ... is back at 3000, gone again at 3200 */ + KUNIT_EXPECT_TRUE(test, tb_dp_host_drop_relink_up(a)); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 2, a->host_rate, a->host_max_rate, + false, false, due, due), TB_DP_RELINK_NONE); + a->host_relink = tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false); + due = 3200 + delay; + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due - 1, due), TB_DP_RELINK_WAIT); + + /* Calm until the debounce is over: A set up again at HBR3 x4 */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due, due), TB_DP_RELINK_NOW); + tb_test_host_dp_up(test, a, TB_DP_HOST_RATE_ALONE, 0, TB_TEST_DOCK_DP_BW, &a_lanes); + KUNIT_EXPECT_EQ(test, a->host_rate, 8100); + + /* B is back within a minute: A set up again at HBR2, the next debounce doubled */ + KUNIT_EXPECT_EQ(test, tb_dp_relink_down(a->host_rate, 2, false), TB_DP_RELINK_NOW); + delay = tb_dp_relink_backoff(delay, true); + KUNIT_EXPECT_EQ(test, delay, 5000); + tb_test_host_dp_up(test, a, tb_dp_host_rate_for(2), 0, TB_TEST_DOCK_DP_BW, &a_lanes); + tb_test_host_dp_up(test, b, tb_dp_host_rate_for(2), a->host_rate, + tb_test_host_dp_room(a, a_lanes), &b_lanes); + KUNIT_EXPECT_EQ(test, a->host_rate, 5400); + KUNIT_EXPECT_EQ(test, a_lanes, 4); + KUNIT_EXPECT_EQ(test, b->host_rate, 5400); + KUNIT_EXPECT_EQ(test, b_lanes, 4); + + /* B goes once more at 10000: HBR3 again only after the doubled debounce */ + a->host_relink = tb_dp_relink_up_wanted(1, a->host_rate, a->host_max_rate, false); + due = 10000 + delay; + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, 10000 + TB_DP_RELINK_DELAY_MS, due), + TB_DP_RELINK_WAIT); + KUNIT_EXPECT_EQ(test, tb_dp_relink_up(a->host_relink, 1, a->host_rate, a->host_max_rate, + false, false, due, due), TB_DP_RELINK_NOW); +} + /* * A domain with the software connection manager, a host router whose DP IN * adapters share one link rate (TB_HOST_DP_SHARED_RATE) and a USB4 device @@ -3967,11 +4227,108 @@ static void tb_test_dp_shared_rate_counted(struct kunit *test) put_device(&cm->tb->dev); } +/* + * TB_HOST_DP_SHARED_RATE, through the connection manager: A (host DP IN 5 to + * device DP OUT 13) is left alone at HBR2 when B (DP IN 6 to DP OUT 14) goes, + * and C is plugged into DP OUT 14 during the debounce. Pairing drops A's + * re-link up and holds A at HBR2 before C's bandwidth is worked out, and the + * debounce timer is stopped unless deferred pairing (@deferred) needs the + * re-link work. A's DP IN adapter reports HBR3 x4, in its caps or (@bw_mode) + * as its allocated bandwidth; it is counted at HBR2 x4 and C gets HBR2 x4. + */ +static void tb_test_dp_shared_rate_arrival(struct kunit *test, bool bw_mode, bool deferred) +{ + struct tb_test_cm *cm = tb_test_cm_alloc(test); + struct list_head *resources = tb_test_cm_dp_resources(cm->tb); + struct tb_port *in = &cm->host->ports[5]; + struct tb_tunnel *a; + u32 rate, lanes; + int counted; + + /* + * The debounce timer is real: should it expire before the end, its + * work runs on this queue and stops at the missing root switch. + */ + cm->tb->wq = alloc_ordered_workqueue("tb-cm-test", 0); + KUNIT_ASSERT_NOT_NULL(test, cm->tb->wq); + + a = tb_test_cm_dp_tunnel(test, cm, in, &cm->dev->ports[13], 5400, 5400); + a->host_max_rate = 8100; + a->bw_mode = bw_mode; + tb_test_cm_reports(cm, in, bw_mode); + list_add_tail(&cm->host->ports[5].list, resources); + list_add_tail(&cm->host->ports[6].list, resources); + list_add_tail(&cm->dev->ports[13].list, resources); + + mutex_lock(&cm->tb->lock); + /* B went (two were up): A is marked, the debounce armed */ + tb_dp_host_tunnel_gone(cm->tb, 2); + KUNIT_EXPECT_TRUE(test, a->host_relink); + KUNIT_EXPECT_TRUE(test, tb_test_cm_relink_armed(cm->tb)); + KUNIT_EXPECT_EQ(test, kref_read(&cm->tb->dev.kobj.kref), 2U); + tb_test_cm_set_relink_deferred(cm->tb, deferred); + + /* C comes on DP OUT 14: pairing takes HBR2 for it, A's re-link dropped */ + list_add_tail(&cm->dev->ports[14].list, resources); + KUNIT_EXPECT_EQ(test, tb_dp_host_rate_limit(cm->tb), 5400U); + KUNIT_EXPECT_FALSE(test, a->host_relink); + KUNIT_EXPECT_EQ(test, a->host_rate_limit, 5400U); + KUNIT_EXPECT_EQ(test, a->max_rate, 5400U); + KUNIT_EXPECT_EQ(test, tb_test_cm_relink_armed(cm->tb), deferred); + KUNIT_EXPECT_EQ(test, kref_read(&cm->tb->dev.kobj.kref), deferred ? 2U : 1U); + + /* C's bandwidth: A counted at HBR2 x4, C at A's rate gets x4 */ + counted = tb_test_cm_counted(test, cm); + KUNIT_EXPECT_EQ(test, counted, 17280); + KUNIT_EXPECT_EQ(test, tb_dp_host_reduce_bandwidth(TB_TEST_DOCK_DP_BW - counted, 5400, 4, + 5400, 4, a->host_rate, 5400, + &rate, &lanes), 0); + KUNIT_EXPECT_EQ(test, rate, 5400); + KUNIT_EXPECT_EQ(test, lanes, 4); + mutex_unlock(&cm->tb->lock); + + /* Outside the domain lock, which the work takes: drain it, then the queue */ + tb_test_cm_stop(cm->tb); + destroy_workqueue(cm->tb->wq); + KUNIT_EXPECT_EQ(test, kref_read(&cm->tb->dev.kobj.kref), 1U); + INIT_LIST_HEAD(resources); + tb_test_cm_dp_tunnel_put(a); + put_device(&cm->tb->dev); +} + +/* + * TB_HOST_DP_SHARED_RATE, a second display comes during the debounce: it + * gets HBR2 x4 and the debounce timer is stopped, whether the tunnel left + * alone is counted from its caps or from its allocated bandwidth. + */ +static void tb_test_dp_shared_rate_debounce(struct kunit *test) +{ + tb_test_dp_shared_rate_arrival(test, false, false); + tb_test_dp_shared_rate_arrival(test, true, false); +} + +/* + * As tb_test_dp_shared_rate_debounce(), with pairing deferred for a system + * sleep transition: the re-link up is dropped, the re-link work kept armed. + */ +static void tb_test_dp_shared_rate_debounce_deferred(struct kunit *test) +{ + tb_test_dp_shared_rate_arrival(test, false, true); +} + static struct kunit_case tb_test_cases[] = { KUNIT_CASE(tb_test_apple_right_dpin_wiring), KUNIT_CASE(tb_test_dp_reduce_bandwidth_shared), KUNIT_CASE(tb_test_dp_shared_rate_single), KUNIT_CASE(tb_test_dp_shared_rate_second), + KUNIT_CASE(tb_test_dp_shared_rate_relink_up), + KUNIT_CASE(tb_test_dp_shared_rate_recover), + KUNIT_CASE(tb_test_dp_shared_rate_lone), + KUNIT_CASE(tb_test_dp_shared_rate_two), + KUNIT_CASE(tb_test_dp_shared_rate_leave), + KUNIT_CASE(tb_test_dp_shared_rate_debounce), + KUNIT_CASE(tb_test_dp_shared_rate_debounce_deferred), + KUNIT_CASE(tb_test_dp_shared_rate_flap), KUNIT_CASE(tb_test_dp_shared_rate_sleep), KUNIT_CASE(tb_test_dp_shared_rate_retry), KUNIT_CASE(tb_test_dp_shared_rate_counted), diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 9de9f717f66ae..947a297f4d09e 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1030,6 +1030,7 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) tunnel->max_rate = tb_dp_host_rate_cap(tunnel, in); tunnel->max_bw = 0; tunnel->host_rate = 0; + tunnel->host_max_rate = 0; /* * Copy DP_LOCAL_CAP register to DP_REMOTE_CAP register for @@ -1057,6 +1058,11 @@ static int tb_dp_xchg_caps(struct tb_tunnel *tunnel) if (ret) return ret; + /* What the tunnel could get alone on the host router: HBR3 at most. */ + if (tunnel->max_rate) + tunnel->host_max_rate = min(tb_dp_cap_get_rate(in_dp_cap), + tb_dp_cap_get_rate(out_dp_cap)); + /* * Both sides see the capped rate: the DP OUT through its remote caps, * the DPTX through the DP IN's. diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 59ecde7decc71..fb5114bbc7563 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -111,10 +111,17 @@ struct tb_tunnel { int max_bw; /* * Under TB_HOST_DP_SHARED_RATE (Mb/s per lane): the highest link rate - * the connection manager allows, and the one the DPTX was offered. + * the connection manager allows, the one the DPTX was offered, and the + * one both adapters take without the policy. @host_relink: to be set + * up again at a higher rate once the debounce is over (alone now); + * @host_relinked_up: set up so, and set up at HBR2 again if it does + * not come up. */ u32 host_rate_limit; u32 host_rate; + u32 host_max_rate; + bool host_relink; + bool host_relinked_up; bool dprx_started; bool dprx_canceled; u64 host_dp_generation; @@ -178,6 +185,14 @@ enum tb_dp_relink { #define TB_DP_RELINK_POLL_MS 500 #define TB_DP_RELINK_RETRIES 5 +/* + * A re-link to a higher rate waits TB_DP_RELINK_DELAY_MS (the debounce), + * doubled up to TB_DP_RELINK_DELAY_MAX_MS while a second DP tunnel takes + * the rate back down within TB_DP_RELINK_CALM_MS of such a re-link. + */ +#define TB_DP_RELINK_DELAY_MAX_MS 40000 +#define TB_DP_RELINK_CALM_MS 60000 + /** * struct tb_dp_pm_state - System sleep transitions, for host DP re-links * @sleeping: From the start of a transition (its PREPARE event, before any @@ -195,6 +210,14 @@ enum tb_dp_relink tb_dp_relink_down(u32 rate, unsigned int tunnels, bool sleepin bool tb_dp_pm_event(struct tb_dp_pm_state *s, unsigned long event); void tb_dp_pm_reopen(struct tb_dp_pm_state *s); unsigned int tb_dp_relink_retry_delay(unsigned int tries); +u32 tb_dp_host_rate_after(unsigned int tunnels, bool recovering); +bool tb_dp_relink_up_wanted(unsigned int tunnels, u32 rate, u32 max_rate, bool sleeping); +enum tb_dp_relink tb_dp_relink_up(bool marked, unsigned int tunnels, u32 rate, u32 max_rate, + bool pairable, bool sleeping, unsigned long now, + unsigned long due); +unsigned int tb_dp_relink_backoff(unsigned int delay_ms, bool flapping); +enum tb_dp_relink tb_dp_relink_activation(bool relinked_up, bool active); +bool tb_dp_host_drop_relink_up(struct tb_tunnel *tunnel); struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_port *out, int link_nr, diff --git a/tools/asahi/tb-dp-repair-tests.py b/tools/asahi/tb-dp-repair-tests.py index b6f88796ab275..be775d7618095 100644 --- a/tools/asahi/tb-dp-repair-tests.py +++ b/tools/asahi/tb-dp-repair-tests.py @@ -45,14 +45,14 @@ def function(text, name): #define BIT(n) (1UL<<(n)) #define QUIRK_NO_SYSTEM_SLEEP BIT(5) struct tb {int lock;struct device dev;void*wq;struct tb_switch*root_switch;struct tb_nhi*nhi;}; -struct tb_cm {bool hotplug_active;struct work_struct dp_tunnel_work;bool dp_tunnel_pending;struct work_struct dp_relink_run;}; +struct tb_cm {bool hotplug_active;struct work_struct dp_tunnel_work;bool dp_tunnel_pending;struct work_struct dp_relink_run;bool dp_host_hold_shared;}; struct domain {struct tb tb;struct tb_cm cm;}; _Static_assert(offsetof(struct domain,cm)==sizeof(struct tb),"mock private storage adjacency"); #define INIT_WORK(w,handler) ((w)->fn=(handler)) struct kref {int count;}; struct tb_path {bool reserved;}; enum {TB_TUNNEL_DP,TB_TUNNEL_USB3}; -struct tb_tunnel {struct kref kref;struct tb*tb;struct tb_port*src_port,*dst_port;int npaths;void(*destroy)(struct tb_tunnel*);struct list_head list;int type;bool dprx_started,dprx_canceled;void(*callback)(struct tb_tunnel*,void*);void*callback_data;struct tb_path*paths[3];}; +struct tb_tunnel {struct kref kref;struct tb*tb;struct tb_port*src_port,*dst_port;int npaths;void(*destroy)(struct tb_tunnel*);struct list_head list;int type;bool dprx_started,dprx_canceled,host_relinked_up;void(*callback)(struct tb_tunnel*,void*);void*callback_data;struct tb_path*paths[3];}; static int tb_tunnel_lock,occupied,alloc_calls,alloc_failures,alloc_successes,freed; static int domain_refs,pm_refs,pm_calls,pm_error,stop_during_pm,requeue_once,lock_depth; static int restore_calls,scan_calls,unplugged_xdomains; @@ -116,6 +116,11 @@ def function(text, name): static void tb_queue_dp_tunnels(struct tb*tb); /* The TB_HOST_DP_SHARED_RATE re-link work is not queued in these cases. */ static void tb_dp_relink_run(struct work_struct*w){CHECK(0);} +/* No tunnel here was re-linked at a higher rate, and none waits for it. */ +#define TB_DP_HOST_RATE_SHARED 5400 +enum tb_dp_relink {TB_DP_RELINK_NONE,TB_DP_RELINK_WAIT,TB_DP_RELINK_NOW}; +static enum tb_dp_relink tb_dp_relink_activation(bool relinked_up,bool active){CHECK(!relinked_up);return TB_DP_RELINK_NONE;} +static void tb_dp_relink_replay(struct tb*t){CHECK(lock_depth==1);} static void tb_tunnel_dp(struct tb*t){ CHECK(lock_depth==1 && domain.cm.hotplug_active && t->root_switch); alloc_calls++;if(occupied)alloc_failures++;else alloc_successes++;