From 032c59c7681c661b63123231824170c025afb7e7 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:33 +0200 Subject: [PATCH 01/63] thunderbolt: Hold a router reference for each allocated HopID tb_stop() drops the reference to all DP tunnels but does not deactivate them, thus nothing cancels a dprx_work still in flight (which holds its own tunnel reference) and the tunnel can outlive tb_switch_remove(). The HopID releases in tb_path_free() then operate on freed IDAs and trigger warnings like ida_free called for id=8 which is not allocated. This can be triggered by unbinding the driver while a DP tunnel is still waiting for the DPRX capabilities read to finish. On the Apple NHI unplugging the cable runs into just that reliably because the read can never finish right now and because the unplug powers down the entire USB4 complex and removes the NHI device. Take a router reference whenever an input or output HopID is allocated and drop it again after the HopID is released. This keeps the ports and their HopID IDAs alive for as long as they are used. The KUnit tests allocate routers without ever registering their devices so initialize the embedded struct device there as well and drop its initial reference when the test is finished. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/switch.c | 9 ++++++++- drivers/thunderbolt/test.c | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 404c0693df50..cf571a7c98d0 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -774,6 +774,7 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, { int port_max_hopid; struct ida *ida; + int ret; if (in) { port_max_hopid = port->config.max_in_hop_id; @@ -793,7 +794,11 @@ static int tb_port_alloc_hopid(struct tb_port *port, bool in, int min_hopid, if (max_hopid < 0 || max_hopid > port_max_hopid) max_hopid = port_max_hopid; - return ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + ret = ida_alloc_range(ida, min_hopid, max_hopid, GFP_KERNEL); + if (ret >= 0) + tb_switch_get(port->sw); + + return ret; } /** @@ -832,6 +837,7 @@ int tb_port_alloc_out_hopid(struct tb_port *port, int min_hopid, int max_hopid) void tb_port_release_in_hopid(struct tb_port *port, int hopid) { ida_free(&port->in_hopids, hopid); + tb_switch_put(port->sw); } /** @@ -842,6 +848,7 @@ void tb_port_release_in_hopid(struct tb_port *port, int hopid) void tb_port_release_out_hopid(struct tb_port *port, int hopid) { ida_free(&port->out_hopids, hopid); + tb_switch_put(port->sw); } static inline bool tb_switch_is_reachable(const struct tb_switch *parent, diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 05652ee82fbf..799ada97cbc9 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -33,6 +33,18 @@ static void kunit_ida_init(struct kunit *test, struct ida *ida) kunit_alloc_resource(test, __ida_init, __ida_destroy, GFP_KERNEL, ida); } +static void tb_test_switch_release(struct device *dev) +{ + /* The memory is owned by KUnit, nothing to do here */ +} + +static void tb_test_switch_put(void *data) +{ + struct tb_switch *sw = data; + + put_device(&sw->dev); +} + static struct tb_switch *alloc_switch(struct kunit *test, u64 route, u8 upstream_port, u8 max_port_number) { @@ -44,6 +56,15 @@ static struct tb_switch *alloc_switch(struct kunit *test, u64 route, if (!sw) return NULL; + /* + * HopID allocations take a reference to their routers and those devices + * have to be initialized for that to work. + */ + sw->dev.release = tb_test_switch_release; + device_initialize(&sw->dev); + if (kunit_add_action_or_reset(test, tb_test_switch_put, sw)) + return NULL; + sw->config.upstream_port_number = upstream_port; sw->config.depth = tb_route_length(route); sw->config.route_hi = upper_32_bits(route); From 12f5d8b85a66a0ac08d082517d92ce136f8c0d42 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:34 +0200 Subject: [PATCH 02/63] thunderbolt: Make the DP tunnel activation callback mandatory tb_tunnel_alloc_dp() takes an optional callback which is run from dprx_work once the DPRX capabilities read has completed. Without that callback tb_dp_dprx_start() reads the capabilities synchronously and never queues the work. It however always takes a tunnel reference which is only dropped by dprx_work itself or by tb_dp_dprx_stop() when cancel_delayed_work() actually canceled that work. That reference is thus leaked for every tunnel without a callback. The only tunnels without one are those from tb_tunnel_discover_dp(), which are activated again when restoring from hibernation. Pass the callback to tb_tunnel_discover_dp() as well and drop the synchronous path such that the DPRX capabilities are always read from dprx_work. Hibernation restore then also no longer blocks for up to 12 seconds while waiting for that read to complete. Also fix up the KUnit tests. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 4 ++- drivers/thunderbolt/test.c | 37 +++++++++++++++++++--------- drivers/thunderbolt/tunnel.c | 47 ++++++++++++++++++++---------------- drivers/thunderbolt/tunnel.h | 8 +++--- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 47753a5c0f2e..20f0f8f4bdf7 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -89,6 +89,7 @@ static void tb_dp_resource_unavailable(struct tb *tb, struct tb_port *port, const char *reason); 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_hotplug(struct tb *tb, u64 route, u8 port, bool unplug) { @@ -385,7 +386,8 @@ static void tb_switch_discover_tunnels(struct tb_switch *sw, switch (port->config.type) { case TB_TYPE_DP_HDMI_IN: - tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids); + tunnel = tb_tunnel_discover_dp(tb, port, alloc_hopids, + tb_dp_tunnel_active, tb); tb_increase_tmu_accuracy(tunnel); break; diff --git a/drivers/thunderbolt/test.c b/drivers/thunderbolt/test.c index 799ada97cbc9..c8c648f31107 100644 --- a/drivers/thunderbolt/test.c +++ b/drivers/thunderbolt/test.c @@ -1407,6 +1407,10 @@ static void tb_test_tunnel_pcie(struct kunit *test) tb_tunnel_put(tunnel1); } +static void tb_test_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) +{ +} + static void tb_test_tunnel_dp(struct kunit *test) { struct tb_switch *host, *dev; @@ -1427,7 +1431,8 @@ static void tb_test_tunnel_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1473,7 +1478,8 @@ static void tb_test_tunnel_dp_chain(struct kunit *test) in = &host->ports[5]; out = &dev4->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1523,7 +1529,8 @@ static void tb_test_tunnel_dp_tree(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1588,7 +1595,8 @@ static void tb_test_tunnel_dp_max_length(struct kunit *test) in = &dev6->ports[13]; out = &dev12->ports[13]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_EXPECT_EQ(test, tunnel->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel->src_port, in); @@ -1658,7 +1666,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) out2 = &dev5->ports[13]; out3 = &dev4->ports[14]; - tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, NULL, NULL); + tunnel1 = tb_tunnel_alloc_dp(NULL, in1, out1, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel1 != NULL); KUNIT_EXPECT_EQ(test, tunnel1->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel1->src_port, in1); @@ -1666,7 +1675,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel1->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel1->paths[0]->path_length, 3); - tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, NULL, NULL); + tunnel2 = tb_tunnel_alloc_dp(NULL, in2, out2, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel2 != NULL); KUNIT_EXPECT_EQ(test, tunnel2->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel2->src_port, in2); @@ -1674,7 +1684,8 @@ static void tb_test_tunnel_3dp(struct kunit *test) KUNIT_ASSERT_EQ(test, tunnel2->npaths, 3); KUNIT_ASSERT_EQ(test, tunnel2->paths[0]->path_length, 4); - tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, NULL, NULL); + tunnel3 = tb_tunnel_alloc_dp(NULL, in3, out3, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_TRUE(test, tunnel3 != NULL); KUNIT_EXPECT_EQ(test, tunnel3->type, TB_TUNNEL_DP); KUNIT_EXPECT_PTR_EQ(test, tunnel3->src_port, in3); @@ -1772,7 +1783,8 @@ static void tb_test_tunnel_port_on_path(struct kunit *test) in = &dev2->ports[13]; out = &dev5->ports[13]; - dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel); KUNIT_EXPECT_TRUE(test, tb_tunnel_port_on_path(dp_tunnel, in)); @@ -2204,7 +2216,8 @@ static void tb_test_credit_alloc_dp(struct kunit *test) in = &host->ports[5]; out = &dev->ports[14]; - tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + tunnel = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, tunnel); KUNIT_ASSERT_EQ(test, tunnel->npaths, (size_t)3); @@ -2440,7 +2453,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL1(struct kunit *test, in = &host->ports[5]; out = &dev->ports[13]; - dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel1 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel1); KUNIT_ASSERT_EQ(test, dp_tunnel1->npaths, (size_t)3); @@ -2477,7 +2491,8 @@ static struct tb_tunnel *TB_TEST_DP_TUNNEL2(struct kunit *test, in = &host->ports[6]; out = &dev->ports[14]; - dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, NULL, NULL); + dp_tunnel2 = tb_tunnel_alloc_dp(NULL, in, out, 1, 0, 0, + tb_test_dp_tunnel_active, NULL); KUNIT_ASSERT_NOT_NULL(test, dp_tunnel2); KUNIT_ASSERT_EQ(test, dp_tunnel2->npaths, (size_t)3); diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 7e8284575dff..6ac44b371266 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1109,8 +1109,7 @@ static void tb_dp_dprx_work(struct work_struct *work) mutex_unlock(&tb->lock); } - if (tunnel->callback) - tunnel->callback(tunnel, tunnel->callback_data); + tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); } @@ -1123,15 +1122,10 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) tb_tunnel_get(tunnel); tunnel->dprx_started = true; + tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); + queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - if (tunnel->callback) { - tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); - queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); - return -EINPROGRESS; - } - - return tb_dp_is_usb4(tunnel->src_port->sw) ? - tb_dp_wait_dprx(tunnel, dprx_timeout) : 0; + return -EINPROGRESS; } static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) @@ -1582,20 +1576,28 @@ static void tb_dp_dump(struct tb_tunnel *tunnel) * @tb: Pointer to the domain structure * @in: DP in adapter * @alloc_hopid: Allocate HopIDs from visited ports + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * If @in adapter is active, follows the tunnel to the DP out adapter * and back. Returns the discovered tunnel or %NULL if there was no - * tunnel. + * tunnel. See tb_tunnel_alloc_dp() for @callback. * * Return: Pointer to &struct tb_tunnel or %NULL if no tunnel found. */ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid) + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data) { struct tb_tunnel *tunnel; struct tb_port *port; struct tb_path *path; + if (WARN_ON(!callback)) + return NULL; + if (!tb_dp_port_is_enabled(in)) return NULL; @@ -1611,6 +1613,9 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tunnel->alloc_bandwidth = tb_dp_alloc_bandwidth; tunnel->consumed_bandwidth = tb_dp_consumed_bandwidth; tunnel->src_port = in; + tunnel->callback = callback; + tunnel->callback_data = callback_data; + INIT_DELAYED_WORK(&tunnel->dprx_work, tb_dp_dprx_work); path = tb_path_discover(in, TB_DP_VIDEO_HOPID, NULL, -1, &tunnel->dst_port, "Video", alloc_hopid); @@ -1677,16 +1682,16 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, * %0 if no available bandwidth. * @max_down: Maximum available downstream bandwidth for the DP tunnel. * %0 if no available bandwidth. - * @callback: Optional callback that is called when the DP tunnel is - * fully activated (or there is an error) - * @callback_data: Optional data for @callback + * @callback: Callback that is called when the DP tunnel is fully + * activated (or there is an error) + * @callback_data: Data for @callback * * Allocates a tunnel between @in and @out that is capable of tunneling - * Display Port traffic. If @callback is not %NULL it will be called - * after tb_tunnel_activate() once the tunnel has been fully activated. - * It can call tb_tunnel_is_active() to check if activation was - * successful (or if it returns %false there was some sort of issue). - * The @callback is called without @tb->lock held. + * Display Port traffic. The @callback is called after tb_tunnel_activate() + * once the tunnel has been fully activated. It can call + * tb_tunnel_is_active() to check if activation was successful (or if it + * returns %false there was some sort of issue). The @callback is called + * without @tb->lock held. * * Return: Pointer to @struct tb_tunnel or %NULL in case of failure. */ @@ -1701,7 +1706,7 @@ struct tb_tunnel *tb_tunnel_alloc_dp(struct tb *tb, struct tb_port *in, struct tb_path *path; bool pm_support; - if (WARN_ON(!in->cap_adap || !out->cap_adap)) + if (WARN_ON(!in->cap_adap || !out->cap_adap || !callback)) return NULL; tunnel = tb_tunnel_alloc(tb, 3, TB_TUNNEL_DP); diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h index 4878763a82b3..7d1d255ab5a7 100644 --- a/drivers/thunderbolt/tunnel.h +++ b/drivers/thunderbolt/tunnel.h @@ -66,8 +66,8 @@ enum tb_tunnel_state { * @dprx_canceled: Was DPRX capabilities read poll canceled * @dprx_timeout: If set DPRX capabilities read poll work will timeout after this passes * @dprx_work: Worker that is scheduled to poll completion of DPRX capabilities read - * @callback: Optional callback called when DP tunnel is fully activated - * @callback_data: Optional data for @callback + * @callback: Callback called when DP tunnel is fully activated + * @callback_data: Data for @callback * @paths: All paths required by the tunnel */ struct tb_tunnel { @@ -117,7 +117,9 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up, bool tb_tunnel_reserved_pci(struct tb_port *port, int *reserved_up, int *reserved_down); struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, - bool alloc_hopid); + bool alloc_hopid, + void (*callback)(struct tb_tunnel *, void *), + void *callback_data); 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, From 1fd1f67c94d30889377bba774f032ec15e8b9299 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:35 +0200 Subject: [PATCH 03/63] thunderbolt: Fix domain reference leak when DPRX read is canceled tb_tunnel_one_dp() takes a domain reference which is only dropped once tb_dp_tunnel_active() has run on the work queue. If that work is cancelled that reference is leaked. Since commit f5cc545f5969 ("thunderbolt: Wait for tb_domain_release() to complete when driver is removed") instead of just leaking memory this now also blocks in the completion wait forever when unbinding the driver. This can be triggered whenever a DP tunnel is torn down before the DPRX read has completed, e.g. by unplugging within the timeout, and then unbinding the driver. That reference only exists to keep the domain around while the DPRX work is scheduled so let the work itself own it: take it in tb_dp_dprx_start() and drop it in both places that end the work. Get/put are then paired inside the same file and it doesn't matter anymore if the callback ever runs. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 6 +----- drivers/thunderbolt/tunnel.c | 12 +++++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 20f0f8f4bdf7..1c5e0c9159c4 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1966,8 +1966,6 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) tb_dp_resource_unavailable(tb, in, "DPRX negotiation failed"); } mutex_unlock(&tb->lock); - - tb_domain_put(tb); } static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, @@ -2028,8 +2026,7 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, available_up, available_down); tunnel = tb_tunnel_alloc_dp(tb, in, out, link_nr, available_up, - available_down, tb_dp_tunnel_active, - tb_domain_get(tb)); + available_down, tb_dp_tunnel_active, tb); if (!tunnel) { tb_port_dbg(out, "could not allocate DP tunnel\n"); goto err_reclaim_usb; @@ -2050,7 +2047,6 @@ static void tb_tunnel_one_dp(struct tb *tb, struct tb_port *in, tb_tunnel_put(tunnel); err_reclaim_usb: tb_reclaim_usb3_bandwidth(tb, in, out); - tb_domain_put(tb); err_detach_group: tb_detach_bandwidth_group(in); err_dealloc_dp: diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 6ac44b371266..4f3a3717e37d 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1111,15 +1111,17 @@ static void tb_dp_dprx_work(struct work_struct *work) tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); + tb_domain_put(tb); } static int tb_dp_dprx_start(struct tb_tunnel *tunnel) { /* - * Bump up the reference to keep the tunnel around. It will be - * dropped in tb_dp_dprx_stop() once the tunnel is deactivated. + * Bump up the references to keep the tunnel and the domain around + * until the work has run or has been canceled. */ tb_tunnel_get(tunnel); + tb_domain_get(tunnel->tb); tunnel->dprx_started = true; tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); @@ -1130,11 +1132,15 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) static void tb_dp_dprx_stop(struct tb_tunnel *tunnel) { + struct tb *tb = tunnel->tb; + if (tunnel->dprx_started) { tunnel->dprx_started = false; tunnel->dprx_canceled = true; - if (cancel_delayed_work(&tunnel->dprx_work)) + if (cancel_delayed_work(&tunnel->dprx_work)) { tb_tunnel_put(tunnel); + tb_domain_put(tb); + } } } From 419fa32fa5bc2d7fb9d0d15d5630b1c1090808b3 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:36 +0200 Subject: [PATCH 04/63] thunderbolt: Don't access a DP tunnel after its DPRX read was canceled tb_dp_dprx_work() checks ->dprx_canceled before it takes tb->lock so it misses a tb_dp_dprx_stop() that could not cancel the already running work. It then polls the DPRX capabilities and runs the callback for a tunnel that has already been torn down while the domain is suspending or going away. This can be hit by cancelling the DPRX read from outside the ordered tb->wq: During suspend tb_disconnect_and_release_dp() does just this and with a later patch tb_stop() will do it as well. The latter in combination with the Apple NHI where the DPRX read never completed is how I hit this. Check the flag with tb->lock held instead and check it again in tb_dp_tunnel_active() because the callback runs after the lock has been dropped again. Also clear the flag in tb_dp_dprx_start() so that it only ever describes the work that is currently in flight. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 12 ++++++++++++ drivers/thunderbolt/tunnel.c | 11 +++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 1c5e0c9159c4..70a1a585bd54 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -1912,6 +1912,18 @@ static void tb_dp_tunnel_active(struct tb_tunnel *tunnel, void *data) struct tb *tb = data; mutex_lock(&tb->lock); + + /* + * If the DPRX read was canceled the tunnel is already being torn + * down by whoever canceled it. Do not touch the adapters here + * because the routers may be gone by now. + */ + if (tunnel->dprx_canceled) { + tb_tunnel_dbg(tunnel, "DPRX read canceled, not activating\n"); + mutex_unlock(&tb->lock); + return; + } + if (tb_tunnel_is_active(tunnel)) { int consumed_up, consumed_down, ret; diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index 4f3a3717e37d..d015e54abde4 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -1093,8 +1093,14 @@ static void tb_dp_dprx_work(struct work_struct *work) struct tb_tunnel *tunnel = container_of(work, typeof(*tunnel), dprx_work.work); struct tb *tb = tunnel->tb; + /* + * The DPRX read can be canceled while this work is waiting for + * tb->lock. Check the flag only once it is held: while the lock is + * held the tunnel cannot be torn down under us and the adapters are + * safe to access. + */ + mutex_lock(&tb->lock); if (!tunnel->dprx_canceled) { - mutex_lock(&tb->lock); if (tb_dp_is_usb4(tunnel->src_port->sw) && tb_dp_wait_dprx(tunnel, TB_DPRX_WAIT_TIMEOUT)) { if (ktime_before(ktime_get(), tunnel->dprx_timeout)) { @@ -1106,8 +1112,8 @@ static void tb_dp_dprx_work(struct work_struct *work) } else { tb_tunnel_set_active(tunnel, true); } - mutex_unlock(&tb->lock); } + mutex_unlock(&tb->lock); tunnel->callback(tunnel, tunnel->callback_data); tb_tunnel_put(tunnel); @@ -1124,6 +1130,7 @@ static int tb_dp_dprx_start(struct tb_tunnel *tunnel) tb_domain_get(tunnel->tb); tunnel->dprx_started = true; + tunnel->dprx_canceled = false; tunnel->dprx_timeout = dprx_timeout_to_ktime(dprx_timeout); queue_delayed_work(tunnel->tb->wq, &tunnel->dprx_work, 0); From c222f80be55f6c17851af1c68a70ae4046c848e2 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:37 +0200 Subject: [PATCH 05/63] thunderbolt: Mark discovered tunnels as active Discovered tunnels that have been activated by whatever was running before us stay in TB_TUNNEL_INACTIVE until hibernation restore such that anything depending on tb_tunnel_is_active() skips them. Mark them active during discovery instead. These now also emit TUNNEL_EVENT=activated uevents during discovery. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tunnel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c index d015e54abde4..ffd2d04b3bfa 100644 --- a/drivers/thunderbolt/tunnel.c +++ b/drivers/thunderbolt/tunnel.c @@ -510,6 +510,7 @@ struct tb_tunnel *tb_tunnel_discover_pci(struct tb *tb, struct tb_port *down, goto err_deactivate; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -1674,6 +1675,7 @@ struct tb_tunnel *tb_tunnel_discover_dp(struct tb *tb, struct tb_port *in, tb_dp_dump(tunnel); + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; @@ -2306,6 +2308,7 @@ struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down, tb_usb3_reclaim_available_bandwidth; } + tb_tunnel_set_active(tunnel, true); tb_tunnel_dbg(tunnel, "discovered\n"); return tunnel; From 0b457c6733f73421a3fb03786f13d67c618b5119 Mon Sep 17 00:00:00 2001 From: Sven Peter Date: Sat, 29 Aug 2026 10:08:38 +0200 Subject: [PATCH 06/63] thunderbolt: Tear down inactive DP tunnels when the domain is stopped tb_stop() only tears down DMA tunnels so a DP tunnel that is still waiting for dprx_work to complete keeps that work queued while the routers are removed and the control channel is stopped. The work only stops once the DPRX timeout has passed and because it requeues itself until then the flush_workqueue() in tb_domain_remove() won't wait for its final run. The callback then runs against a domain that is already torn down. A reference to that domain is kept so the completion waiting for that domain to disappear in unbind will block until the timeout is eventually reached. Tear down DP tunnels that are not active yet as well which also cancels that work. Tunnels for displays that are already alive are untouched and keep working. Fixes: d6d458d42e1e ("thunderbolt: Handle DisplayPort tunnel activation asynchronously") Cc: stable@vger.kernel.org Signed-off-by: Sven Peter Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 70a1a585bd54..ba63610d2ce2 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -2960,11 +2960,12 @@ static void tb_stop(struct tb *tb) /* tunnels are only present after everything has been initialized */ list_for_each_entry_safe(tunnel, n, &tcm->tunnel_list, list) { /* - * DMA tunnels require the driver to be functional so we - * tear them down. Other protocol tunnels can be left - * intact. + * DMA tunnels and DP tunnels which are not yet active require + * the driver to be functional so we tear them down. + * Other protocol tunnels can be left intact. */ - if (tb_tunnel_is_dma(tunnel)) + if (tb_tunnel_is_dma(tunnel) || + (tb_tunnel_is_dp(tunnel) && !tb_tunnel_is_active(tunnel))) tb_tunnel_deactivate(tunnel); tb_tunnel_put(tunnel); } From 4310c6b8e75d6a47f7548e5948fbe6318aa4440a Mon Sep 17 00:00:00 2001 From: Fedor Pchelkin Date: Mon, 31 Aug 2026 10:58:09 +0300 Subject: [PATCH 07/63] thunderbolt: Fix NULL dereference in tb_remove_work() There is a slight race between tb_remove_work() and tb_domain_remove() which leads to dereferencing a NULL tb->root_switch pointer inside tb_free_unplugged_xdomains(): Thread A Thread B tb_remove_work() tb_domain_remove() mutex_lock(&tb->lock) tb_stop() /* doesn't cancel a running callback */ cancel_delayed_work(&tcm->remove_work) ... tb_switch_remove(tb->root_switch) tb->root_switch = NULL mutex_unlock(&tb->lock) mutex_lock(&tb->lock) ... /* without checking ->root_switch */ tb_free_unplugged_xdomains(tb->root_switch) mutex_unlock(&tb->lock) Commit a8937f35cf39 ("thunderbolt: Remove XDomain from the bus without holding tb->lock") doesn't seem right to move tb_free_unplugged_xdomains() out of the &tb->lock section and the check for tb->root_switch, in particular. It states: For this reason separate removing the XDomain from the topology data structures (where we need the lock) from unregistering the device from the bus (where remove callbacks of the drivers are being called). tb_free_unplugged_xdomains() belongs to the former group of functions requiring the lock. And it also calls tb_xdomain_remove() which should only be called with &tb->lock held. Found by Linux Verification Center (linuxtesting.org) with Svace static analysis tool. Fixes: a8937f35cf39 ("thunderbolt: Remove XDomain from the bus without holding tb->lock") Cc: stable@vger.kernel.org Signed-off-by: Fedor Pchelkin Signed-off-by: Mika Westerberg --- drivers/thunderbolt/tb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index ba63610d2ce2..4e5d0578fd4b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -3285,11 +3285,11 @@ static void tb_remove_work(struct work_struct *work) struct tb *tb = tcm_to_tb(tcm); mutex_lock(&tb->lock); - if (tb->root_switch) + if (tb->root_switch) { tb_free_unplugged_children(tb->root_switch); + tb_free_unplugged_xdomains(tb->root_switch); + } mutex_unlock(&tb->lock); - - tb_free_unplugged_xdomains(tb->root_switch); } static int tb_runtime_resume(struct tb *tb) From cd243401994880b8b6810c53177bce56eebaece5 Mon Sep 17 00:00:00 2001 From: Carlo Szelinsky Date: Wed, 12 Aug 2026 13:42:28 +0300 Subject: [PATCH 08/63] USB: serial: option: add Quectel EG060W Add Quectel EG060W with product ID 0x6004. The three vendor-specific interfaces are DIAG, NMEA and AT; the CDC-NCM data interfaces are handled by the cdc_ncm driver. T: Bus=01 Lev=01 Prnt=01 Port=01 Cnt=01 Dev#= 2 Spd=480 MxCh= 0 D: Ver= 2.10 Cls=ef(misc ) Sub=02 Prot=01 MxPS=64 #Cfgs= 1 P: Vendor=2c7c ProdID=6004 Rev= 3.18 S: Manufacturer=Quectel S: Product=EG060W-EAAA S: SerialNumber=123456789ABCD C:* #Ifs= 5 Cfg#= 1 Atr=e0 MxPwr= 2mA A: FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=0d Prot=00 I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=0d Prot=00 Driver=cdc_ncm E: Ad=82(I) Atr=03(Int.) MxPS= 16 Ivl=32ms I: If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm I:* If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=01 Driver=cdc_ncm E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=83(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=84(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=option E: Ad=85(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms Cc: stable@vger.kernel.org Signed-off-by: Carlo Szelinsky Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index e4ad14375d6a..f6d1803bfdad 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -280,6 +280,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EC200U 0x0901 #define QUECTEL_PRODUCT_EG912Y 0x6001 #define QUECTEL_PRODUCT_EC200S_CN 0x6002 +#define QUECTEL_PRODUCT_EG060W 0x6004 #define QUECTEL_PRODUCT_EC200A 0x6005 #define QUECTEL_PRODUCT_EG916Q 0x6007 #define QUECTEL_PRODUCT_EM061K_LWW 0x6008 @@ -1268,6 +1269,7 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200A, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200U, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200S_CN, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG060W, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EC200T, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG912Y, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_EG916Q, 0xff, 0x00, 0x00) }, From 35c950c9ec2c4c4d45dc9b63cfe8fca774ce33a0 Mon Sep 17 00:00:00 2001 From: Zhao Dongdong Date: Mon, 17 Aug 2026 15:14:27 +0800 Subject: [PATCH 09/63] USB: serial: option: add support for SIMCom SIM8260C Add support for SIMCom SIM8260C (1e0e:902b). T: Bus=08 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 2 Spd=5000 MxCh= 0 D: Ver= 3.20 Cls=00(>ifc ) Sub=00 Prot=00 MxPS= 9 #Cfgs= 1 P: Vendor=1e0e ProdID=902b Rev= 5.04 S: Manufacturer=SIMCOM S: Product=SDXLEMUR-LITE-MTP _SN:120696AB S: SerialNumber=0123456789ABCDEF C:* #Ifs= 8 Cfg#= 1 Atr=a0 MxPwr=896mA A: FirstIf#= 5 IfCount= 3 Cls=01(audio) Sub=00 Prot=20 I:* If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option E: Ad=01(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=81(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option E: Ad=83(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=82(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=02(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=85(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=84(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=03(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option E: Ad=87(I) Atr=03(Int.) MxPS= 10 Ivl=32ms E: Ad=86(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=04(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 4 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan_simcom E: Ad=88(I) Atr=03(Int.) MxPS= 8 Ivl=32ms E: Ad=8e(I) Atr=02(Bulk) MxPS=1024 Ivl=0ms E: Ad=0f(O) Atr=02(Bulk) MxPS=1024 Ivl=0ms I:* If#= 5 Alt= 0 #EPs= 0 Cls=01(audio) Sub=01 Prot=20 Driver=snd-usb-audio I:* If#= 6 Alt= 0 #EPs= 0 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio I: If#= 6 Alt= 1 #EPs= 1 Cls=01(audio) Sub=02 Prot=20 Driver=snd-usb-audio E: Ad=05(O) Atr=0d(Isoc) MxPS= 34 Ivl=1ms I:* If#= 7 Alt= 0 #EPs= 0 Cls=01(audio) Sub=02 Prot=20 Driver=(none) I: If#= 7 Alt= 1 #EPs= 1 Cls=01(audio) Sub=02 Prot=20 Driver=(none) E: Ad=89(I) Atr=0d(Isoc) MxPS= 34 Ivl=1ms Signed-off-by: Zhao Dongdong Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index f6d1803bfdad..0fe1694fcd74 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2170,6 +2170,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9003, 0xff) }, /* Simcom SIM7500/SIM7600 MBIM mode */ { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9011, 0xff), /* Simcom SIM7500/SIM7600 RNDIS mode */ .driver_info = RSVD(7) }, + { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x902b, 0xff), + .driver_info = RSVD(4) }, { USB_DEVICE(0x1e0e, 0x9071), /* Simcom SIM8230 RMNET mode */ .driver_info = RSVD(3) | RSVD(4) }, { USB_DEVICE_INTERFACE_CLASS(0x1e0e, 0x9078, 0xff), /* Simcom SIM8230 ECM mode */ From 7f11480cda081de78a05d8c97073df66326b50b3 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Mon, 31 Aug 2026 16:44:05 +0800 Subject: [PATCH 10/63] USB: serial: option: add Compal EXM-G1x support The Compal EXM-G1x is a Qualcomm SDX12-based LTE modem. Add support for its vendor-specific serial interfaces with protocols 0x30, 0x40, and 0x60. Relevant output from usb-devices: T: Bus=04 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#= 4 Spd=5000 MxCh= 0 D: Ver= 3.20 Cls=ef(misc ) Sub=02 Prot=01 MxPS= 9 #Cfgs= 1 P: Vendor=04b7 ProdID=8217 Rev=05.04 S: Manufacturer=COMPAL S: Product=EXM-G1x C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=896mA I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=ff Prot=30 Driver=option I: If#= 1 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=42 Prot=01 Driver=(none) I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=60 Driver=option I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=40 Driver=option I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#= 8 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=ff Prot=50 Driver=qmi_wwan Tested on a Compal EXM-G1x modem. Assisted-by: Codex:GPT-5 Signed-off-by: Ian Lin Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 0fe1694fcd74..03c3785a1585 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2433,6 +2433,9 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, { USB_DEVICE(0x1508, 0x1001), /* Fibocom NL668 (IOT version) */ .driver_info = RSVD(4) | RSVD(5) | RSVD(6) }, { USB_DEVICE(0x1782, 0x4d10) }, /* Fibocom L610 (AT mode) */ From a02188ddc24b7872f0c5e0c5873f827318717803 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 7 May 2026 08:06:48 +0300 Subject: [PATCH 11/63] thunderbolt: Fix KASAN reported use-after-free when request is canceled Alan reported that when doing stress testing sometimes KASAN notices use-after-free during control channel operation (stripped down keeping the relevant parts): BUG: KASAN: slab-use-after-free in tb_cfg_request_sync+0x240/0x250 [thunderbolt] Read of size 24 at addr ffff88811067f290 by task kworker/u40:2/1760 tb_cfg_request_sync+0x240/0x250 [thunderbolt] tb_cfg_read_raw+0x367/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Allocated by task 1760: __kmalloc_cache_noprof+0x1ee/0x550 tb_cfg_read_raw+0x1d3/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Freed by task 926: kfree+0x18f/0x4a0 tb_cfg_request_put+0xb7/0xe0 [thunderbolt] tb_cfg_request_work+0x82/0x120 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 Second to last potentially related work creation: __queue_work+0x575/0xd00 queue_work_on+0x77/0x80 tb_cfg_request_cancel+0xc7/0x260 [thunderbolt] tb_cfg_request_sync+0x1f6/0x250 [thunderbolt] tb_cfg_read_raw+0x367/0x510 [thunderbolt] tb_cfg_read+0xec/0x240 [thunderbolt] tb_port_get_link_generation+0x258/0x420 [thunderbolt] tb_usb3_consumed_bandwidth+0x1c1/0x2c0 [thunderbolt] tb_tunnel_consumed_bandwidth+0xfd/0x910 [thunderbolt] tb_available_bandwidth+0x5f2/0xeb0 [thunderbolt] tb_recalc_estimated_bandwidth+0x2a0/0x1bc0 [thunderbolt] tb_handle_dp_bandwidth_request+0x1897/0x5e20 [thunderbolt] process_one_work+0x675/0x1230 worker_thread+0x5e6/0xf70 kthread+0x365/0x470 ret_from_fork+0x54d/0x710 ret_from_fork_asm+0x1a/0x30 The last stack trace is helpful because it shows that we are cancelling a request and looking at tb_cfg_request_cancel() what might happen is that tb_cfg_request_work() completes right before tb_cfg_request_cancel() starts and because of this it will call schedule_work() queueing the same work to run again. However, it is already removed from the request_queue and reference count is dropped so when tb_cfg_request_work() triggers again it will access memory that is already released. Fix this so that we first make sure a cancelled request is not handed away from tb_cfg_request_find() or scheduled to run. Then instead of relying on the worker to clean up the request we will do it in tb_cfg_request_cancel() after the work is canceled from running. Make tb_cfg_request_dequeue() release the request only if it was actually removed from the queue. Reported-by: Alan Borzeszkowski Fixes: d7f781bfdbf4 ("thunderbolt: Rework control channel to be more reliable") Cc: stable@vger.kernel.org Signed-off-by: Mika Westerberg --- drivers/thunderbolt/ctl.c | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index cd47b627f97b..965988b18608 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -73,7 +73,6 @@ struct tb_ctl { #define tb_ctl_dbg_once(ctl, format, arg...) \ dev_dbg_once((ctl)->nhi->dev, format, ## arg) -static DECLARE_WAIT_QUEUE_HEAD(tb_cfg_request_cancel_queue); /* Serializes access to request kref_get/put */ static DEFINE_MUTEX(tb_cfg_request_lock); @@ -133,41 +132,42 @@ void tb_cfg_request_put(struct tb_cfg_request *req) static int tb_cfg_request_enqueue(struct tb_ctl *ctl, struct tb_cfg_request *req) { + tb_cfg_request_get(req); + WARN_ON(test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)); WARN_ON(req->ctl); - mutex_lock(&ctl->request_queue_lock); + guard(mutex)(&ctl->request_queue_lock); if (!ctl->running) { - mutex_unlock(&ctl->request_queue_lock); + tb_cfg_request_put(req); return -ENOTCONN; } req->ctl = ctl; list_add_tail(&req->list, &ctl->request_queue); set_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - mutex_unlock(&ctl->request_queue_lock); return 0; } +static bool tb_cfg_request_is_active(struct tb_cfg_request *req) +{ + return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); +} + +static bool tb_cfg_request_is_canceled(struct tb_cfg_request *req) +{ + return test_bit(TB_CFG_REQUEST_CANCELED, &req->flags); +} + static void tb_cfg_request_dequeue(struct tb_cfg_request *req) { struct tb_ctl *ctl = req->ctl; - mutex_lock(&ctl->request_queue_lock); - if (!test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags)) { - mutex_unlock(&ctl->request_queue_lock); - return; + guard(mutex)(&ctl->request_queue_lock); + if (tb_cfg_request_is_active(req)) { + list_del(&req->list); + clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); + tb_cfg_request_put(req); } - - list_del(&req->list); - clear_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); - if (test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) - wake_up(&tb_cfg_request_cancel_queue); - mutex_unlock(&ctl->request_queue_lock); -} - -static bool tb_cfg_request_is_active(struct tb_cfg_request *req) -{ - return test_bit(TB_CFG_REQUEST_ACTIVE, &req->flags); } static struct tb_cfg_request * @@ -178,7 +178,7 @@ tb_cfg_request_find(struct tb_ctl *ctl, struct ctl_pkg *pkg) mutex_lock(&pkg->ctl->request_queue_lock); list_for_each_entry(iter, &pkg->ctl->request_queue, list) { tb_cfg_request_get(iter); - if (iter->match(iter, pkg)) { + if (!tb_cfg_request_is_canceled(iter) && iter->match(iter, pkg)) { req = iter; break; } @@ -512,8 +512,11 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, trace_tb_rx(pkg->ctl->index, frame->eof, pkg->buffer, frame->size, !req); if (req) { - if (req->copy(req, pkg)) - schedule_work(&req->work); + scoped_guard(mutex, &pkg->ctl->request_queue_lock) { + if (!tb_cfg_request_is_canceled(req) && + req->copy(req, pkg)) + schedule_work(&req->work); + } tb_cfg_request_put(req); } @@ -525,11 +528,10 @@ static void tb_cfg_request_work(struct work_struct *work) { struct tb_cfg_request *req = container_of(work, typeof(*req), work); - if (!test_bit(TB_CFG_REQUEST_CANCELED, &req->flags)) + if (!tb_cfg_request_is_canceled(req)) req->callback(req->callback_data); tb_cfg_request_dequeue(req); - tb_cfg_request_put(req); } /** @@ -555,10 +557,9 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, INIT_WORK(&req->work, tb_cfg_request_work); INIT_LIST_HEAD(&req->list); - tb_cfg_request_get(req); ret = tb_cfg_request_enqueue(ctl, req); if (ret) - goto err_put; + return ret; ret = tb_ctl_tx(ctl, req->request, req->request_size, req->request_type); @@ -572,9 +573,6 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, err_dequeue: tb_cfg_request_dequeue(req); -err_put: - tb_cfg_request_put(req); - return ret; } @@ -588,9 +586,10 @@ int tb_cfg_request(struct tb_ctl *ctl, struct tb_cfg_request *req, */ void tb_cfg_request_cancel(struct tb_cfg_request *req, int err) { - set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); - schedule_work(&req->work); - wait_event(tb_cfg_request_cancel_queue, !tb_cfg_request_is_active(req)); + scoped_guard(mutex, &req->ctl->request_queue_lock) + set_bit(TB_CFG_REQUEST_CANCELED, &req->flags); + cancel_work_sync(&req->work); + tb_cfg_request_dequeue(req); req->result.err = err; } From 96ff396e6e0eb754b73f1870ef7e72f2f80cdbe0 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 15 May 2026 10:28:40 +0300 Subject: [PATCH 12/63] thunderbolt: Use separate lock class for each ring When connected to another host and then unplugging cable lockdep triggers following: ====================================================== WARNING: possible circular locking dependency detected 7.1.0-rc2+ #1775 Tainted: G U ------------------------------------------------------ kworker/u16:6/312 is trying to acquire lock: ffff8881179c70a8 ((work_completion)(&ring->work)){+.+.}-{0:0}, at: __flush_work+0x3cf/0xd10 but task is already holding lock: ffff8881a8b810b0 (&net->connection_lock){+.+.}-{4:4}, at: tbnet_tear_down+0x110/0x720 [thunderbolt_net] which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #1 (&net->connection_lock){+.+.}-{4:4}: __mutex_lock+0x19a/0x2490 mutex_lock_nested+0x1b/0x30 tbnet_handle_packet+0x74c/0xd70 [thunderbolt_net] tb_xdomain_handle_request+0x37c/0x4b0 [thunderbolt] tb_domain_event_cb+0xc9/0x140 [thunderbolt] tb_ctl_handle_event+0xd6/0x2c0 [thunderbolt] tb_ctl_rx_callback+0x22c/0xa10 [thunderbolt] ring_work+0x715/0xcb0 [thunderbolt] process_one_work+0x902/0x1790 worker_thread+0x5cd/0xfe0 kthread+0x339/0x420 ret_from_fork+0x79a/0x9d0 ret_from_fork_asm+0x1a/0x30 -> #0 ((work_completion)(&ring->work)){+.+.}-{0:0}: __lock_acquire+0x1592/0x2640 lock_acquire+0x1a3/0x300 __flush_work+0x3e9/0xd10 flush_work+0x21/0x30 tb_ring_stop+0x240/0x840 [thunderbolt] tbnet_tear_down+0x2ff/0x720 [thunderbolt_net] tbnet_stop+0x47/0x1a0 [thunderbolt_net] __dev_close_many+0x19e/0x4e0 netif_close_many+0x1e8/0x640 unregister_netdevice_many_notify+0x6d3/0x22d0 unregister_netdevice_queue+0x2b9/0x3a0 unregister_netdev+0x1c/0x70 tbnet_remove+0x52/0xb0 [thunderbolt_net] tb_service_remove+0x8a/0xe0 [thunderbolt] device_remove+0xc5/0x190 device_release_driver_internal+0x3db/0x590 device_release_driver+0x12/0x20 bus_remove_device+0x2c1/0x580 device_del+0x3d9/0x9f0 device_unregister+0x17/0xc0 unregister_service+0x46/0x60 [thunderbolt] device_for_each_child_reverse+0xfa/0x180 tb_xdomain_unregister+0x57/0xe0 [thunderbolt] unregister_unplugged_xdomain+0x101/0x1a0 [thunderbolt] bus_for_each_dev+0x111/0x1a0 tb_domain_unregister_unplugged_xdomains+0x98/0xe0 [thunderbolt] tb_handle_hotplug+0xc3/0x2bb0 [thunderbolt] process_one_work+0x902/0x1790 worker_thread+0x5cd/0xfe0 kthread+0x339/0x420 ret_from_fork+0x79a/0x9d0 ret_from_fork_asm+0x1a/0x30 other info that might help us debug this: Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&net->connection_lock); lock((work_completion)(&ring->work)); lock(&net->connection_lock); lock((work_completion)(&ring->work)); This in fact is false positive because they involve unrelated rings (and unrelated work structures). In the first one it is ring 0 which is used for control traffic and in the second it is dealing with another ring used for the high-speed traffic. Fix this by using separate lock class for each ring worker. Signed-off-by: Mika Westerberg --- drivers/thunderbolt/nhi.c | 5 +++++ include/linux/thunderbolt.h | 3 +++ 2 files changed, 8 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 5809809f64d4..d4d1efa2afa0 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -560,6 +561,8 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, INIT_LIST_HEAD(&ring->in_flight); INIT_WORK(&ring->work, ring_work); init_waitqueue_head(&ring->wait); + lockdep_register_key(&ring->lock_key); + lockdep_init_map(&ring->work.lockdep_map, "ring.work", &ring->lock_key, 0); ring->nhi = nhi; ring->hop = hop; @@ -599,6 +602,7 @@ static struct tb_ring *tb_ring_alloc(struct tb_nhi *nhi, u32 hop, int size, ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); err_free_ring: + lockdep_unregister_key(&ring->lock_key); kfree(ring); return NULL; @@ -848,6 +852,7 @@ void tb_ring_free(struct tb_ring *ring) * to finish before freeing the ring. */ flush_work(&ring->work); + lockdep_unregister_key(&ring->lock_key); kfree(ring); } EXPORT_SYMBOL_GPL(tb_ring_free); diff --git a/include/linux/thunderbolt.h b/include/linux/thunderbolt.h index d48623fda79b..b62dfa52b149 100644 --- a/include/linux/thunderbolt.h +++ b/include/linux/thunderbolt.h @@ -22,6 +22,7 @@ struct device; #include #include #include +#include #include #include #include @@ -565,6 +566,7 @@ struct tb_nhi { * @interval_nsec: Interval counter if interrupt throttling is to be * used with this ring (in ns) * @wait: Used to signal that the ring may be empty now + * @lock_key: Lock validator class key per-ring */ struct tb_ring { spinlock_t lock; @@ -590,6 +592,7 @@ struct tb_ring { void *poll_data; unsigned int interval_nsec; wait_queue_head_t wait; + struct lock_class_key lock_key; }; /* Leave ring interrupt enabled on suspend */ From e6df180e976fd08673214bc1305a46a8536aea9a Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Fri, 14 Aug 2026 06:35:52 +0200 Subject: [PATCH 13/63] Revert "thunderbolt: xdomain: Notify peers after enumeration" This reverts commit e027dba038f0008df9bc9575f5c3e803e90636c6. Alan noticed that this causes the peers send change properties to each other continuously. Reported-by: Borzeszkowski, Alan Cc: Milo Chen Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index c179bd751fe4..05442df0e99c 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -1814,7 +1814,6 @@ static void tb_xdomain_state_work(struct work_struct *work) tb_xdomain_failed(xd); } else { xd->state = XDOMAIN_STATE_ENUMERATED; - tb_xdomain_queue_properties_changed(xd); } break; From 6177bcd94be83f80a032c2e69a06606d325335b9 Mon Sep 17 00:00:00 2001 From: Sebastian Sjoholm Date: Thu, 3 Sep 2026 20:00:29 +0200 Subject: [PATCH 14/63] USB: serial: option: add Quectel RG660QB Add support for the Quectel RG660QB 5G module (USB ID 2c7c:013d). The interface layout is the same as the RG650V: If 0: DIAG ff/ff/30, 2 endpoints If 1: NMEA ff/00/00, 2 endpoints If 2: AT ff/00/00, 3 endpoints If 3: Modem ff/00/00, 3 endpoints If 4: QMI ff/ff/ff, 3 endpoints Match the serial interfaces on class/subclass/protocol so that the QMI interface is left to qmi_wwan. # lsusb -v -d 2c7c:013d Bus 004 Device 002: ID 2c7c:013d Quectel Wireless Solutions Co., Ltd. RG660QB-EU Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 3.20 bDeviceClass 0 bDeviceSubClass 0 bDeviceProtocol 0 bMaxPacketSize0 9 idVendor 0x2c7c Quectel Wireless Solutions Co., Ltd. idProduct 0x013d bcdDevice 6.06 iManufacturer 1 Quectel iProduct 2 RG660QB-EU iSerial 3 f50119d0 bNumConfigurations 1 Configuration Descriptor: bLength 9 bDescriptorType 2 wTotalLength 0x0105 bNumInterfaces 5 bConfigurationValue 1 iConfiguration 4 DIAG_SER_RMNET bmAttributes 0xa0 (Bus Powered) Remote Wakeup MaxPower 896mA Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 0 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 48 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x01 EP 1 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x81 EP 1 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 1 bAlternateSetting 0 bNumEndpoints 2 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x82 EP 2 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x02 EP 2 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 2 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 6 CDEV Serial ** UNRECOGNIZED: 05 24 00 10 01 ** UNRECOGNIZED: 05 24 01 00 00 ** UNRECOGNIZED: 04 24 02 02 ** UNRECOGNIZED: 05 24 06 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x84 EP 4 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x000a 1x 10 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x83 EP 3 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x03 EP 3 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 3 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 0 bInterfaceProtocol 0 iInterface 6 CDEV Serial ** UNRECOGNIZED: 05 24 00 10 01 ** UNRECOGNIZED: 05 24 01 00 00 ** UNRECOGNIZED: 04 24 02 02 ** UNRECOGNIZED: 05 24 06 00 00 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x86 EP 6 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x000a 1x 10 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x85 EP 5 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x04 EP 4 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 0 Interface Descriptor: bLength 9 bDescriptorType 4 bInterfaceNumber 4 bAlternateSetting 0 bNumEndpoints 3 bInterfaceClass 255 Vendor Specific Class bInterfaceSubClass 255 Vendor Specific Subclass bInterfaceProtocol 255 Vendor Specific Protocol iInterface 7 RmNet Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x88 EP 8 IN bmAttributes 3 Transfer Type Interrupt Synch Type None Usage Type Data wMaxPacketSize 0x0008 1x 8 bytes bInterval 9 bMaxBurst 0 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x87 EP 7 IN bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 6 Endpoint Descriptor: bLength 7 bDescriptorType 5 bEndpointAddress 0x05 EP 5 OUT bmAttributes 2 Transfer Type Bulk Synch Type None Usage Type Data wMaxPacketSize 0x0400 1x 1024 bytes bInterval 0 bMaxBurst 2 Binary Object Store Descriptor: bLength 5 bDescriptorType 15 wTotalLength 0x0023 bNumDeviceCaps 2 SuperSpeed USB Device Capability: bLength 10 bDescriptorType 16 bDevCapabilityType 3 bmAttributes 0x00 wSpeedsSupported 0x000f Device can operate at Low Speed (1Mbps) Device can operate at Full Speed (12Mbps) Device can operate at High Speed (480Mbps) Device can operate at SuperSpeed (5Gbps) bFunctionalitySupport 1 Lowest fully-functional device speed is Full Speed (12Mbps) bU1DevExitLat 0 micro seconds bU2DevExitLat 0 micro seconds SuperSpeedPlus USB Device Capability: bLength 20 bDescriptorType 16 bDevCapabilityType 10 bmAttributes 0x00000001 Sublink Speed Attribute count 1 Sublink Speed ID count 0 wFunctionalitySupport 0x1100 bmSublinkSpeedAttr[0] 0x000a4030 Speed Attribute ID: 0 10Gb/s Symmetric RX SuperSpeedPlus bmSublinkSpeedAttr[1] 0x000a40b0 Speed Attribute ID: 0 10Gb/s Symmetric TX SuperSpeedPlus Device Status: 0x0000 (Bus Powered) Tested with an early sample of the module on a Quectel 5G EVB connected over USB 3 to a Raspberry Pi 5. Assisted-by: LLM Signed-off-by: Sebastian Sjoholm Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 03c3785a1585..d530dbceccb1 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -260,6 +260,7 @@ static void option_instat_callback(struct urb *urb); #define QUECTEL_PRODUCT_EM060K_12a 0x012a #define QUECTEL_PRODUCT_EM060K_12b 0x012b #define QUECTEL_PRODUCT_EM060K_12c 0x012c +#define QUECTEL_PRODUCT_RG660QB 0x013d #define QUECTEL_PRODUCT_EG91 0x0191 #define QUECTEL_PRODUCT_EG95 0x0195 #define QUECTEL_PRODUCT_BG96 0x0296 @@ -1276,6 +1277,8 @@ static const struct usb_device_id option_ids[] = { { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RM500K, 0xff, 0x00, 0x00) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG650V, 0xff, 0, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0xff, 0x30) }, + { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG660QB, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x30) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0, 0) }, { USB_DEVICE_AND_INTERFACE_INFO(QUECTEL_VENDOR_ID, QUECTEL_PRODUCT_RG255C, 0xff, 0xff, 0x40) }, From b51faf3f2e307f114854bbe6d495f03cd5370686 Mon Sep 17 00:00:00 2001 From: Ian Lin Date: Fri, 4 Sep 2026 16:00:46 +0800 Subject: [PATCH 15/63] USB: serial: option: add Compal EXC-T1 support The Compal EXC-T1 is a Qualcomm MDM9207-based LTE modem which reports "EXC-x1" as its USB product string. It exposes diagnostic, modem, AT, and NMEA ports as vendor-specific interfaces with subclass 0x10 and protocols 0x01 through 0x04. Add exact interface matches for these four ports, leaving the ff/ff/ff interface unbound. Relevant output from usb-devices: T: Bus=03 Lev=02 Prnt=03 Port=03 Cnt=01 Dev#= 6 Spd=480 MxCh= 0 D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1 P: Vendor=04b7 ProdID=4d22 Rev=03.18 S: Manufacturer=CEI S: Product=EXC-x1 C: #Ifs= 6 Cfg#= 1 Atr=a0 MxPwr=500mA I: If#= 0 Alt= 0 #EPs= 2 Cls=ff(vend.) Sub=10 Prot=01 Driver=option I: If#= 1 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=02 Driver=option I: If#= 2 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=03 Driver=option I: If#= 3 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=04 Driver=option I: If#= 4 Alt= 0 #EPs= 1 Cls=ff(vend.) Sub=ff Prot=ff Driver=(none) I: If#= 5 Alt= 0 #EPs= 3 Cls=ff(vend.) Sub=10 Prot=05 Driver=qmi_wwan Tested on a Compal EXC-T1 modem using an Ubuntu kernel build. The four serial interfaces bound to option and exposed ttyUSB3 through ttyUSB6. Assisted-by: Codex:GPT-5 Signed-off-by: Ian Lin Cc: stable@vger.kernel.org Signed-off-by: Johan Hovold --- drivers/usb/serial/option.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index d530dbceccb1..828b1be3fee2 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -2436,6 +2436,10 @@ static const struct usb_device_id option_ids[] = { .driver_info = RSVD(5) }, { USB_DEVICE_INTERFACE_CLASS(0x0489, 0xe167, 0xff), /* Foxconn T99W640 MBIM */ .driver_info = RSVD(3) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x01) }, /* Compal EXC-T1 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x02) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x03) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x4d22, 0xff, 0x10, 0x04) }, { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x30) }, /* Compal EXM-G1x */ { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x40) }, { USB_DEVICE_AND_INTERFACE_INFO(0x04b7, 0x8217, 0xff, 0xff, 0x60) }, From 33c920f7c5952dfc42d1b8ea6efe16b1f0bb0c5b Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:43 +0200 Subject: [PATCH 16/63] USB: serial: fix port tear down use-after-free Some drivers for multiport devices access port driver data from completion handlers of shared URBs submitted at attach() or first open() and stopped at disconnect() or last close(), respectively. A simple NULL check before accessing the driver data makes sure that a port state container has at least been allocated, but a completion handler can still race with port tear down. Reorder the disconnect handling so that ports are not deregistered (and their driver data freed) until after all ports have been hung up and the driver disconnect() callback has run so that all I/O has been stopped. Fixes: 2d93148ab698 ("USB: serial: fix lifetime and locking problems") Reported-by: syzbot+e5e28c3e953b2eebb16e@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a7e6fb9.ec5dc6cc.21cb3f.00c0.GAE@google.com/ Cc: stable@vger.kernel.org # 2.6.30 Cc: Alan Stern Reviewed-by: Greg Kroah-Hartman Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 17edc057a311..a4fbc849c0fa 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1191,11 +1191,16 @@ static void usb_serial_disconnect(struct usb_interface *interface) usb_serial_port_poison_urbs(port); wake_up_interruptible(&port->port.delta_msr_wait); cancel_work_sync(&port->work); + } + + if (serial->type->disconnect) + serial->type->disconnect(serial); + + for (i = 0; i < serial->num_ports; ++i) { + port = serial->port[i]; if (device_is_registered(&port->dev)) device_del(&port->dev); } - if (serial->type->disconnect) - serial->type->disconnect(serial); release_sibling(serial, interface); From 63594e8ffe097bc0b7639936f33799a7653dded2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:44 +0200 Subject: [PATCH 17/63] USB: serial: fix dynamic id driver deregistration race Only free the dynamic ids after having deregistered the driver and removed the "new_id" attribute to avoid leaking any id added by a racing write to the attribute. Fixes: 93bacefc4cc0 ("USB serial: add dynamic id support to usb-serial core") Cc: stable@vger.kernel.org # 2.6.21 Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 9e2a18c0b218..295c61afa44c 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -165,7 +165,7 @@ int usb_serial_bus_register(struct usb_serial_driver *driver) void usb_serial_bus_deregister(struct usb_serial_driver *driver) { - free_dynids(driver); driver_unregister(&driver->driver); + free_dynids(driver); } From 646e5b28b4fdbc1b828a2398d6ced2c1dd308a46 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:45 +0200 Subject: [PATCH 18/63] USB: serial: fix driver deregistration order USB serial driver modules register one driver for the USB bus and one or more drivers for the ports on the USB serial bus. When unloading a driver module, the USB driver must be deregistered before the USB serial bus drivers so that I/O is stopped before unbinding the ports to avoid use-after-free in completion handlers accessing port data. Note that the "new_id" attributes must first be removed to prevent new ids from being added and triggering a probe of the USB driver after it has been deregistered. Fixes: 765e0ba62613 ("usb-serial: new API for driver registration") Cc: stable@vger.kernel.org # 3.4 Cc: Alan Stern Signed-off-by: Johan Hovold --- drivers/usb/serial/bus.c | 4 ++++ drivers/usb/serial/usb-serial.c | 21 ++++++++++++++++++--- include/linux/usb/serial.h | 1 + 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/bus.c b/drivers/usb/serial/bus.c index 295c61afa44c..ea1fe5d1e449 100644 --- a/drivers/usb/serial/bus.c +++ b/drivers/usb/serial/bus.c @@ -169,3 +169,7 @@ void usb_serial_bus_deregister(struct usb_serial_driver *driver) free_dynids(driver); } +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver) +{ + driver_remove_file(&driver->driver, &driver_attr_new_id); +} diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index a4fbc849c0fa..11a0ed6d6546 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1469,7 +1469,7 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers { int rc; struct usb_driver *udriver; - struct usb_serial_driver * const *sd; + struct usb_serial_driver * const *sd, * const *s; /* * udriver must be registered before any of the serial drivers, @@ -1522,9 +1522,11 @@ int __usb_serial_register_drivers(struct usb_serial_driver *const serial_drivers return 0; err_deregister_drivers: + for (s = serial_drivers; s < sd; ++s) + usb_serial_bus_remove_new_id(*s); + usb_deregister(udriver); while (sd-- > serial_drivers) usb_serial_deregister(*sd); - usb_deregister(udriver); err_free_driver: kfree(udriver); return rc; @@ -1542,10 +1544,23 @@ EXPORT_SYMBOL_GPL(__usb_serial_register_drivers); void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_drivers[]) { struct usb_driver *udriver = (*serial_drivers)->usb_driver; + struct usb_serial_driver * const *sd; + + /* + * udriver must be deregistered before the serial drivers so that + * I/O is stopped before unbinding the ports. + * + * Remove the new_id attributes to prevent ids from being added and + * triggering a probe of udriver after it has been deregistered. + */ + for (sd = serial_drivers; *sd; ++sd) + usb_serial_bus_remove_new_id(*sd); + + usb_deregister(udriver); for (; *serial_drivers; ++serial_drivers) usb_serial_deregister(*serial_drivers); - usb_deregister(udriver); + kfree(udriver); } EXPORT_SYMBOL_GPL(usb_serial_deregister_drivers); diff --git a/include/linux/usb/serial.h b/include/linux/usb/serial.h index 534e6650e2aa..ab0a32a90629 100644 --- a/include/linux/usb/serial.h +++ b/include/linux/usb/serial.h @@ -381,6 +381,7 @@ void usb_serial_handle_dcd_change(struct usb_serial_port *usb_port, int usb_serial_bus_register(struct usb_serial_driver *device); void usb_serial_bus_deregister(struct usb_serial_driver *device); +void usb_serial_bus_remove_new_id(struct usb_serial_driver *driver); extern const struct bus_type usb_serial_bus_type; extern struct tty_driver *usb_serial_tty_driver; From 8fe535227455db566bcaf40fd83db50b16dce935 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Fri, 21 Aug 2026 17:45:46 +0200 Subject: [PATCH 19/63] USB: serial: use iterator for driver deregistration Use the new serial driver iterator variable also when deregistering serial drivers to improve readability. Signed-off-by: Johan Hovold --- drivers/usb/serial/usb-serial.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 11a0ed6d6546..9c51b44e2284 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -1558,8 +1558,8 @@ void usb_serial_deregister_drivers(struct usb_serial_driver *const serial_driver usb_deregister(udriver); - for (; *serial_drivers; ++serial_drivers) - usb_serial_deregister(*serial_drivers); + for (sd = serial_drivers; *sd; ++sd) + usb_serial_deregister(*sd); kfree(udriver); } From ae0e61d95677efa778379853e495d748e6efbf95 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 3 Sep 2026 18:31:46 +0200 Subject: [PATCH 20/63] USB: serial: fix ioctl hangup race The tty ioctls can race with hangup and end up calling into a tty driver for a device that is already gone or powered down. Serialise the callbacks using the tty port mutex and add the missing checks to make sure the port has not been hung up before accessing port driver data or hardware. This specifically avoids dereferencing a NULL-pointer if a device is disconnected during lengthy break signalling. Note that TIOCGICOUNT and TIOCMIWAIT do not access port driver data or hardware and are therefore not affected by the race. Fixes: f34d7a5b7010 ("tty: The big operations rework") Reported-by: Nick Bowler Link: https://lore.kernel.org/all/CADyTPExB2kYOOwkO0JqGhKaYVDqO9uS9WCw0J=MCTdVhcGOogA@mail.gmail.com/ Reported-by: syzbot+473d7477c523b41d4046@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/6a9087a4.4d659fcc.734b4.0013.GAE@google.com Cc: stable@vger.kernel.org # 2.6.26 Signed-off-by: Johan Hovold --- drivers/usb/serial/generic.c | 10 +++- drivers/usb/serial/usb-serial.c | 94 ++++++++++++++++++++++++++------- drivers/usb/serial/xr_serial.c | 10 ++-- 3 files changed, 86 insertions(+), 28 deletions(-) diff --git a/drivers/usb/serial/generic.c b/drivers/usb/serial/generic.c index 6eaf74930aa3..17272701fab0 100644 --- a/drivers/usb/serial/generic.c +++ b/drivers/usb/serial/generic.c @@ -266,6 +266,7 @@ EXPORT_SYMBOL_GPL(usb_serial_generic_chars_in_buffer); void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; unsigned int bps; unsigned long period; unsigned long expire; @@ -285,7 +286,14 @@ void usb_serial_generic_wait_until_sent(struct tty_struct *tty, long timeout) __func__, jiffies_to_msecs(timeout), jiffies_to_msecs(period)); expire = jiffies + timeout; - while (!port->serial->type->tx_empty(port)) { + for (;;) { + mutex_lock(&tport->mutex); + if (tty_io_error(tty) || port->serial->type->tx_empty(port)) { + mutex_unlock(&tport->mutex); + break; + } + mutex_unlock(&tport->mutex); + schedule_timeout_interruptible(period); if (signal_pending(current)) break; diff --git a/drivers/usb/serial/usb-serial.c b/drivers/usb/serial/usb-serial.c index 9c51b44e2284..f3c594f1a806 100644 --- a/drivers/usb/serial/usb-serial.c +++ b/drivers/usb/serial/usb-serial.c @@ -400,17 +400,13 @@ static unsigned int serial_chars_in_buffer(struct tty_struct *tty) static void serial_wait_until_sent(struct tty_struct *tty, int timeout) { struct usb_serial_port *port = tty->driver_data; - struct usb_serial *serial = port->serial; dev_dbg(&port->dev, "%s\n", __func__); if (!port->serial->type->wait_until_sent) return; - mutex_lock(&serial->disc_mutex); - if (!serial->disconnected) - port->serial->type->wait_until_sent(tty, timeout); - mutex_unlock(&serial->disc_mutex); + port->serial->type->wait_until_sent(tty, timeout); } static void serial_throttle(struct tty_struct *tty) @@ -438,8 +434,13 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) struct usb_serial_port *port = tty->driver_data; struct tty_port *tport = &port->port; unsigned int close_delay, closing_wait; + int ret = 0; mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } close_delay = jiffies_to_msecs(tport->close_delay) / 10; closing_wait = tport->closing_wait; @@ -452,10 +453,10 @@ static int serial_get_serial(struct tty_struct *tty, struct serial_struct *ss) if (port->serial->type->get_serial) port->serial->type->get_serial(tty, ss); - +out_unlock: mutex_unlock(&tport->mutex); - return 0; + return ret; } static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) @@ -471,6 +472,10 @@ static int serial_set_serial(struct tty_struct *tty, struct serial_struct *ss) closing_wait = msecs_to_jiffies(closing_wait * 10); mutex_lock(&tport->mutex); + if (tty_io_error(tty)) { + ret = -EIO; + goto out_unlock; + } if (!capable(CAP_SYS_ADMIN)) { if (close_delay != tport->close_delay || @@ -498,6 +503,7 @@ static int serial_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; int retval = -ENOIOCTLCMD; dev_dbg(&port->dev, "%s - cmd 0x%04x\n", __func__, cmd); @@ -508,8 +514,21 @@ static int serial_ioctl(struct tty_struct *tty, retval = port->serial->type->tiocmiwait(tty, arg); break; default: - if (port->serial->type->ioctl) + if (!port->serial->type->ioctl) + break; + + if (cmd == TIOCSRS485) + down_write(&tty->termios_rwsem); + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + retval = -EIO; + else retval = port->serial->type->ioctl(tty, cmd, arg); + mutex_unlock(&tport->mutex); + + if (cmd == TIOCSRS485) + up_write(&tty->termios_rwsem); } return retval; @@ -519,25 +538,40 @@ static void serial_set_termios(struct tty_struct *tty, const struct ktermios *old) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->set_termios) - port->serial->type->set_termios(tty, port, old); - else + if (!port->serial->type->set_termios) { tty_termios_copy_hw(&tty->termios, old); + return; + } + + mutex_lock(&tport->mutex); + if (!tty_io_error(tty)) + port->serial->type->set_termios(tty, port, old); + mutex_unlock(&tport->mutex); } static int serial_break(struct tty_struct *tty, int break_state) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->break_ctl) - return port->serial->type->break_ctl(tty, break_state); + if (!port->serial->type->break_ctl) + return -ENOTTY; - return -ENOTTY; + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->break_ctl(tty, break_state); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_proc_show(struct seq_file *m, void *v) @@ -578,24 +612,44 @@ static int serial_proc_show(struct seq_file *m, void *v) static int serial_tiocmget(struct tty_struct *tty) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmget) - return port->serial->type->tiocmget(tty); - return -ENOTTY; + if (!port->serial->type->tiocmget) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmget(tty); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_tiocmset(struct tty_struct *tty, unsigned int set, unsigned int clear) { struct usb_serial_port *port = tty->driver_data; + struct tty_port *tport = &port->port; + int ret; dev_dbg(&port->dev, "%s\n", __func__); - if (port->serial->type->tiocmset) - return port->serial->type->tiocmset(tty, set, clear); - return -ENOTTY; + if (!port->serial->type->tiocmset) + return -ENOTTY; + + mutex_lock(&tport->mutex); + if (tty_io_error(tty)) + ret = -EIO; + else + ret = port->serial->type->tiocmset(tty, set, clear); + mutex_unlock(&tport->mutex); + + return ret; } static int serial_get_icount(struct tty_struct *tty, diff --git a/drivers/usb/serial/xr_serial.c b/drivers/usb/serial/xr_serial.c index 352c765d8803..c08f4aa14a3d 100644 --- a/drivers/usb/serial/xr_serial.c +++ b/drivers/usb/serial/xr_serial.c @@ -850,12 +850,9 @@ static int xr_get_rs485_config(struct tty_struct *tty, struct usb_serial_port *port = tty->driver_data; struct xr_data *data = usb_get_serial_port_data(port); - down_read(&tty->termios_rwsem); - if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) { - up_read(&tty->termios_rwsem); + /* core holds port mutex */ + if (copy_to_user(argp, &data->rs485, sizeof(data->rs485))) return -EFAULT; - } - up_read(&tty->termios_rwsem); return 0; } @@ -871,10 +868,9 @@ static int xr_set_rs485_config(struct tty_struct *tty, return -EFAULT; xr_sanitize_serial_rs485(&rs485); - down_write(&tty->termios_rwsem); + /* core holds termios rwsem and port mutex */ data->rs485 = rs485; xr_set_flow_mode(tty, port, NULL); - up_write(&tty->termios_rwsem); if (copy_to_user(argp, &rs485, sizeof(rs485))) return -EFAULT; From 310c2d4608d8c1a2baa57db1dfd319c5c40cf4a1 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 1 Sep 2026 16:52:46 +0200 Subject: [PATCH 21/63] usb: gadget: midi2: Fix the jack descriptor array sizes The jack in/out descriptor arrays in struct f_midi2_usb_config have the size of MAX_CABLES, which look reasonable -- but it turned out to be incorrect. Namely, the entries for those arrays are added from both inputs and outputs, hence for each loop cycle, it adds two, ended up with as twice as the expected size. This inconsistency may result in potential OOB when a large number of jacks are set up via configfs, although the max number of cables (the loop count) is limited to MAX_CABLES. For addressing it, correct the jack_ins & jack_outs array sizes to twice, MAX_CABLES * 2. Fixes: 856fa444b098 ("usb: gadget: midi2: Dynamically create MIDI 1.0 altset descriptors") Cc: stable Reported-by: syzbot+c35f34092a4bc9855be6@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=c35f34092a4bc9855be6 Link: https://lore.kernel.org/20260826134606.127250-1-eadavis@sina.com Cc: Edward Adam Davis Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260901145512.785142-1-tiwai@suse.de Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_midi2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_midi2.c b/drivers/usb/gadget/function/f_midi2.c index 5b8b18281989..d693de427641 100644 --- a/drivers/usb/gadget/function/f_midi2.c +++ b/drivers/usb/gadget/function/f_midi2.c @@ -1633,8 +1633,8 @@ struct f_midi2_usb_config { /* MIDI 1.0 jacks */ unsigned char jack_in, jack_out, jack_id; - struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES]; - struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES]; + struct usb_midi_in_jack_descriptor jack_ins[MAX_CABLES * 2]; + struct usb_midi_out_jack_descriptor_1 jack_outs[MAX_CABLES * 2]; }; static int append_config(struct f_midi2_usb_config *config, void *d) From 63b269ec537dc0fd26d0bd64872c26860d108c9d Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 8 Sep 2026 06:02:15 +0000 Subject: [PATCH 22/63] usb: typec: ucsi: glink: fix use-after-free of ucsi on remove The pmic_glink client is released by the devres cleanup, which runs after pmic_glink_ucsi_remove() has returned, so its notification callbacks can queue work until then. Work that runs after ucsi_unregister() has been called touches freed state: it dereferences the connector array that ucsi_unregister() freed, or registers and unregisters the instance a second time. Disable both work items with disable_work_sync() before unregistering, and unregister only if the instance is still registered. This issue was found by an in-house static analysis tool. Fixes: 62b5412b1f4a ("usb: typec: ucsi: add PMIC Glink UCSI driver") Cc: stable Assisted-by: Codex:gpt-5.6 Link: https://lore.kernel.org/r/20260227190430.889-1-nathan.c.rebello@gmail.com Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260908060216.616045-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_glink.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index 12e07b9fe622..1db0c88d817e 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -362,6 +362,10 @@ static void pmic_glink_ucsi_destroy(void *data) { struct pmic_glink_ucsi *ucsi = data; + /* Drain the work items before ucsi_destroy() frees the ucsi instance */ + cancel_work_sync(&ucsi->notify_work); + cancel_work_sync(&ucsi->register_work); + /* Protect to make sure we're not in a middle of a transaction from a glink callback */ mutex_lock(&ucsi->lock); ucsi_destroy(ucsi->ucsi); @@ -467,8 +471,15 @@ static void pmic_glink_ucsi_remove(struct auxiliary_device *adev) { struct pmic_glink_ucsi *ucsi = dev_get_drvdata(&adev->dev); - /* Unregister first to stop having read & writes */ - ucsi_unregister(ucsi->ucsi); + /* Callbacks can queue work until devres releases the client */ + disable_work_sync(&ucsi->notify_work); + disable_work_sync(&ucsi->register_work); + + /* register_work may have unregistered the instance already */ + if (ucsi->ucsi_registered) { + ucsi->ucsi_registered = false; + ucsi_unregister(ucsi->ucsi); + } } static const struct auxiliary_device_id pmic_glink_ucsi_id_table[] = { From c5df31f10880737d13b75eff42faee7accb9f956 Mon Sep 17 00:00:00 2001 From: Marian Rotariu Date: Thu, 6 Aug 2026 11:53:02 +0300 Subject: [PATCH 23/63] usb: typec: tipd: don't send GAID when probe fails in APP mode When probe fails after the chip was already in APP mode (booted from EEPROM), the err_reset_controller path unconditionally calls tps->data->reset(), which for tps25750 sends the GAID 4CC command. GAID transitions the chip from APP back to PTCH, causing a second probe attempt that sees PTCH mode and tries to load the firmware from /lib/firmware, instead of reading the EEPROM as it should. The err_reset_controller path can be triggered by -EPROBE_DEFER from the probe sequence. GAID should only be sent when the controller actually loaded firmware at probe time. The abnormal behavior was seen on a tps25751d that has an EEPROM connected to it. Fixes: d49f90822015 ("usb: typec: tipd: add init and reset functions to tipd_data") Cc: stable Signed-off-by: Marian Rotariu Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260806085302.1307606-1-marian.c.rotariu@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index f76f563dc42b..d060150d2e15 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -1792,6 +1792,7 @@ static int tps6598x_probe(struct i2c_client *client) const struct tipd_data *data; struct tps6598x *tps; struct fwnode_handle *fwnode; + bool patch_loaded = false; u32 status; u32 vid = 0; int ret; @@ -1847,6 +1848,7 @@ static int tps6598x_probe(struct i2c_client *client) return ret; if (ret == TPS_MODE_PTCH) { + patch_loaded = true; ret = tps->data->init(tps); if (ret) return ret; @@ -1937,7 +1939,8 @@ static int tps6598x_probe(struct i2c_client *client) tps6598x_write64(tps, TPS_REG_INT_MASK1, 0); err_reset_controller: /* Reset PD controller to remove any applied patch */ - tps->data->reset(tps); + if (patch_loaded) + tps->data->reset(tps); return ret; } From 29480b05e6e226594dc11ccf2a036fbd5cdd0e42 Mon Sep 17 00:00:00 2001 From: Lovekesh Solanki Date: Thu, 3 Sep 2026 16:59:03 +0530 Subject: [PATCH 24/63] usb: hub: use shorter 120ms post resume hold for SS root hubs Holding a USB3 hub PM runtime reference for 200ms at hub resume triggers an AMD platform issue. Users running Android adb report crashes after adb has been polling and waking up the USB subsystem once a second for some time. Vendors are working on a solution. Disabling USB runtime PM is one way to prevent this issue, but it is also proven that reducing the hold time to 120ms in resume also mitigates it. See Link for more details. Reducing the hold time to 120ms for the USB3 roothub is in itself a valid change and optimization, as the current 200ms is excessive: a root hub has no upstream suspended hub whose wake propagation needs to be accounted for, but still need some time for USB3 link training to complete. Keep the 200ms hold for external hubs that commit 8f5b7e2bec1c ("usb: hub: fix detection of high tier USB3 devices behind suspended hubs") is intended for. Reported-by: Mathieu Fluhr Link: https://lore.kernel.org/all/CAPyJwA_D9qw0T72f8zwM1yKjP+To=maVANbcdsWM7yRmbBxYvw@mail.gmail.com/ Cc: stable Fixes: 8f5b7e2bec1c ("usb: hub: fix detection of high tier USB3 devices behind suspended hubs") Signed-off-by: Lovekesh Solanki Acked-by: Mathias Nyman Link: https://patch.msgid.link/20260903112903.542719-1-lovekeshsolanki00@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 3345b3298daf..b446905b48be 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -73,8 +73,11 @@ /* * Give SS hubs 200ms time after wake to train downstream links before * assuming no port activity and allowing hub to runtime suspend back. + * Root hubs have no upstream hub whose wake propagation needs to be + * accounted for, so they need less time, use 120ms for them. */ #define USB_SS_PORT_U0_WAKE_TIME 200 /* ms */ +#define USB_SS_RH_PORT_U0_WAKE_TIME 120 /* ms */ /* Protect struct usb_device->state and ->children members * Note: Both are also protected by ->dev.sem, except that ->state can @@ -1358,7 +1361,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) queue_delayed_work(system_power_efficient_wq, &hub->post_resume_work, - msecs_to_jiffies(USB_SS_PORT_U0_WAKE_TIME)); + msecs_to_jiffies(hdev->parent ? + USB_SS_PORT_U0_WAKE_TIME : + USB_SS_RH_PORT_U0_WAKE_TIME)); return; } From 4c7a1b9f1f1979f2b3b69428ada12680695159c2 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:27 +0000 Subject: [PATCH 25/63] usb: octeon-hcd: fix the FIFO-flush timeout computation cvmx_wait_tx_rx() computes its 100us deadline from (u64)octeon_get_clock_rate - the address of the function, not its return value; the parentheses have been missing since the CVMX_WAIT_FOR_FIELD32 macro became a function. The cast makes it compile silently, and the resulting deadline is effectively infinite. On a healthy controller the flush bit clears on the first read and nothing is noticed. On a controller whose PHY did not come up (for example when the reference-clock configuration is wrong for the board), txfflsh/rxfflsh never clear and probe spins forever in __delay() - observed as a hard hang with a soft-lockup splat on a CN5020 board, where the board watchdog then resets the system with no console output. Call the function, restoring the 100us timeout the code always intended. Fixes: 3e195a80e096 ("Staging: octeon-usb: Replaces CVMX_WAIT_FOR_FIELD32 macro with a function") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-1-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 34ce771fa0f5..7d121909f514 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -578,7 +578,7 @@ static int cvmx_wait_tx_rx(struct octeon_hcd *usb, int fflsh_type) int result; u64 address = CVMX_USBCX_GRSTCTL(usb->index); u64 done = cvmx_get_cycle() + 100 * - (u64)octeon_get_clock_rate / 1000000; + (u64)octeon_get_clock_rate() / 1000000; union cvmx_usbcx_grstctl c; while (1) { From 22bce6d2b3fd9e845d71e0037b6929913301462d Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:28 +0000 Subject: [PATCH 26/63] usb: octeon-hcd: fail the probe when the USB core does not respond If the USBN clock/PHY setup did not actually bring the core up (wrong board reference-clock configuration, for instance), every USBC CSR reads as all-ones and initialization marches on programming garbage, ending in timeouts or a wedge deep in the FIFO flush. Read GHWCFG3 - a hardwired configuration register that can be neither 0 nor all-ones on a live core - right before core initialization, and give up cleanly if the core is not answering. Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-2-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 7d121909f514..5a846c3fc34a 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -853,6 +853,14 @@ static int cvmx_usb_initialize(struct device *dev, * USBC_GAHBCFG[PTXFEMPLVL] * Global interrupt mask, USBC_GAHBCFG[GLBLINTRMSK] = 1 */ + usbcx_gahbcfg.u32 = cvmx_usb_read_csr32(usb, + CVMX_USBCX_GHWCFG3(usb->index)); + if (usbcx_gahbcfg.u32 == 0xffffffff || usbcx_gahbcfg.u32 == 0) { + dev_err(dev, "USB core is not responding (GHWCFG3=0x%08x)\n", + usbcx_gahbcfg.u32); + return -ENODEV; + } + usbcx_gahbcfg.u32 = 0; usbcx_gahbcfg.s.dmaen = !(usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA); From aebbc7d63407f843a1c74806041792db65d69a51 Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Tue, 1 Sep 2026 19:30:29 +0000 Subject: [PATCH 27/63] usb: octeon-hcd: sleep on an hrtimer for far-off periodic transfers cvmx_usb_schedule() enables the SOF interrupt whenever any pipe has next_tx_frame in the future, and leaves it enabled until the transfer is due. An interrupt endpoint keeps such a deadline pending permanently, so a single attached hub (status pipe polled every 256ms) costs an interrupt on every SOF - 8000/s in high-speed mode, forever. On a 300MHz CN5020 that is measurably ~20% of one core spent counting frames. The frame counter is resynchronized from HFNUM at the top of every poll, so the driver does not actually need to see every SOF to know when a deadline arrives. Sleep on an hrtimer when the nearest deadline is more than a few frames away (one high-speed frame is 125us) and keep SOF interrupts only for deadlines that are imminent - or far enough away to risk the 16383-frame HFNUM wrap, where the interrupt path still tracks the counter extension. Measured on a CN5020 board with one 4-port hub attached and idle: 8200 -> 4 USB interrupts/s, with no change in enumeration or transfer behavior. Assisted-by: Claude:claude-opus-5 Signed-off-by: Orgad Shaneh Link: https://patch.msgid.link/20260901193100.1352110-3-orgads@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/octeon-hcd.c | 44 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/octeon-hcd.c b/drivers/usb/host/octeon-hcd.c index 5a846c3fc34a..256e459ff6d8 100644 --- a/drivers/usb/host/octeon-hcd.c +++ b/drivers/usb/host/octeon-hcd.c @@ -378,8 +378,22 @@ struct octeon_hcd { struct cvmx_usb_transaction *active_split; struct cvmx_usb_tx_fifo periodic; struct cvmx_usb_tx_fifo nonperiodic; + struct hrtimer sof_timer; }; +static int cvmx_usb_poll(struct octeon_hcd *usb); + +static enum hrtimer_restart octeon_usb_sof_timer(struct hrtimer *t) +{ + struct octeon_hcd *usb = container_of(t, struct octeon_hcd, sof_timer); + unsigned long flags; + + spin_lock_irqsave(&usb->lock, flags); + cvmx_usb_poll(usb); + spin_unlock_irqrestore(&usb->lock, flags); + return HRTIMER_NORESTART; +} + /* * This macro logically sets a single field in a CSR. It does the sequence * read, modify, and write @@ -1908,6 +1922,7 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) int channel; struct cvmx_usb_pipe *pipe; int need_sof; + u64 min_due; enum cvmx_usb_transfer ttype; if (usb->init_flags & CVMX_USB_INITIALIZE_FLAGS_NO_DMA) { @@ -1948,15 +1963,33 @@ static void cvmx_usb_schedule(struct octeon_hcd *usb, int is_sof) * future that might need to be scheduled */ need_sof = 0; + min_due = ~0ull; for (ttype = CVMX_USB_TRANSFER_CONTROL; ttype <= CVMX_USB_TRANSFER_INTERRUPT; ttype++) { list_for_each_entry(pipe, &usb->active_pipes[ttype], node) { - if (pipe->next_tx_frame > usb->frame_number) { - need_sof = 1; - break; - } + if (pipe->next_tx_frame > usb->frame_number && + pipe->next_tx_frame < min_due) + min_due = pipe->next_tx_frame; } } + if (min_due != ~0ull) { + u64 delta = min_due - usb->frame_number; + + /* + * frame_number is resynced from HFNUM on every poll, so a + * deadline that is many frames away does not need an + * interrupt on every SOF to count them down - sleep on the + * timer instead and keep SOF interrupts for deadlines within + * a few frames. Stay well below the 16383-frame wrap of + * HFNUM. One (micro)frame is 125us in high-speed mode. + */ + if (delta <= 4 || delta > 8000) + need_sof = 1; + else + hrtimer_start(&usb->sof_timer, + ns_to_ktime((delta - 2) * 125000), + HRTIMER_MODE_REL); + } USB_SET_FIELD32(CVMX_USBCX_GINTMSK(usb->index), cvmx_usbcx_gintmsk, sofmsk, need_sof); } @@ -3646,6 +3679,8 @@ static int octeon_usb_probe(struct platform_device *pdev) usb = (struct octeon_hcd *)hcd->hcd_priv; spin_lock_init(&usb->lock); + hrtimer_setup(&usb->sof_timer, octeon_usb_sof_timer, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); usb->init_flags = initialize_flags; @@ -3696,6 +3731,7 @@ static void octeon_usb_remove(struct platform_device *pdev) unsigned long flags; usb_remove_hcd(hcd); + hrtimer_cancel(&usb->sof_timer); spin_lock_irqsave(&usb->lock, flags); status = cvmx_usb_shutdown(usb); spin_unlock_irqrestore(&usb->lock, flags); From 3d9bbf0ffeb9b7ac1b6ad06b52e037870a7e0c63 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Wed, 2 Sep 2026 20:24:04 +0530 Subject: [PATCH 28/63] usb: dwc3: am62: Fix runtime PM usage count leak on probe error path dwc3_ti_probe() takes a runtime PM reference with pm_runtime_get_noresume() before creating the dwc3 core child device, and releases it with pm_runtime_put_autosuspend() once probe has succeeded. The err_pm_disable error path only disables runtime PM, so the reference taken a few lines earlier is never dropped. The usage counter lives in struct device and is not reset when the driver is unbound, so the leaked reference outlives the failed probe. If the device is probed again, through a manual rebind or a module reload, the counter starts at one instead of zero and the pm_runtime_put_autosuspend() on the success path can no longer bring it back down. The wrapper then stays runtime resumed for good and autosuspend never kicks in. Drop the reference before disabling runtime PM, matching the ordering already used in dwc3_ti_remove(). Fixes: e8784c0aec03 ("drivers: usb: dwc3: Add AM62 USB wrapper driver") Cc: stable Assisted-by: Claude:claude-opus-5 Signed-off-by: Radhey Shyam Pandey Acked-by: Thinh Nguyen Link: https://patch.msgid.link/20260902-dwc3-am62-rpm-fix-v1-1-7a2807e33307@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/dwc3-am62.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/dwc3/dwc3-am62.c b/drivers/usb/dwc3/dwc3-am62.c index 632634d6e81e..d03b3950ce04 100644 --- a/drivers/usb/dwc3/dwc3-am62.c +++ b/drivers/usb/dwc3/dwc3-am62.c @@ -299,6 +299,7 @@ static int dwc3_ti_probe(struct platform_device *pdev) err_pm_disable: clk_disable_unprepare(am62->usb2_refclk); + pm_runtime_put_noidle(dev); pm_runtime_disable(dev); pm_runtime_set_suspended(dev); return ret; From fd772af9512cdecb83de34a461801533828e77fd Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Fri, 4 Sep 2026 14:53:23 +0800 Subject: [PATCH 29/63] USB: dwc2: shut down wakeup timer before freeing HCD state dwc2_wakeup_detected() accesses the DWC2 host state and can rearm the wakeup timer. dwc2_hcd_free() currently deletes the timer only after freeing host-owned state, and timer_delete() does not synchronize a callback or prevent it from being queued again. Stop and shut down the timer in dwc2_hcd_release(), before the HCD resources are freed. This covers both the HCD initialization error path and normal HCD removal. Fixes: 7359d482eb4d ("staging: HCD files for the DWC2 driver") Cc: stable Assisted-by: Codex:GPT-5 Signed-off-by: Runyu Xiao Reviewed-by: Thinh Nguyen Link: https://patch.msgid.link/20260904065323.4047026-1-runyu.xiao@seu.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc2/hcd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 2414291aa908..cd0dc876b421 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -5082,14 +5082,13 @@ static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) } cancel_work_sync(&hsotg->phy_reset_work); - - timer_delete(&hsotg->wkp_timer); } static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) { /* Turn off all host-specific interrupts */ dwc2_disable_host_interrupts(hsotg); + timer_shutdown_sync(&hsotg->wkp_timer); dwc2_hcd_free(hsotg); } From 90156585765b29304f07b0e4a1c56a969876fcfc Mon Sep 17 00:00:00 2001 From: Amit Sunil Dhamne Date: Thu, 3 Sep 2026 00:22:21 +0000 Subject: [PATCH 30/63] usb: typec: tcpm: recover after failure to start the frs ams Reset the port if the tcpm fails to start the FAST_ROLE_SWAP AMS by initiating error recovery on it. This helps in cases where some cables (incorrectly) signal an FRS to an FRS capable port during disconnection. The TCPC autonomously starts sourcing VBUS on detecting the FRS signal. However, the VBUS sourcing is left on when the FAST_ROLE_SWAP AMS fails to start (as tcpm_sink_tx_ok is 0 as CC is open due to the cable disconnect). This is because the code sets the state to INVALID_STATE without resetting the port state. Log snippet before changes: [ 101.401960] AMS FAST_ROLE_SWAP start [ 101.401971] Sink TX No Go [ 101.401982] sourcing vbus [ 101.401987] VBUS on [ 101.402159] VBUS on [ 109.257809] CC1: 0 -> 0, CC2: 5 -> 0 [state SNK_READY, polarity 1, disconnected] [ 111.267442] VBUS on After changes: [ 70.541211] AMS FAST_ROLE_SWAP start [ 70.541220] Sink TX No Go [ 70.541228] state change SNK_READY -> ERROR_RECOVERY [rev3 NONE_AMS] [ 70.541362] VBUS on [ 70.541365] sourcing vbus [ 70.541367] VBUS on [ 70.541374] state change ERROR_RECOVERY -> PORT_RESET [rev3 NONE_AMS] [ 70.541410] disable vbus discharge ret:0 [ 70.543028] Setting usb_comm capable false [ 70.544009] Setting voltage/current limit 0 mV 0 mA [ 70.544034] polarity 0 [ 70.544239] Requesting mux state 0, usb-role 0, orientation 0 [ 70.555550] cc:=0 [ 70.555595] pending state change PORT_RESET -> PORT_RESET_WAIT_OFF @ 100 ms [rev3 NONE_AMS] [ 70.555697] VBUS off [ 70.555702] VBUS VSAFE0V [ 70.555762] CC1: 5 -> 0, CC2: 0 -> 0 [state PORT_RESET, polarity 0, disconnected] [ 70.587794] VBUS off [ 70.587799] VBUS VSAFE0V [ 70.655672] state change PORT_RESET -> PORT_RESET_WAIT_OFF [delayed 100 ms] [ 70.655682] state change PORT_RESET_WAIT_OFF -> SNK_UNATTACHED [rev3 NONE_AMS] [ 70.655686] Start toggling [ 70.656274] CC1: 0 -> 0, CC2: 0 -> 0 [state TOGGLING, polarity 0, disconnected] Fixes: 0908c5aca31e ("usb: typec: tcpm: AMS and Collision Avoidance") Cc: stable Signed-off-by: Amit Sunil Dhamne Reviewed-by: Badhri Jagan Sridharan Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260903-frs-error-handling-v1-1-ad0fee541847@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 2d6b14aa2085..8f80bdd62f94 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -7113,7 +7113,7 @@ static void tcpm_pd_event_handler(struct kthread_work *work) port->upcoming_state = FR_SWAP_SEND; ret = tcpm_ams_start(port, FAST_ROLE_SWAP); if (ret == -EAGAIN) - port->upcoming_state = INVALID_STATE; + tcpm_set_state(port, ERROR_RECOVERY, 0); } else { tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready"); } From 8a0200d14acd9db388b85cd016e68a4e60d900ec Mon Sep 17 00:00:00 2001 From: Syed Labeeq Sajid Bukhari Date: Thu, 10 Sep 2026 19:03:43 +0500 Subject: [PATCH 31/63] usb: storage: sierra_ms: reject short SWoC info transfers sierra_get_swoc_info() requests sizeof(struct swoc_info) (60) bytes from the device via usb_control_msg(), but its callers only treat a negative return value as failure. A device that answers the vendor-specific GetSwocInfo request with a short IN transfer is therefore accepted, leaving the tail of the freshly allocated (kmalloc(), non-zeroing) swoc_info buffer uninitialized. truinst_show() subsequently prints swocInfo->rev, swocInfo->LinuxSKU and swocInfo->LinuxVer from that buffer into the world-readable (0444) "truinst" sysfs attribute. An emulated/malicious USB device (VID 0x1199, PID 0x0fff) can exploit this to disclose up to 5 bytes of stale kernel heap memory (kmalloc-64) to unprivileged userspace, once per sysfs read, indefinitely. On kernels built without init_on_alloc this leaks recently freed heap contents. Only accept the transfer when the full structure was received. sierra_ms_init() already retries failed queries, so well-behaved devices are unaffected. Fixes: 32fe5e393455 ("USB Storage Sierra: TRU-Install feature update") Cc: stable Signed-off-by: Syed Labeeq Sajid Bukhari Assisted-by: Kimi:K2 [Kimi Code CLI] Link: https://patch.msgid.link/20260910140343.49371-1-syedlabeeq@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/storage/sierra_ms.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/usb/storage/sierra_ms.c b/drivers/usb/storage/sierra_ms.c index c5e3eeeb2144..992685ca6f1a 100644 --- a/drivers/usb/storage/sierra_ms.c +++ b/drivers/usb/storage/sierra_ms.c @@ -77,6 +77,13 @@ static int sierra_get_swoc_info(struct usb_device *udev, sizeof(struct swoc_info), /* __u16 size */ USB_CTRL_SET_TIMEOUT); /* int timeout */ + /* + * A short IN transfer leaves the tail of swocInfo uninitialized; + * only a full transfer is valid. + */ + if (result != sizeof(struct swoc_info)) + return -EIO; + swocInfo->LinuxSKU = le16_to_cpu(swocInfo->LinuxSKU); swocInfo->LinuxVer = le16_to_cpu(swocInfo->LinuxVer); return result; From c4b27b3605788bbf9e4251f3cd4ab94645ab036f Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sun, 6 Sep 2026 18:49:41 +0530 Subject: [PATCH 32/63] usb: typec: tipd: fix uninitialized typec_partner_desc in tps6598x_connect() tps6598x_connect() passes a stack-allocated typec_partner_desc to typec_register_partner() after initializing only usb_pd, accessory and identity. typec_register_partner() copies attach and deattach from the descriptor into the partner. With those fields left unset, garbage function pointers may be stored and later invoked from typec_partner_link_device() when a USB device is linked to the port. Uninitialized pd_revision and usb_capability similarly leak stack data through partner sysfs. Zero-initialize the descriptor so optional callbacks remain NULL and the remaining fields are zero. Fixes: 0a4c005bd171 ("usb: typec: driver for TI TPS6598x USB Power Delivery controllers") Cc: stable # 5.15+ Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260906131942.83153-2-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index d060150d2e15..e55c3dc1aa69 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -343,7 +343,7 @@ static void tps6598x_set_data_role(struct tps6598x *tps, static int tps6598x_connect(struct tps6598x *tps, u32 status) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; enum typec_pwr_opmode mode; int ret; @@ -354,7 +354,6 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status) desc.usb_pd = mode == TYPEC_PWR_MODE_PD; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) { ret = tps6598x_read_partner_identity(tps); From 88c7e458af53ff781a3f1051708395892b325576 Mon Sep 17 00:00:00 2001 From: Radhey Shyam Pandey Date: Sun, 6 Sep 2026 18:49:42 +0530 Subject: [PATCH 33/63] usb: typec: tipd: fix uninitialized typec_partner_desc in cd321x_update_work() cd321x_update_work() passes a stack-allocated typec_partner_desc to typec_register_partner() after initializing only usb_pd, accessory and identity. typec_register_partner() copies attach and deattach from the descriptor into the partner. With those fields left unset, garbage function pointers may be stored and later invoked from typec_partner_link_device() when a USB device is linked to the port. Uninitialized pd_revision and usb_capability similarly leak stack data through partner sysfs. Zero-initialize the descriptor so optional callbacks remain NULL and the remaining fields are zero. Fixes: 82432bbfb9e8 ("usb: typec: tipd: Handle mode transitions for CD321x") Cc: stable # 6.18+ Reviewed-by: Heikki Krogerus Signed-off-by: Radhey Shyam Pandey Link: https://patch.msgid.link/20260906131942.83153-3-radhey.shyam.pandey@amd.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tipd/core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c index e55c3dc1aa69..483539185177 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.c @@ -849,11 +849,10 @@ static void cd321x_update_work(struct work_struct *work) /* Set up partner if we were previously disconnected (or changed). */ if (!tps->partner) { - struct typec_partner_desc desc; + struct typec_partner_desc desc = { }; desc.usb_pd = is_pd; desc.accessory = TYPEC_ACCESSORY_NONE; /* XXX: handle accessories */ - desc.identity = NULL; if (desc.usb_pd) desc.identity = &st.partner_identity; From be4219dd98608736e13e0b790ef742b76a13254d Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Mon, 7 Sep 2026 11:51:30 +0200 Subject: [PATCH 34/63] USB: cdc-acm: fix racy TIOCMIWAIT implementation Check the wakeup condition after adding the task to the waitqueue and updating the task state to avoid missing a racing modem status update or disconnect. Store the old icount on entry and do not update it in the completion handler to avoid missing events or reporting spurious ones (due to modem status changes before TIOCMIWAIT was called). Fixes: 5a6a62bdb925 ("cdc-acm: add TIOCMIWAIT") Cc: stable Cc: Oliver Neukum Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260907095130.130636-1-johan@kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 36 +++++++++++++++++++----------------- drivers/usb/class/cdc-acm.h | 1 - 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 7bc5329fa3ed..21417b4c5154 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -331,7 +331,6 @@ static void acm_process_notification(struct acm *acm, unsigned char *buf) spin_lock_irqsave(&acm->read_lock, flags); acm->ctrlin = newctrl; - acm->oldcount = acm->iocount; if (difference & USB_CDC_SERIAL_STATE_DSR) acm->iocount.dsr++; @@ -1027,11 +1026,16 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) DECLARE_WAITQUEUE(wait, current); struct async_icount old, new; - do { + spin_lock_irq(&acm->read_lock); + old = acm->iocount; + spin_unlock_irq(&acm->read_lock); + + add_wait_queue(&acm->wioctl, &wait); + for (;;) { + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irq(&acm->read_lock); - old = acm->oldcount; new = acm->iocount; - acm->oldcount = new; spin_unlock_irq(&acm->read_lock); if ((arg & TIOCM_DSR) && @@ -1044,22 +1048,20 @@ static int wait_serial_change(struct acm *acm, unsigned long arg) old.rng != new.rng) break; - add_wait_queue(&acm->wioctl, &wait); - set_current_state(TASK_INTERRUPTIBLE); - schedule(); - remove_wait_queue(&acm->wioctl, &wait); if (acm->disconnected) { - if (arg & TIOCM_CD) - break; - else - rv = -ENODEV; - } else { - if (signal_pending(current)) - rv = -ERESTARTSYS; + rv = -ENODEV; + break; } - } while (!rv); - + schedule(); + + if (signal_pending(current)) { + rv = -ERESTARTSYS; + break; + } + } + __set_current_state(TASK_RUNNING); + remove_wait_queue(&acm->wioctl, &wait); return rv; } diff --git a/drivers/usb/class/cdc-acm.h b/drivers/usb/class/cdc-acm.h index 01f448a783c0..d245e60eb9f5 100644 --- a/drivers/usb/class/cdc-acm.h +++ b/drivers/usb/class/cdc-acm.h @@ -90,7 +90,6 @@ struct acm { unsigned int ctrlin; /* input control lines (DCD, DSR, RI, break, overruns) */ unsigned int ctrlout; /* output control lines (DTR, RTS) */ struct async_icount iocount; /* counters for control line changes */ - struct async_icount oldcount; /* for comparison of counter */ wait_queue_head_t wioctl; /* for ioctl */ unsigned int writesize; /* max packet size for the output bulk endpoint */ unsigned int readsize,ctrlsize; /* buffer sizes for freeing */ From 8c51ea651d043c786c37668c51624bd6b338a0ef Mon Sep 17 00:00:00 2001 From: Pannarat Wiriyaarritham Date: Thu, 10 Sep 2026 09:33:37 +0700 Subject: [PATCH 35/63] usb: typec: ucsi: acpi: Assume UCSI 1.2 on Acer Nitro ANV15-41 The Acer Nitro ANV15-41 exposes a functional UCSI ACPI PPM but reports a UCSI VERSION value of zero. ucsi_register() rejects a zero version with -ENODEV, leaving the system without registered USB Type-C connectors. The PPM works correctly when using the UCSI 1.2 layout. With UCSI 1.2 assumed, both Type-C connectors register correctly and USB Power Delivery negotiation works. Add a DMI-specific UCSI operation for the Acer Nitro ANV15-41 that substitutes UCSI 1.2 only when firmware reports a zero version. Preserve any valid non-zero firmware version. Tested on an Acer Nitro ANV15-41 with BIOS V1.51. Fixes: c1b0bc2dabfa ("usb: typec: Add support for UCSI interface") Cc: stable Assisted-by: LLM Signed-off-by: Pannarat Wiriyaarritham Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260910023338.31816-1-pannarat.wiriyaarritham@danielcorp.dev Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi_acpi.c | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c index 18286d3e9cc5..5697ccc24ec3 100644 --- a/drivers/usb/typec/ucsi/ucsi_acpi.c +++ b/drivers/usb/typec/ucsi/ucsi_acpi.c @@ -121,6 +121,33 @@ static const struct ucsi_operations ucsi_acpi_ops = { .async_control = ucsi_acpi_async_control }; +static int ucsi_acer_read_version(struct ucsi *ucsi, u16 *version) +{ + struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); + int ret; + + ret = ucsi_acpi_read_version(ucsi, version); + if (ret) + return ret; + + if (!*version) { + dev_warn(ua->dev, "UCSI version is zero, assuming 1.2\n"); + *version = UCSI_VERSION_1_2; + } + + return 0; +} + +static const struct ucsi_operations ucsi_acer_ops = { + .read_version = ucsi_acer_read_version, + .read_cci = ucsi_acpi_read_cci, + .poll_cci = ucsi_acpi_poll_cci, + .read_message_in = ucsi_acpi_read_message_in, + .write_message_out = ucsi_acpi_write_message_out, + .sync_control = ucsi_sync_control_common, + .async_control = ucsi_acpi_async_control +}; + static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, void *val, size_t len, void *msg_out, size_t msg_out_size) @@ -162,6 +189,13 @@ static const struct ucsi_operations ucsi_gram_ops = { }; static const struct dmi_system_id ucsi_acpi_quirks[] = { + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro ANV15-41"), + }, + .driver_data = (void *)&ucsi_acer_ops, + }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"), From 80756e263846ab694984e749e8c626897331438f Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Wed, 9 Sep 2026 10:07:37 +0900 Subject: [PATCH 36/63] thunderbolt: Reject oversized XDomain properties responses tb_xdp_properties_request() allocates room for 45 data dwords in its 252-byte response buffer. The XDomain length field is six bits wide, however, and a malicious peer can set it to 63. After the fixed response fields are subtracted, the driver treats this as 48 data dwords. Commit 322e93448d90 ("thunderbolt: Clamp XDomain response data copy to allocation size") only bounds the copy against data_len. If data_len is at least 48, memcpy() reads 192 bytes from the 180-byte res->data array, causing a 12-byte heap out-of-bounds read. Commit 4db2bd2ed478 ("thunderbolt: Limit XDomain response copy to actual frame size") limits the earlier copy but does not constrain this header-derived length. Reject response data lengths that exceed the allocated source buffer before copying them into the assembled property block. Fixes: d1ff70241a27 ("thunderbolt: Add support for XDomain discovery protocol") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Signed-off-by: Mika Westerberg --- drivers/thunderbolt/xdomain.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c index 05442df0e99c..b187a60663f5 100644 --- a/drivers/thunderbolt/xdomain.c +++ b/drivers/thunderbolt/xdomain.c @@ -377,6 +377,10 @@ static int tb_xdp_properties_request(struct tb_ctl *ctl, u64 route, len += sizeof(res->hdr.xd_hdr) / 4; len -= sizeof(*res) / 4; + if (len > TB_XDP_PROPERTIES_MAX_DATA_LENGTH) { + ret = -EINVAL; + goto err; + } if (res->offset != req.offset) { ret = -EINVAL; From c230bc2b926d137ed06cd4538dfccf57b80fb9ea Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 15 Sep 2026 15:46:23 -0500 Subject: [PATCH 37/63] Revert "thunderbolt: Add quirk to reset host interface on DMA path teardown for AMD USB4 routers" This commit has caused a deadlock at shutdown. A proper fix with another approach will be coming later. Revert commit f1de1fc5f632cdeae1f5c2984572ab710d4dfcaa for now. Reported-by: juan.martinez@amd.com Closes: https://lore.kernel.org/linux-usb/20260825214237.4179813-1-juan.martinez@amd.com/ Cc: Sanath S Cc: Basavaraj Natikar Signed-off-by: Mario Limonciello Signed-off-by: Mika Westerberg --- drivers/thunderbolt/domain.c | 26 +------------------------- drivers/thunderbolt/nhi.c | 26 -------------------------- drivers/thunderbolt/nhi.h | 21 ++------------------- drivers/thunderbolt/nhi_regs.h | 4 ---- drivers/thunderbolt/pci.c | 23 ----------------------- 5 files changed, 3 insertions(+), 97 deletions(-) diff --git a/drivers/thunderbolt/domain.c b/drivers/thunderbolt/domain.c index 12c88509a54f..24611f05b3cd 100644 --- a/drivers/thunderbolt/domain.c +++ b/drivers/thunderbolt/domain.c @@ -788,21 +788,6 @@ int tb_domain_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, transmit_ring, receive_path, receive_ring); } -static void tb_domain_reset_interface(struct tb *tb) -{ - struct tb_nhi *nhi = tb->nhi; - - if (!nhi->ops->reset_interface) - return; - - guard(mutex)(&tb->lock); - - /* The reset clears the ring state so stop the control channel */ - tb_ctl_stop(tb->ctl); - nhi->ops->reset_interface(nhi); - tb_ctl_start(tb->ctl); -} - /** * tb_domain_disconnect_xdomain_paths() - Disable DMA paths for XDomain * @tb: Domain disabling the DMA paths @@ -825,20 +810,11 @@ int tb_domain_disconnect_xdomain_paths(struct tb *tb, struct tb_xdomain *xd, int transmit_path, int transmit_ring, int receive_path, int receive_ring) { - int ret; - if (!tb->cm_ops->disconnect_xdomain_paths) return -ENOTSUPP; - ret = tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, + return tb->cm_ops->disconnect_xdomain_paths(tb, xd, transmit_path, transmit_ring, receive_path, receive_ring); - if (ret) - return ret; - - if (tb->nhi->quirks & QUIRK_RESET_DMA_ON_TEARDOWN) - tb_domain_reset_interface(tb); - - return 0; } static int disconnect_xdomain(struct device *dev, void *data) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d4d1efa2afa0..be18f7b6595a 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -1180,32 +1180,6 @@ static void nhi_reset(struct tb_nhi *nhi) dev_warn(nhi->dev, "timeout resetting host router\n"); } -/** - * nhi_reset_interface() - Reset the host interface - * @nhi: Host interface to reset - * - * Brings the registers in the memory BAR back to their default state and - * clears the End-to-End Flow Control state. The caller is responsible for - * stopping the control channel over the reset because it clears the ring - * state as well. - */ -void nhi_reset_interface(struct tb_nhi *nhi) -{ - u32 val; - - val = ioread32(nhi->iobase + REG_CAPS); - /* Only v1 host interfaces implement the reset */ - if (FIELD_GET(REG_CAPS_VERSION_MASK, val) >= REG_CAPS_VERSION_2) - return; - - dev_dbg(nhi->dev, "issuing host interface reset\n"); - - iowrite32(REG_HOST_INTERFACE_RESET_RST, - nhi->iobase + REG_HOST_INTERFACE_RESET); - /* Wait for tHIReset (10 ms) to complete */ - usleep_range(10000, 20000); -} - static struct tb *nhi_select_cm(struct tb_nhi *nhi) { struct tb *tb; diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h index f72d6b274501..d488eadadfce 100644 --- a/drivers/thunderbolt/nhi.h +++ b/drivers/thunderbolt/nhi.h @@ -36,8 +36,6 @@ irqreturn_t nhi_msi(int irq, void *data); irqreturn_t ring_msix(int irq, void *data); int nhi_probe(struct tb_nhi *nhi); void nhi_shutdown(struct tb_nhi *nhi); -void nhi_reset_interface(struct tb_nhi *nhi); - extern const struct dev_pm_ops nhi_pm_ops; /** @@ -54,7 +52,6 @@ extern const struct dev_pm_ops nhi_pm_ops; * @release_ring_irq: NHI specific interrupt release hook * @is_present: Whether the device is currently present on the parent bus * @init_interrupts: NHI specific interrupt initialization hook - * @reset_interface: Resets the host interface */ struct tb_nhi_ops { int (*init)(struct tb_nhi *nhi); @@ -69,7 +66,6 @@ struct tb_nhi_ops { void (*release_ring_irq)(struct tb_ring *ring); bool (*is_present)(struct tb_nhi *nhi); int (*init_interrupts)(struct tb_nhi *nhi); - void (*reset_interface)(struct tb_nhi *nhi); }; /* @@ -120,24 +116,11 @@ struct tb_nhi_ops { #define PCI_DEVICE_ID_INTEL_PTL_P_NHI0 0xe433 #define PCI_DEVICE_ID_INTEL_PTL_P_NHI1 0xe434 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI0 0x1120 -#define PCI_DEVICE_ID_AMD_1AH_M60H_NHI1 0x1121 -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI0 0x113b -#define PCI_DEVICE_ID_AMD_1AH_M68H_NHI1 0x113c -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI0 0x1155 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI1 0x1158 -#define PCI_DEVICE_ID_AMD_1AH_M80H_NHI2 0x1159 -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI0 0x151c -#define PCI_DEVICE_ID_AMD_1AH_M24H_NHI1 0x151d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI0 0x158d -#define PCI_DEVICE_ID_AMD_1AH_M70H_NHI1 0x158e - #define PCI_CLASS_SERIAL_USB_USB4 0x0c0340 /* Host interface quirks */ -#define QUIRK_AUTO_CLEAR_INT BIT(0) -#define QUIRK_E2E BIT(1) -#define QUIRK_RESET_DMA_ON_TEARDOWN BIT(2) +#define QUIRK_AUTO_CLEAR_INT BIT(0) +#define QUIRK_E2E BIT(1) /* * Minimal number of vectors when we use MSI-X. Two for control channel diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h index 99df60b6db36..d6a197fabc74 100644 --- a/drivers/thunderbolt/nhi_regs.h +++ b/drivers/thunderbolt/nhi_regs.h @@ -115,10 +115,6 @@ struct ring_desc { #define REG_CAPS_VERSION_MASK GENMASK(23, 16) #define REG_CAPS_VERSION_2 0x40 -/* Host Interface Reset - resets TX/RX rings and E2E flow control counters */ -#define REG_HOST_INTERFACE_RESET 0x39858 -#define REG_HOST_INTERFACE_RESET_RST BIT(0) - #define REG_DMA_MISC 0x39864 #define REG_DMA_MISC_INT_AUTO_CLEAR BIT(2) #define REG_DMA_MISC_DISABLE_AUTO_CLEAR BIT(17) diff --git a/drivers/thunderbolt/pci.c b/drivers/thunderbolt/pci.c index 99333729f3c2..8462ccb59b7e 100644 --- a/drivers/thunderbolt/pci.c +++ b/drivers/thunderbolt/pci.c @@ -62,27 +62,6 @@ static void nhi_pci_check_quirks(struct tb_nhi_pci *nhi_pci) nhi->quirks |= QUIRK_E2E; break; } - } else if (pdev->vendor == PCI_VENDOR_ID_AMD) { - switch (pdev->device) { - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M60H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M68H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M80H_NHI2: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M24H_NHI1: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI0: - case PCI_DEVICE_ID_AMD_1AH_M70H_NHI1: - /* - * These AMD hosts may hang the Tx ring when the - * DMA paths are torn down so they need the host - * interface reset after each teardown. - */ - nhi->quirks |= QUIRK_RESET_DMA_ON_TEARDOWN; - break; - } } } @@ -279,7 +258,6 @@ static const struct tb_nhi_ops pci_nhi_default_ops = { .shutdown = nhi_pci_release_irq, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; /* Ice Lake specific NHI operations */ @@ -463,7 +441,6 @@ static const struct tb_nhi_ops icl_nhi_ops = { .release_ring_irq = nhi_pci_ring_release_msix, .is_present = nhi_pci_is_present, .init_interrupts = nhi_pci_init_msi, - .reset_interface = nhi_reset_interface, }; static int nhi_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) From d84e27d87738634b92001500435ae4dadba10a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Carrat?= Date: Thu, 10 Sep 2026 18:26:09 +0200 Subject: [PATCH 38/63] USB: serial: cp210x: add Corsair AX1500i Power Supply MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Corsair AX1500i power supply exposes its Corsair Link monitoring interface on a mini-USB port through an on-board CP2103, using a Corsair-specific product ID. Without the ID in the table no driver binds and no tty is created. Tested by forcing the driver association with echo 1b1c 1c02 > /sys/bus/usb-serial/drivers/cp210x/new_id and then setting the serial port to 115200 8N1. Adding the ID is sufficient to talk to the device: the tty reaches a USB-to-SMBus bridge sitting behind the UART, which identifies itself as "USB to SMB Bridge (Firmware by Ross Fosler)" version 0.9 and relays PMBus reads to the power supply controller. Input voltage and current, input and output power, internal temperature and fan speed were all read back on an AX1500i. [ 7.806181] usb 3-14: New USB device found, idVendor=1b1c, idProduct=1c02, bcdDevice= 1.00 [ 698.526754] usbcore: registered new interface driver cp210x [ 698.526767] usbserial: USB Serial support registered for cp210x [ 698.527863] cp210x 3-14:1.0: cp210x converter detected [ 698.529263] usb 3-14: cp210x converter now attached to ttyUSB0 The related AX1600i (1b1c:1c11, iProduct "USB API") is expected to be the same kind of device but has not been tested here, so it is not added. Cc: stable@vger.kernel.org Signed-off-by: JĂ©rĂ©my Carrat Signed-off-by: Johan Hovold --- drivers/usb/serial/cp210x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index 333b8b463704..c3f9add98333 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -223,6 +223,7 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x19CF, 0x3000) }, /* Parrot NMEA GPS Flight Recorder */ { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */ { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */ + { USB_DEVICE(0x1B1C, 0x1C02) }, /* Corsair AX1500i Power Supply */ { USB_DEVICE(0x1B93, 0x1013) }, /* Phoenix Contact UPS Device */ { USB_DEVICE(0x1BA4, 0x0002) }, /* Silicon Labs 358x factory default */ { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */ From afc235a217360c6859068d2e37a4df305c4d1be2 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 15 Sep 2026 14:40:11 +0200 Subject: [PATCH 39/63] USB: serial: ssu100: fix baud rate overflow The requested baud rate is incorrectly truncated to 16 bits so that line speeds above 65535 bps cannot be set. Use 32 bits for the rate and remainder while rejecting rates outside of [50,460800] to avoid having the divisor or remainder overflow. This issue was flagged by an LLM. Fixes: 52af95459939 ("USB: add USB serial ssu100 driver") Cc: stable@vger.kernel.org # 2.6.36 Cc: Bill Pemberton Assisted-by: LLM Signed-off-by: Johan Hovold --- drivers/usb/serial/ssu100.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/usb/serial/ssu100.c b/drivers/usb/serial/ssu100.c index b0d51558b73c..8c5b9ef708b3 100644 --- a/drivers/usb/serial/ssu100.c +++ b/drivers/usb/serial/ssu100.c @@ -32,6 +32,7 @@ #define SERIAL_EVEN_PARITY (UART_LCR_PARITY | UART_LCR_EPAR) +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 460800 #define ATC_DISABLED 0x00 @@ -217,9 +218,10 @@ static void ssu100_set_termios(struct tty_struct *tty, { struct usb_device *dev = port->serial->dev; struct ktermios *termios = &tty->termios; - u16 baud, divisor, remainder; + speed_t baud, remainder; unsigned int cflag = termios->c_cflag; u16 urb_value = 0; /* will hold the new flags */ + u16 divisor; int result; if (cflag & PARENB) { @@ -235,6 +237,18 @@ static void ssu100_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + dev_dbg(&port->dev, "%s - got baud = %d\n", __func__, baud); @@ -310,7 +324,7 @@ static int ssu100_open(struct tty_struct *tty, struct usb_serial_port *port) dev_dbg(&port->dev, "%s - set uart failed\n", __func__); if (tty) - ssu100_set_termios(tty, port, &tty->termios); + ssu100_set_termios(tty, port, NULL); return usb_serial_generic_open(tty, port); } From ec06546b43b612cefc851558981b4955c0ef9e46 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Tue, 15 Sep 2026 14:40:12 +0200 Subject: [PATCH 40/63] USB: serial: quatech2: fix baud rate overflow The requested baud rate is incorrectly truncated to 16 bits so that line speeds above 65535 bps cannot be set. Use 32 bits for the rate while rejecting rates outside of [50,921600] to avoid having the 16-bit divisor overflow. Fixes: f7a33e608d9a ("USB: serial: add quatech2 usb to serial driver") Cc: stable@vger.kernel.org # 3.5 Cc: Bill Pemberton Signed-off-by: Johan Hovold --- drivers/usb/serial/quatech2.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/usb/serial/quatech2.c b/drivers/usb/serial/quatech2.c index dc2b39810a26..3b4df3ac7c73 100644 --- a/drivers/usb/serial/quatech2.c +++ b/drivers/usb/serial/quatech2.c @@ -52,6 +52,7 @@ #define QT2_XMIT_FLUSH 0x05 /* no following info */ #define QT2_CONTROL_ESCAPE 0xff /* pass through previous 2 control bytes */ +#define MIN_BAUD_RATE 50 #define MAX_BAUD_RATE 921600 #define DEFAULT_BAUD_RATE 9600 @@ -156,7 +157,7 @@ static inline int calc_baud_divisor(int baudrate) static inline int qt2_set_port_config(struct usb_device *dev, unsigned char port_number, - u16 baudrate, u16 lcr) + speed_t baudrate, u16 lcr) { int divisor = calc_baud_divisor(baudrate); u16 index = ((u16) (lcr << 8) | (u16) (port_number)); @@ -257,9 +258,9 @@ static void qt2_set_termios(struct tty_struct *tty, struct usb_device *dev = port->serial->dev; struct qt2_port_private *port_priv; struct ktermios *termios = &tty->termios; - u16 baud; unsigned int cflag = termios->c_cflag; u16 new_lcr = 0; + speed_t baud; int status; port_priv = usb_get_serial_port_data(port); @@ -277,6 +278,18 @@ static void qt2_set_termios(struct tty_struct *tty, if (!baud) baud = 9600; + if (baud < MIN_BAUD_RATE || baud > MAX_BAUD_RATE) { + if (old_termios) + baud = tty_termios_baud_rate(old_termios); + else + baud = clamp(baud, MIN_BAUD_RATE, MAX_BAUD_RATE); + + tty_encode_baud_rate(tty, baud, baud); + + if (!baud) + baud = 9600; + } + status = qt2_set_port_config(dev, port_priv->device_port, baud, new_lcr); if (status < 0) @@ -373,7 +386,7 @@ static int qt2_open(struct tty_struct *tty, struct usb_serial_port *port) port_priv->device_port = (u8) device_port; if (tty) - qt2_set_termios(tty, port, &tty->termios); + qt2_set_termios(tty, port, NULL); return 0; From 98d00deb6ae4a83f567444215302ab7b101ed439 Mon Sep 17 00:00:00 2001 From: Igor Paunovic Date: Mon, 7 Sep 2026 20:30:41 +0200 Subject: [PATCH 41/63] usb: typec: tcpm: fix use-after-free of the kthread worker on port unregister tcpm_unregister_port() destroys the port's kthread worker first and calls tcpm_reset_port() afterwards. Since the Discover Identity retry mechanism was added, tcpm_reset_port() calls mod_vdm_discovery_cancel_delayed_work(), which does kthread_cancel_work_sync(&port->vdm_discovery_work). That dereferences work->worker, which still points at the worker that kthread_destroy_worker() has already freed: tcpm_unregister_port() kthread_destroy_worker(port->wq) -> kfree(worker) ... tcpm_reset_port() mod_vdm_discovery_cancel_delayed_work() kthread_cancel_work_sync(&port->vdm_discovery_work) __kthread_cancel_work_sync() raw_spin_lock_irqsave(&worker->lock, ...) <- freed memory KASAN report on 7.3-rc1 when unbinding a fusb302 port (RK3588): BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave+0x10c/0x210 Write of size 4 at addr ffff00010122ef04 by task bash/8349 Call trace: _raw_spin_lock_irqsave+0x10c/0x210 __kthread_cancel_work_sync+0x60/0x408 kthread_cancel_work_sync+0x20/0x48 tcpm_reset_port+0x18c/0xb80 [tcpm] tcpm_unregister_port+0x104/0x2f8 [tcpm] fusb302_remove+0xc8/0x200 [fusb302] i2c_device_remove+0x7c/0x288 ... Allocated by task 112: kthread_create_worker_on_node+0x14c/0x2c8 tcpm_register_port+0x288/0x3918 [tcpm] fusb302_probe+0x604/0xc88 [fusb302] Freed by task 8349: kfree+0x260/0x558 kthread_destroy_worker+0xa0/0x130 tcpm_unregister_port+0x74/0x2f8 [tcpm] fusb302_remove+0xc8/0x200 [fusb302] With CONFIG_PROVE_LOCKING the same unbind shows up as "DEBUG_LOCKS_WARN_ON(lock->magic != lock)" in __lock_acquire, followed by an oops in the unbinding task, which then exits with interrupts disabled and the following shutdown hangs. The work itself cannot be pending at that point: kthread_destroy_worker() has flushed the worker and the discovery timer is cancelled right before the cancel call. So just remember that the worker is gone and skip the cancel in that case. Tested on an Orange Pi 5 Plus (RK3588, fusb302) with KASAN: unbinding the port reports the use-after-free above without this patch and nothing with it; the port binds again fine afterwards in both cases. Fixes: 205dc9cb39f5 ("usb: typec: tcpm: implement retry mechanism for Discover Identity VDMs") Signed-off-by: Igor Paunovic Assisted-by: LLM Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260907183041.8253-1-royalnet026@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/tcpm/tcpm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 8f80bdd62f94..06692feb6d46 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -1756,7 +1756,8 @@ static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int del static void mod_vdm_discovery_cancel_delayed_work(struct tcpm_port *port) { hrtimer_cancel(&port->vdm_discovery_timer); - kthread_cancel_work_sync(&port->vdm_discovery_work); + if (port->wq) + kthread_cancel_work_sync(&port->vdm_discovery_work); } static void mod_vdm_discovery_delayed_work(struct tcpm_port *port, unsigned int delay_ms) @@ -8977,6 +8978,7 @@ void tcpm_unregister_port(struct tcpm_port *port) port->registered = false; kthread_destroy_worker(port->wq); + port->wq = NULL; hrtimer_cancel(&port->vdm_discovery_timer); hrtimer_cancel(&port->enable_frs_timer); From 469600d5d292b1482c90dd6853555821d11b8c5f Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Thu, 10 Sep 2026 09:11:50 +0800 Subject: [PATCH 42/63] usb: typec: anx7411 - stop the IRQ before destroying the workqueue The IRQ is devm-managed, so it can still queue plat->work while remove() is tearing the queue down. Disable it first. Fixes: fe6d8a9c8e64 ("usb: typec: anx7411: Add Analogix PD ANX7411 support") Cc: stable Signed-off-by: Linkai Gong Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260910011150.2966843-1-gonglinkai@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/anx7411.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/typec/anx7411.c b/drivers/usb/typec/anx7411.c index 41df115912b9..c3d36da692e4 100644 --- a/drivers/usb/typec/anx7411.c +++ b/drivers/usb/typec/anx7411.c @@ -1566,8 +1566,11 @@ static void anx7411_i2c_remove(struct i2c_client *client) anx7411_partner_unregister_altmode(plat); anx7411_unregister_partner(plat); - if (plat->workqueue) + if (plat->workqueue) { + disable_irq(plat->intp_irq); + cancel_work_sync(&plat->work); destroy_workqueue(plat->workqueue); + } i2c_unregister_device(plat->spi_client); From f1d6f7d25fe88ee49aaad96c8649e090fc3a4b68 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 13 Sep 2026 00:10:33 -0400 Subject: [PATCH 43/63] usb: chipidea: tegra: disable runtime PM on resume failure tegra_usb_probe() enables runtime PM before resuming the device. If pm_runtime_resume_and_get() fails, probe returns without disabling runtime PM. The later error paths reach pm_runtime_force_suspend(), but this early return bypasses that cleanup. Disable runtime PM before returning the resume error. Do not use the fail_power_off path: the failed resume did not retain a usage reference, so its pm_runtime_put_sync_suspend() would be unbalanced. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8b85e11c1a7a ("usb: chipidea: tegra: Add runtime PM and OPP support") Cc: stable Assisted-by: OpenAI:GPT-5.6 Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Peter Chen Link: https://patch.msgid.link/20260913041033.25144-1-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/chipidea/ci_hdrc_tegra.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/usb/chipidea/ci_hdrc_tegra.c b/drivers/usb/chipidea/ci_hdrc_tegra.c index 372788f0f970..26225b034d3b 100644 --- a/drivers/usb/chipidea/ci_hdrc_tegra.c +++ b/drivers/usb/chipidea/ci_hdrc_tegra.c @@ -306,8 +306,10 @@ static int tegra_usb_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); err = pm_runtime_resume_and_get(&pdev->dev); - if (err) + if (err) { + pm_runtime_disable(&pdev->dev); return err; + } if (device_property_present(&pdev->dev, "nvidia,needs-double-reset")) usb->needs_double_reset = true; From ebe9b68c396792c15d0ac690d592e449e74ed804 Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Wed, 9 Sep 2026 09:56:55 +0000 Subject: [PATCH 44/63] usb: cdns3: fix use-after-free in cdns3_gadget_exit() cdns3_gadget_start() arms two works on system_freezable_wq: pending_status_wq for the deferred ep0 status stage and aligned_buf_wq for realigned request buffers. Both handlers use the cdns3_device the works are embedded in, and cdns3_pending_setup_status_handler() also calls the ep0 request completion. cdns3_gadget_exit() does not wait for these works. It frees all endpoints and aligned buffers and drops the last reference to the gadget device, which frees priv_dev, so a work queued before the exit can run after the free. Fix this by waiting for both works after the gadget driver is unbound and the IRQ is freed, when no new work can be queued, and before the endpoints and buffers are released. This issue was found by an in-house static analysis tool. Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver") Cc: stable Reported-by: Sicong Huang Closes: https://lore.kernel.org/linux-usb/7f5719b.8700.18f67b324d3.Coremail.congei42@163.com/ Suggested-by: Sicong Huang Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Acked-by: Peter Chen Link: https://patch.msgid.link/20260909095655.694527-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-gadget.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/cdns3/cdns3-gadget.c b/drivers/usb/cdns3/cdns3-gadget.c index 42311c1bfada..0d272e9c556c 100644 --- a/drivers/usb/cdns3/cdns3-gadget.c +++ b/drivers/usb/cdns3/cdns3-gadget.c @@ -3269,6 +3269,10 @@ static void cdns3_gadget_exit(struct cdns *cdns) usb_del_gadget(&priv_dev->gadget); devm_free_irq(cdns->dev, cdns->dev_irq, priv_dev); + /* The works can still be queued until the IRQ is freed. */ + cancel_work_sync(&priv_dev->pending_status_wq); + cancel_work_sync(&priv_dev->aligned_buf_wq); + cdns3_free_all_eps(priv_dev); while (!list_empty(&priv_dev->aligned_buf_list)) { From 901197dd4c7dcffbf97426487d4c047555c98554 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:08 -0400 Subject: [PATCH 45/63] usb: ohci-da8xx: disable controller wakeup on cleanup The OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Probe enables wakeup, but neither removal nor a later notifier registration failure disables it, leaving the wakeup source attached. Disable controller wakeup after removing the HCD on both paths. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-2-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-da8xx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/usb/host/ohci-da8xx.c b/drivers/usb/host/ohci-da8xx.c index 0938c0e7a8b6..a7cd5fb4f119 100644 --- a/drivers/usb/host/ohci-da8xx.c +++ b/drivers/usb/host/ohci-da8xx.c @@ -447,6 +447,7 @@ static int ohci_da8xx_probe(struct platform_device *pdev) err_remove_hcd: usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); err: usb_put_hcd(hcd); return error; @@ -457,6 +458,7 @@ static void ohci_da8xx_remove(struct platform_device *pdev) struct usb_hcd *hcd = platform_get_drvdata(pdev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); usb_put_hcd(hcd); } From 8dd8451c527e2a723bca20da5a6d724b8592551f Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:09 -0400 Subject: [PATCH 46/63] usb: ohci-s3c2410: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-3-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-s3c2410.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-s3c2410.c b/drivers/usb/host/ohci-s3c2410.c index e623e24d3f8e..7c0e35be58f1 100644 --- a/drivers/usb/host/ohci-s3c2410.c +++ b/drivers/usb/host/ohci-s3c2410.c @@ -335,6 +335,7 @@ ohci_hcd_s3c2410_remove(struct platform_device *dev) struct usb_hcd *hcd = platform_get_drvdata(dev); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); s3c2410_stop_hc(dev); usb_put_hcd(hcd); } From b1b0f8bdbbc891a4ee7f3b1a878c633e02b7bbeb Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:10 -0400 Subject: [PATCH 47/63] usb: ohci-spear: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: a6eeeb9f45b5 ("USB: Update USB default wakeup settings") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-4-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-spear.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-spear.c b/drivers/usb/host/ohci-spear.c index 6843d7cb3f9f..5aeabb051265 100644 --- a/drivers/usb/host/ohci-spear.c +++ b/drivers/usb/host/ohci-spear.c @@ -103,6 +103,7 @@ static void spear_ohci_hcd_drv_remove(struct platform_device *pdev) struct spear_ohci *sohci_p = to_spear_ohci(hcd); usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); clk_disable_unprepare(sohci_p->clk); usb_put_hcd(hcd); From 08b2ccd7eb7e41f77a04a09112ca3edcc588e3f0 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Mon, 14 Sep 2026 21:20:11 -0400 Subject: [PATCH 48/63] usb: ohci-st: disable controller wakeup on removal Probe enables controller wakeup after the OHCI core marks controllers with RemoteWakeupConnected as wakeup-capable. Removal does not undo this, so the wakeup source can remain attached after driver unbind. Disable controller wakeup after removing the HCD. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: d115837259ad ("usb: host: ohci-st: Add OHCI driver support for ST STB devices") Cc: stable Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Reviewed-by: Patrice Chotard Acked-by: Alan Stern Link: https://patch.msgid.link/20260915012011.61874-5-mhun512@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/host/ohci-st.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/host/ohci-st.c b/drivers/usb/host/ohci-st.c index d1656fce5400..53010a277b67 100644 --- a/drivers/usb/host/ohci-st.c +++ b/drivers/usb/host/ohci-st.c @@ -235,6 +235,7 @@ static void st_ohci_platform_remove(struct platform_device *dev) int clk; usb_remove_hcd(hcd); + device_wakeup_disable(hcd->self.controller); if (pdata->power_off) pdata->power_off(dev); From ea31c6498c61307dc0d8ce338a7f268cb8cbdd57 Mon Sep 17 00:00:00 2001 From: Jameson Thies Date: Thu, 17 Sep 2026 03:20:08 +0000 Subject: [PATCH 49/63] usb: typec: ucsi: displayport: Current CAM OOB index fixup Update ucsi_displayport_enter() to return invalid error when the PPM reports an current alternate mode greater than or equal to UCSI_MAX_ALTMODES. While here, remove 0xff current cam assignment on failed GET_CURRENT_CAM response. Fixes: 04cec690b1fd ("usb: typec: ucsi: displayport: Fix OOB altmode array index") Cc: stable Reviewed-by: Heikki Krogerus Reviewed-by: Benson Leung Signed-off-by: Jameson Thies Link: https://patch.msgid.link/20260917032008.1701888-1-jthies@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/displayport.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/displayport.c b/drivers/usb/typec/ucsi/displayport.c index 8d2032d0762c..572da7bbd29c 100644 --- a/drivers/usb/typec/ucsi/displayport.c +++ b/drivers/usb/typec/ucsi/displayport.c @@ -71,11 +71,14 @@ static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo) if (ret < 0) { if (ucsi->version > 0x0100) goto err_unlock; - cur = 0xff; } - if (cur < UCSI_MAX_ALTMODES) { - ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + if (cur != 0xff) { + if (cur < UCSI_MAX_ALTMODES) + ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY; + else + ret = -EINVAL; + goto err_unlock; } From 47c73a43c1de51ed54b621d570c5cdf20549c5b7 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 22 Sep 2026 13:59:39 +0300 Subject: [PATCH 50/63] thunderbolt: stream: Announce support for FMODE_NOWAIT Commit 42bc6935339b ("thunderbolt: stream: Support IOCB_NOWAIT in non-blocking I/O as well") added support for IOCB_NOWAIT but forgot to actually announce it as part of the file->f_mode. Add this now so users such as io_uring can actually take advantage of IOCB_NOWAIT. Fixes: 42bc6935339b ("thunderbolt: stream: Support IOCB_NOWAIT in non-blocking I/O as well") Signed-off-by: Mika Westerberg --- drivers/thunderbolt/stream.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/stream.c b/drivers/thunderbolt/stream.c index c737dd0ca6e7..8ef4064b5939 100644 --- a/drivers/thunderbolt/stream.c +++ b/drivers/thunderbolt/stream.c @@ -980,6 +980,9 @@ static int tbstream_dev_fops_open(struct inode *inode, struct file *file) } mutex_unlock(&sdev->lock); + + /* Stream handles IOCB_NOWAIT just fine */ + file->f_mode |= FMODE_NOWAIT; return 0; err_unlock: From 395e9f2967a7ac6898026e8f136c369805dc2fd9 Mon Sep 17 00:00:00 2001 From: Kurt Lieber Date: Thu, 24 Sep 2026 12:26:37 +0000 Subject: [PATCH 51/63] thunderbolt: Disable CL states for the Anker Prime TB5 dock The Anker Prime TB5 dock identifies itself in the DROM as 01ea:83b5. Its router config space is an Intel Barlow Ridge 80G hub (8087:5786). The upstream lane adapter advertises CL0s, CL1, and CL2. With CLx left enabled, TMU uni-directional LowRes setup fails with -ENOTCONN, the USB3 and DisplayPort tunnels are aborted, and the router reconnects in a loop. Loading thunderbolt with clx=0 keeps the link up on this machine. Match the DROM id together with the Barlow Ridge hub id and disable CL states for this dock only. QUIRK_NO_CLX takes the same early-out as the module parameter, without disabling CLx for every other router. Closes: https://lore.kernel.org/linux-usb/SJ0PR15MB4696D491504DB667AA8E0617A3812@SJ0PR15MB4696.namprd15.prod.outlook.com/ Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Kurt Lieber Signed-off-by: Mika Westerberg --- drivers/thunderbolt/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c index 9f7914ac2f48..96b7fe0386e3 100644 --- a/drivers/thunderbolt/quirks.c +++ b/drivers/thunderbolt/quirks.c @@ -99,6 +99,9 @@ static const struct tb_quirk tb_quirks[] = { quirk_usb3_maximum_bandwidth }, { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_40G_BRIDGE, 0x0000, 0x0000, quirk_usb3_maximum_bandwidth }, + /* CLx is unstable on the Anker Prime TB5 dock (DROM 01ea:83b5) */ + { 0x8087, PCI_DEVICE_ID_INTEL_BARLOW_RIDGE_HUB_80G_BRIDGE, 0x01ea, 0x83b5, + quirk_clx_disable }, /* * Block Runtime PM in DP redrive mode for Intel Barlow Ridge host * controllers. From 20388c8d1d74c203fec8194c45cd31afdfab934e Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Tue, 8 Sep 2026 04:17:39 +0000 Subject: [PATCH 52/63] usb: gadget: aspeed-vhub: cancel wake work on device removal wake_work is armed from the gadget .wakeup callback to resume suspended downstream ports, and it is never cancelled in ast_vhub_remove(), so a work item queued before or during removal can run after devm has freed vhub and its ports, a use-after-free. Cancelling the work alone is not sufficient: usb_gadget_wakeup() takes no lock and the unbind path never clears wakeup_en, so a remote-wakeup request in flight on another CPU can re-arm the work after the cancel and before vhub is freed. ast_vhub_del_dev() already clears d->registered under vhub->lock before unregistering the gadget. Refuse the wakeup in ast_vhub_udc_wakeup() once d->registered is clear: the check and the schedule_work() are then atomic against the unbind, so a wakeup that passed before the flag was cleared is drained by the cancel_work_sync() after the del_dev loop, and one that arrives later returns without arming. Also move INIT_WORK() to the top of ast_vhub_probe(), since the probe error path reuses ast_vhub_remove() and would otherwise cancel a never-initialized work item. This issue was found by an in-house static analysis tool. Fixes: 7ecca2a4080c ("usb/gadget: Add driver for Aspeed SoC virtual hub") Cc: stable@kernel.org Assisted-by: Codex:gpt-5.6 Co-developed-by: Song Li Signed-off-by: Song Li Signed-off-by: Fan Wu Reviewed-by: Benjamin Herrenschmidt Link: https://patch.msgid.link/20260908041740.613713-1-fanwu01@zju.edu.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/aspeed-vhub/core.c | 4 ++++ drivers/usb/gadget/udc/aspeed-vhub/dev.c | 2 +- drivers/usb/gadget/udc/aspeed-vhub/hub.c | 5 +++-- drivers/usb/gadget/udc/aspeed-vhub/vhub.h | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/drivers/usb/gadget/udc/aspeed-vhub/core.c b/drivers/usb/gadget/udc/aspeed-vhub/core.c index 4a8b9ff8368f..069673f0d359 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/core.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/core.c @@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev) for (i = 0; i < vhub->max_ports; i++) ast_vhub_del_dev(&vhub->ports[i].dev); + /* Final drain; the worker takes vhub->lock, so stay outside of it */ + cancel_work_sync(&vhub->wake_work); + spin_lock_irqsave(&vhub->lock, flags); /* Mask & ack all interrupts */ @@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev) return -ENOMEM; spin_lock_init(&vhub->lock); + INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); vhub->pdev = pdev; vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1, VHUB_IRQ_DEV1_BIT); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/dev.c b/drivers/usb/gadget/udc/aspeed-vhub/dev.c index 8b9449d16324..4b389de0d935 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/dev.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/dev.c @@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget) int rc = -EINVAL; spin_lock_irqsave(&d->vhub->lock, flags); - if (!d->wakeup_en) + if (!d->wakeup_en || !d->registered) goto err; DDBG(d, "Device initiated wakeup\n"); diff --git a/drivers/usb/gadget/udc/aspeed-vhub/hub.c b/drivers/usb/gadget/udc/aspeed-vhub/hub.c index 02fe1a08d575..d0345f31037a 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/hub.c +++ b/drivers/usb/gadget/udc/aspeed-vhub/hub.c @@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub, ast_vhub_send_host_wakeup(vhub); } -static void ast_vhub_wake_work(struct work_struct *work) +void ast_vhub_wake_work(struct work_struct *work) { struct ast_vhub *vhub = container_of(work, struct ast_vhub, @@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work) void ast_vhub_hub_wake_all(struct ast_vhub *vhub) { + lockdep_assert_held(&vhub->lock); + /* * A device is trying to wake the world, because this * can recurse into the device, we break the call chain @@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub) int ast_vhub_init_hub(struct ast_vhub *vhub) { vhub->speed = USB_SPEED_UNKNOWN; - INIT_WORK(&vhub->wake_work, ast_vhub_wake_work); return ast_vhub_init_desc(vhub); } diff --git a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h index aca2050e2db0..96f1c3709042 100644 --- a/drivers/usb/gadget/udc/aspeed-vhub/vhub.h +++ b/drivers/usb/gadget/udc/aspeed-vhub/vhub.h @@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub); void ast_vhub_hub_resume(struct ast_vhub *vhub); void ast_vhub_hub_reset(struct ast_vhub *vhub); void ast_vhub_hub_wake_all(struct ast_vhub *vhub); +void ast_vhub_wake_work(struct work_struct *work); /* dev.c */ int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx); From 9cd072788c76595529eb38f42bf94d23852f8534 Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Fri, 18 Sep 2026 08:54:38 +0800 Subject: [PATCH 53/63] usb: gadget: f_fs: Fix NULL pointer dereference in FUNCTIONFS_ENDPOINT_DESC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When user space sets up FunctionFS with only full-speed descriptors (e.g., flags = FUNCTIONFS_HAS_FS_DESC) and the gadget operates at a higher speed (such as USB_SPEED_HIGH or USB_SPEED_SUPER), config_ep_by_speed() detects that the function lacks descriptors for the current speed, logs a warning, and falls back to full-speed descriptors. The endpoint is then successfully enabled via usb_ep_enable(), and epfile->ep becomes valid. However, when user space subsequently issues the FUNCTIONFS_ENDPOINT_DESC ioctl to query the endpoint descriptor, ffs_epfile_ioctl() selects the descriptor index solely based on gadget->speed: switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: case USB_SPEED_SUPER_PLUS: desc_idx = 2; break; case USB_SPEED_HIGH: desc_idx = 1; break; default: desc_idx = 0; } desc = epfile->ep->descs[desc_idx]; memcpy(&desc1, desc, desc->bLength); Because user space only supplied full-speed descriptors, epfile->ep->descs[1] is NULL. Dereferencing desc->bLength triggers an immediate kernel NULL pointer dereference panic. Fix this by falling back through lower speed descriptors if the current speed descriptor was not supplied, matching the fallback logic in config_ep_by_speed() and historical f_fs behavior. If still missing, fall back to epfile->ep->ep->desc (the active descriptor), and return -EINVAL safely if no valid descriptor is found. In addition, mark desc as const to match struct usb_ep::desc and avoid discarding qualifiers. Fixes: c559a3534109 ("usb: gadget: f_fs: add ioctl returning ep descriptor") Cc: stable@kernel.org Signed-off-by: Yuchao Zhang Acked-by: MichaƂ Nazarewicz Link: https://patch.msgid.link/20260918005438.81263-2-ndaugoing@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_fs.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 43962e05eacf..c64a268e98a4 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -1877,8 +1877,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, break; case FUNCTIONFS_ENDPOINT_DESC: { + const struct usb_endpoint_descriptor *desc; + struct usb_endpoint_descriptor desc1; int desc_idx; - struct usb_endpoint_descriptor desc1, *desc; switch (epfile->ffs->gadget->speed) { case USB_SPEED_SUPER: @@ -1892,7 +1893,17 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, desc_idx = 0; } - desc = epfile->ep->descs[desc_idx]; + do { + desc = epfile->ep->descs[desc_idx]; + } while (!desc && --desc_idx >= 0); + + if (!desc) + desc = epfile->ep->ep->desc; + if (!desc) { + ret = -EINVAL; + break; + } + memcpy(&desc1, desc, desc->bLength); spin_unlock_irq(&epfile->ffs->eps_lock); From dc614d6bc991740d41d5a99ac7765c4b675dda88 Mon Sep 17 00:00:00 2001 From: Yuchao Zhang Date: Fri, 18 Sep 2026 19:43:09 +0800 Subject: [PATCH 54/63] USB: cdc-acm: skip URB restart in port_shutdown if disconnected acm_port_shutdown() is called from the tty-port machinery whenever the last user closes the port. acm_disconnect() sets acm->disconnected and calls tty_port_tty_vhangup(), which eventually triggers port_shutdown(). At that point acm_disconnect() has already called acm_poison_urbs() and is about to free the control and read URBs. However, acm_port_shutdown() unconditionally calls acm_unpoison_urbs() and, when the ALWAYS_POLL_CTRL quirk is set, immediately re-submits the ctrlurb and all read URBs. If the concurrent acm_disconnect() then frees those URBs and their DMA buffers while they are in-flight, the result is a use-after-free in acm_ctrl_irq() or acm_read_bulk_callback() accessing the freed DMA memory. Fix by checking acm->disconnected after draining the delayed anchor and returning early: if we are in a disconnect path the URBs must remain poisoned and must not be resubmitted. The early return is placed after the delayed-anchor drain loop because that loop only cancels write-side accounting (wb->use = false and autopm counter) and does not touch the hardware path. Fixes: f58752ebcb35 ("USB: cdc-acm: Add quirks for Yoga Book 9 14IAH10 INGENIC touchscreen") Cc: stable Signed-off-by: Yuchao Zhang Link: https://patch.msgid.link/20260918114309.3659635-2-ndaugoing@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/class/cdc-acm.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index 21417b4c5154..8ba4e4fa28f2 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -787,6 +787,9 @@ static void acm_port_shutdown(struct tty_port *port) usb_autopm_put_interface_async(acm->control); } + if (acm->disconnected) + return; + acm_unpoison_urbs(acm); if (acm->quirks & ALWAYS_POLL_CTRL) { From 0e8baa78d872f2db440d8b9ded946ab8dceb89eb Mon Sep 17 00:00:00 2001 From: Hui Peng Date: Sun, 20 Sep 2026 23:17:02 +0000 Subject: [PATCH 55/63] usb: core: clear both ep_in and ep_out for non-ep0 control endpoints In usb_enable_endpoint(), non-zero control endpoints (where usb_endpoint_xfer_control(&ep->desc) is true) populate both dev->ep_in[epnum] and dev->ep_out[epnum] with the same struct usb_host_endpoint pointer. However, usb_disable_endpoint() only clears either dev->ep_out[epnum] or dev->ep_in[epnum] depending on the direction bit of epaddr when reset_hardware is set, leaving the opposite direction's array slot pointing to the disabled/freed endpoint. Clear both dev->ep_out[epnum] and dev->ep_in[epnum] when disabling a non-ep0 control endpoint with reset_hardware set, and update the function kerneldoc accordingly. Tested in QEMU against Linux 7.3.0-rc3 using dummy_hcd and raw-gadget to enumerate a USB device with a non-zero control endpoint (bEndpointAddress 0x01, bmAttributes USB_ENDPOINT_XFER_CONTROL), verifying that both dev->ep_in[1] and dev->ep_out[1] are cleared when the interface is disabled. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable Assisted-by: LLM Signed-off-by: Hui Peng Acked-by: Alan Stern Link: https://patch.msgid.link/20260920231702.110963-1-benquike@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/message.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 75e2bfd744a9..0cd6dd2b6334 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -1337,7 +1337,8 @@ static void remove_intf_ep_devs(struct usb_interface *intf) * * Disables the endpoint for URB submission and nukes all pending URBs. * If @reset_hardware is set then also deallocates hcd/hardware state - * for the endpoint. + * for the endpoint (clearing both ep_in and ep_out pointers for + * bidirectional non-ep0 control endpoints). */ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, bool reset_hardware) @@ -1358,6 +1359,11 @@ void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr, dev->ep_in[epnum] = NULL; } if (ep) { + if (reset_hardware && epnum != 0 && + usb_endpoint_xfer_control(&ep->desc)) { + dev->ep_out[epnum] = NULL; + dev->ep_in[epnum] = NULL; + } ep->enabled = 0; usb_hcd_flush_endpoint(dev, ep); if (reset_hardware) From 9062d50e75c24dc7911be05c9b1719b3b256af15 Mon Sep 17 00:00:00 2001 From: Liu Chao Date: Mon, 21 Sep 2026 15:25:51 +0800 Subject: [PATCH 56/63] usb: gadget: f_uac1_legacy: validate bRequest index in generic_{set,get}_cmd generic_set_cmd() and generic_get_cmd() use the low nibble of ctrl->bRequest as an index into con->data[]: u8 cmd = (ctrl->bRequest & 0x0F); /* 0 .. 15 */ ... con->data[cmd] = value; /* OOB when cmd >= 5 */ struct usb_audio_control (include/linux/usb/audio.h) declares data as a 5-element array, so indices 5 through 15 write (or read) up to 44 bytes past the end of the array on the heap. A malicious USB host can craft a class-specific SET_CUR / GET_CUR request with an arbitrary bRequest value, triggering the out-of-bounds access from an IRQ completion handler with no further preconditions. Add an ARRAY_SIZE() guard to both functions. Fixes: c47d7b09891a ("USB: audio: add USB audio class definitions") Cc: stable Reviewed-by: Weibin Liu Signed-off-by: Liu Chao Reviewed-by: Ivy Lopez Link: https://patch.msgid.link/20260921072551.3708191-1-liuc63@xiaopeng.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/function/f_uac1_legacy.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/usb/gadget/function/f_uac1_legacy.c b/drivers/usb/gadget/function/f_uac1_legacy.c index 3f52099a4fdd..7c8d60c7e87d 100644 --- a/drivers/usb/gadget/function/f_uac1_legacy.c +++ b/drivers/usb/gadget/function/f_uac1_legacy.c @@ -797,6 +797,9 @@ f_audio_bind(struct usb_configuration *c, struct usb_function *f) static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + con->data[cmd] = value; return 0; @@ -804,6 +807,9 @@ static int generic_set_cmd(struct usb_audio_control *con, u8 cmd, int value) static int generic_get_cmd(struct usb_audio_control *con, u8 cmd) { + if (cmd >= ARRAY_SIZE(con->data)) + return -EINVAL; + return con->data[cmd]; } From 6c51f09c6db5868a5090e8dd4d7764660c87f1ad Mon Sep 17 00:00:00 2001 From: Prashanth K Date: Wed, 16 Sep 2026 09:58:08 +0530 Subject: [PATCH 57/63] usb: typec: ucsi: Get the connector fwnode based on reg value ucsi_find_fwnode() currently maps UCSI connectors to Device tree connector nodes based on the order in which connector child nodes are described in DT. This can fail because the ordering of child nodes isn't guaranteed in Device-tree. For example, DTB may contain connector@1 before connector@0, causing connector numbers to be associated with the wrong fwnode. As a result, role switch and Type-C notifications can be delivered to the wrong remote endpoints. Fix this by using the "reg" property of each connector to match its corresponding fwnode. While at it, if the reg property isn't present, then fall back to the old method. Fixes: c1b0bc2dabfa ("usb: typec: Add support for UCSI interface") Cc: stable Signed-off-by: Prashanth K Reviewed-by: Heikki Krogerus Link: https://patch.msgid.link/20260916042808.2879079-1-prashanth.k@oss.qualcomm.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/ucsi/ucsi.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index bef3f9b71d71..c1450639c795 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1801,10 +1801,21 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con) { struct fwnode_handle *fwnode; int i = 1; + int ret; + u32 port; - device_for_each_child_node(con->ucsi->dev, fwnode) - if (i++ == con->num) - return fwnode; + device_for_each_child_node(con->ucsi->dev, fwnode) { + ret = fwnode_property_read_u32(fwnode, "reg", &port); + if (ret < 0) { + if (i == con->num) + return fwnode; + } else { + if (port == con->num - 1) + return fwnode; + } + + i++; + } return NULL; } From a107edec57659c5a1a2f016311b944af58c904c9 Mon Sep 17 00:00:00 2001 From: Jiazi Liu Date: Mon, 7 Sep 2026 15:37:56 +0800 Subject: [PATCH 58/63] usb: dwc3: gadget: fix IRQ storm on invalid event buffer count When dwc3_check_event_buf() reads a GEVNTCOUNT value exceeding the event buffer length, commit 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") returns IRQ_NONE without writing back GEVNTCOUNT. Since the DWC3 interrupt is level-triggered, the uncleared IRQ source keeps the line asserted, causing a tight IRQ storm that accumulates 99,900 unhandled interrupts and triggers spurious.c:184 BUG -> kernel panic. The resulting call stack: __report_bad_irq+0xac/0xc8 note_interrupt+0x340/0x468 handle_irq_event+0xac/0xc0 handle_fasteoi_irq+0x120/0x228 gic_handle_irq+0x68/0x108 ... kernel BUG at kernel/irq/spurious.c:184 To reproduce, write a bogus value exceeding the event buffer length directly to the GEVNTCOUNT register: devmem 4 0x1004 Write the bogus count back to GEVNTCOUNT to clear the IRQ source, consistent with the stale event clearing pattern in dwc3_event_buffers_setup(), and schedule error recovery to reinitialize the controller. Fixes: 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") Cc: stable Co-developed-by: Thinh Nguyen Signed-off-by: Thinh Nguyen Signed-off-by: Jiazi Liu Suggested-by: Thinh Nguyen Link: https://patch.msgid.link/20260907073756.22329-1-jiazi.liu1984@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 +++++++ drivers/usb/dwc3/ep0.c | 9 ++++ drivers/usb/dwc3/gadget.c | 105 +++++++++++++++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 608daeb7ef10..f3dabb37f806 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,6 +49,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 +#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -841,6 +842,12 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; +enum dwc3_err_state { + DWC3_ERR_NONE = 0, + DWC3_ERR_RECOVERY, + DWC3_ERR_UNRECOVERABLE, +}; + /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping + * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching + * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state + * @err_state: current error recovery state. + * @err_recovery_count: number of consecutive error recovery attempts until + * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; + struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1203,6 +1216,9 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; + /* serializes pull-up run/stop vs error recovery */ + struct mutex connect_mutex; + struct device *dev; struct device *sysdev; @@ -1332,6 +1348,9 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; + enum dwc3_err_state err_state; + + u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236a..46a34c4a046b 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + ret = -ESHUTDOWN; + goto out; + } + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f245e66cd13d..13096ee475d3 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); + /* Serialize against dwc3_err_recovery_work() */ + mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); + mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; + /* The controller is recovered. */ + if (dwc->err_state == DWC3_ERR_NONE) + dwc->err_recovery_count = 0; + /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } +static void dwc3_schedule_err_recovery(struct dwc3 *dwc) +{ + dwc->err_state = DWC3_ERR_RECOVERY; + schedule_work(&dwc->err_recovery_work); +} + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - return IRQ_NONE; + /* + * This is a fatal error - the driver and controller are out of + * sync on which event has been consumed. Reinitializing the + * controller is required to recover. Write the bogus count back + * to GEVNTCOUNT to clear the IRQ source, consistent with the + * stale event clearing in dwc3_event_buffers_setup(), then + * schedule error recovery. + */ + spin_lock(&dwc->lock); + dwc3_schedule_err_recovery(dwc); + spin_unlock(&dwc->lock); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + return IRQ_HANDLED; } evt->count = count; @@ -4735,6 +4784,54 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } +static void dwc3_err_recovery_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); + unsigned long flags; + int ret; + + dwc->err_recovery_count++; + + /* serializes against dwc3_gadget_pullup() */ + mutex_lock(&dwc->connect_mutex); + + ret = dwc3_gadget_soft_disconnect(dwc); + + dwc3_disconnect_gadget_sleepable(dwc); + + if (ret) + goto err_unrecoverable; + + if (dwc->err_recovery_count > DWC3_ERR_RECOVERY_MAX) + goto err_unrecoverable; + + if (dwc->softconnect) { + /* + * Wait irq to finish before soft_connect resets evt->lpos and + * the event buffer registers to avoid racing with + * dwc3_process_event_buf(). + */ + synchronize_irq(dwc->irq_gadget); + + ret = dwc3_gadget_soft_connect(dwc); + if (ret) + goto err_unrecoverable; + } + mutex_unlock(&dwc->connect_mutex); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); + return; + +err_unrecoverable: + dev_err(dwc->dev, "Unable to recover the controller\n"); + mutex_unlock(&dwc->connect_mutex); + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_UNRECOVERABLE; + spin_unlock_irqrestore(&dwc->lock, flags); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4778,6 +4875,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); + mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4874,6 +4973,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; + cancel_work_sync(&dwc->err_recovery_work); + mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); From e5052f2c5c73c1dcca42bafc0bc737af2b2af059 Mon Sep 17 00:00:00 2001 From: Jie Deng Date: Mon, 10 Aug 2026 10:36:26 +0800 Subject: [PATCH 59/63] usb: cdns3: Fix NULL pointer dereference in cdns3_pci_probe The Cadence USBSS controller is a two-function PCI device. The first probed function allocates the driver data and stores it with pci_set_drvdata(), while the second function reuses it via pci_get_drvdata() when pci_is_enabled() reports that the first function has already been probed. When the second function is probed while the first one has been enabled but has not yet set its driver data, pci_get_drvdata() returns NULL, and the subsequent wrap->devfn assignment dereferences a NULL pointer and crashes the kernel. logs: Call trace: cdns3_pci_probe+0xa4/0x300 local_pci_probe+0x44/0xa8 pci_call_probe+0x54/0x158 pci_device_probe+0x84/0x100 really_probe+0x184/0x3d0 __driver_probe_device+0x80/0x178 driver_probe_device+0x44/0xe8 __driver_attach+0xec/0x1f8 bus_for_each_dev+0x7c/0xe0 driver_attach+0x28/0x38 bus_add_driver+0x110/0x238 driver_register+0x64/0x128 __pci_register_driver+0x50/0x60 cdns3_pci_driver_init+0x28/0x38 do_one_initcall+0x5c/0x280 do_initcalls+0x104/0x1d8 kernel_init_freeable+0x140/0x218 kernel_init+0x28/0x1f8 ret_from_fork+0x10/0x20 Return -EPROBE_DEFER in this case so that probing is retried after the first function has completed its probe. Fixes: 7733f6c32e36 ("usb: cdns3: Add Cadence USB3 DRD Driver") Cc: stable Signed-off-by: Jie Deng Acked-by: Peter Chen Link: https://patch.msgid.link/20260810023626.70669-1-dengjie03@kylinos.cn Signed-off-by: Greg Kroah-Hartman --- drivers/usb/cdns3/cdns3-pci-wrap.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c index eb5760f75b9d..fd06a3d8e638 100644 --- a/drivers/usb/cdns3/cdns3-pci-wrap.c +++ b/drivers/usb/cdns3/cdns3-pci-wrap.c @@ -96,6 +96,11 @@ static int cdns3_pci_probe(struct pci_dev *pdev, if (pci_is_enabled(func)) { wrap = pci_get_drvdata(func); + if (!wrap) { + dev_err(&pdev->dev, + "second function not initialized, retrying\n"); + return -EPROBE_DEFER; + } } else { wrap = kzalloc_obj(*wrap); if (!wrap) From aef5a7e207656ad6abdeeaa0bfc8ee9a1f9c02f6 Mon Sep 17 00:00:00 2001 From: Marek Maslanka Date: Fri, 25 Sep 2026 15:16:50 +0200 Subject: [PATCH 60/63] usb: typec: port-mapper: Only match USB4 port if host interface is available typec_port_match() adds a component match for the USB4 port whenever a USB 3.x port that shares the _PLD with the Type-C connector has the "usb4-host-interface" property, regardless of whether a USB4 port can ever be registered for it. The component framework binds the aggregate device only once every match has found its component, so if the USB4 port never shows up, the USB 2.0 and USB 3.x ports are not linked to the connector either: the "connector" symlinks are never created and USB devices enumerated on those ports are never linked with the Type-C partner. This happens in at least two cases: 1. CONFIG_USB4 is not reachable from the Type-C core, i.e. CONFIG_USB4=n, or CONFIG_USB4=m with CONFIG_TYPEC=y as in the x86_64 gki_defconfig. usb4_usb3_port_match() is then a stub that always returns false. 2. The firmware references a USB4 host interface that is disabled. For example, on Intel Alder Lake-N (ChromeOS Nissa) the TCSS xHCI USB3 ports (SS01-SS04) reference TDM0/TDM1, whose _STA returns 0 because the SoC has no integrated Thunderbolt/USB4 and the DMA controllers are disabled in TCSS DEVEN. No PCI device is enumerated for them. Only add the USB4 component match if CONFIG_USB4 is reachable and the referenced host interface is available and has been enumerated as a device. The latter mirrors the check in usb_acpi_add_usb4_devlink(), see commit 623dae3e7084 ("usb: acpi: fix boot hang due to early incorrect 'tunneled' USB3 device links"). Fixes: 4fd7a1f0f7f2 ("usb: typec: Connect Type-C port with associated USB4 port") Cc: stable Signed-off-by: Marek Maslanka Acked-by: Heikki Krogerus Link: https://patch.msgid.link/20260925131650.3777399-1-mmaslanka@google.com Signed-off-by: Greg Kroah-Hartman --- drivers/usb/typec/port-mapper.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/usb/typec/port-mapper.c b/drivers/usb/typec/port-mapper.c index cdbb7c11d714..3a97b084c3bd 100644 --- a/drivers/usb/typec/port-mapper.c +++ b/drivers/usb/typec/port-mapper.c @@ -42,6 +42,23 @@ static int usb4_port_compare(struct device *dev, void *fwnode) return usb4_usb3_port_match(dev, fwnode); } +static bool typec_has_usb4_host_interface(const struct fwnode_handle *fwnode) +{ + if (!IS_REACHABLE(CONFIG_USB4)) + return false; + + struct fwnode_handle *nhi_fwnode __free(fwnode_handle) = + fwnode_find_reference(fwnode, "usb4-host-interface", 0); + + /* + * The USB4 port can only appear if the host interface is enabled in + * the firmware and has been enumerated as a device. The latter is + * the same check as in usb_acpi_add_usb4_devlink(). + */ + return !IS_ERR(nhi_fwnode) && fwnode_device_is_available(nhi_fwnode) && + nhi_fwnode->dev; +} + static int typec_port_compare(struct device *dev, void *fwnode) { return device_match_fwnode(dev, fwnode); @@ -64,11 +81,11 @@ static int typec_port_match(struct device *dev, void *data) adev_fwnode); /* - * If dev is USB 3.x port, it may have reference to the + * If dev is USB 3.x port, it may have reference to an available * USB4 host interface in which case we can also link the * Type-C port with the USB4 port. */ - if (fwnode_property_present(adev_fwnode, "usb4-host-interface")) + if (typec_has_usb4_host_interface(adev_fwnode)) component_match_add(&arg->port->dev, &arg->match, usb4_port_compare, adev_fwnode); } From b263ff9b0fc5c1f371d65c4eb196b31263d039ff Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Sun, 20 Sep 2026 17:37:34 -0400 Subject: [PATCH 61/63] USB: gadget: dummy-hcd: Fix wait for outstanding request completions The dummy-hcd driver emulates synchronize_irq() by waiting until its private callback_usage counter drops to 0 (with the private lock not held). This counter is incremented whenever a gadget driver callback occurs (which requires the private lock to be dropped), but not when a request completion handler is called. This is an oversight. Request completion is triggered by timer interrupts (emulating device IRQs in a real UDC), and the interrupt handlers are supposed to have completed when the synchronize_irq() emulation routine returns -- they aren't supposed to be in the middle of a completion callback. If this happens it can lead to a gadget driver's unbind routine running before all outstanding request completions have finished, maybe even allowing the gadget driver's module to be unloaded while a completion handler is still running. Fix the oversight by incrementing the callback_usage value across request completion callbacks. Link: https://lore.kernel.org/linux-usb/7a87e293-633c-4100-aa8d-91560ba5e5c5@rowland.harvard.edu/ Fixes: 7dbd8f4cabd9 ("USB: dummy-hcd: Fix erroneous synchronization change") Tested-by: Minseo Kim Signed-off-by: Alan Stern Cc: stable Link: https://patch.msgid.link/f23b9e1a-f110-441a-a1d8-95f442ec09d5@rowland.harvard.edu Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/udc/dummy_hcd.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/usb/gadget/udc/dummy_hcd.c b/drivers/usb/gadget/udc/dummy_hcd.c index c0e40fa6dde5..0d30dd67b9ca 100644 --- a/drivers/usb/gadget/udc/dummy_hcd.c +++ b/drivers/usb/gadget/udc/dummy_hcd.c @@ -343,9 +343,11 @@ static void dummy_giveback(struct dummy *dum, struct usb_ep *_ep, { bool fifo = req == &dum->fifo_req; + ++dum->callback_usage; spin_unlock(&dum->lock); usb_gadget_giveback_request(_ep, &req->req); spin_lock(&dum->lock); + --dum->callback_usage; if (fifo) dum->fifo_req_busy = 0; } @@ -759,11 +761,13 @@ static int dummy_queue(struct usb_ep *_ep, struct usb_request *_req, req->req.complete = fifo_complete; list_add_tail(&req->queue, &ep->queue); + ++dum->callback_usage; spin_unlock(&dum->lock); _req->actual = _req->length; _req->status = 0; usb_gadget_giveback_request(_ep, _req); spin_lock(&dum->lock); + --dum->callback_usage; } else list_add_tail(&req->queue, &ep->queue); spin_unlock_irqrestore(&dum->lock, flags); From a3e981f42557d7bb4cb212462de463e0b6b8875b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 1 Oct 2026 10:16:44 +0200 Subject: [PATCH 62/63] Revert "usb: dwc3: gadget: fix IRQ storm on invalid event buffer count" This reverts commit a107edec57659c5a1a2f016311b944af58c904c9. It was incorrectly applied, and was 2 versions old. The "correct" one will be added instead afterward... Cc: stable Cc: Jiazi Liu Reported-by: Thinh Nguyen Fixes: a107edec5765 ("usb: dwc3: gadget: fix IRQ storm on invalid event buffer count") Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 ------- drivers/usb/dwc3/ep0.c | 9 ---- drivers/usb/dwc3/gadget.c | 105 +------------------------------------- 3 files changed, 2 insertions(+), 131 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index f3dabb37f806..608daeb7ef10 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,7 +49,6 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 -#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -842,12 +841,6 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; -enum dwc3_err_state { - DWC3_ERR_NONE = 0, - DWC3_ERR_RECOVERY, - DWC3_ERR_UNRECOVERABLE, -}; - /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1011,7 +1004,6 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping - * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1021,7 +1013,6 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching - * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1088,9 +1079,6 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state - * @err_state: current error recovery state. - * @err_recovery_count: number of consecutive error recovery attempts until - * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1201,7 +1189,6 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; - struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1216,9 +1203,6 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; - /* serializes pull-up run/stop vs error recovery */ - struct mutex connect_mutex; - struct device *dev; struct device *sysdev; @@ -1348,9 +1332,6 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; - enum dwc3_err_state err_state; - - u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 46a34c4a046b..310b5ffb236a 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,11 +200,6 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - ret = -ESHUTDOWN; - goto out; - } - if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -276,10 +271,6 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 13096ee475d3..f245e66cd13d 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,10 +1149,6 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2064,10 +2060,6 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2300,10 +2292,6 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2318,10 +2306,6 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2453,10 +2437,6 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2484,10 +2464,6 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); - if (dwc->err_state != DWC3_ERR_NONE) { - spin_unlock_irqrestore(&dwc->lock, flags); - return -ESHUTDOWN; - } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2857,13 +2833,10 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); - /* Serialize against dwc3_err_recovery_work() */ - mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); - mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4180,10 +4153,6 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; - /* The controller is recovered. */ - if (dwc->err_state == DWC3_ERR_NONE) - dwc->err_recovery_count = 0; - /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4671,12 +4640,6 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } -static void dwc3_schedule_err_recovery(struct dwc3 *dwc) -{ - dwc->err_state = DWC3_ERR_RECOVERY; - schedule_work(&dwc->err_recovery_work); -} - static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4710,21 +4673,9 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - /* - * This is a fatal error - the driver and controller are out of - * sync on which event has been consumed. Reinitializing the - * controller is required to recover. Write the bogus count back - * to GEVNTCOUNT to clear the IRQ source, consistent with the - * stale event clearing in dwc3_event_buffers_setup(), then - * schedule error recovery. - */ - spin_lock(&dwc->lock); - dwc3_schedule_err_recovery(dwc); - spin_unlock(&dwc->lock); - dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); - return IRQ_HANDLED; + return IRQ_NONE; } evt->count = count; @@ -4784,54 +4735,6 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } -static void dwc3_err_recovery_work(struct work_struct *work) -{ - struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); - unsigned long flags; - int ret; - - dwc->err_recovery_count++; - - /* serializes against dwc3_gadget_pullup() */ - mutex_lock(&dwc->connect_mutex); - - ret = dwc3_gadget_soft_disconnect(dwc); - - dwc3_disconnect_gadget_sleepable(dwc); - - if (ret) - goto err_unrecoverable; - - if (dwc->err_recovery_count > DWC3_ERR_RECOVERY_MAX) - goto err_unrecoverable; - - if (dwc->softconnect) { - /* - * Wait irq to finish before soft_connect resets evt->lpos and - * the event buffer registers to avoid racing with - * dwc3_process_event_buf(). - */ - synchronize_irq(dwc->irq_gadget); - - ret = dwc3_gadget_soft_connect(dwc); - if (ret) - goto err_unrecoverable; - } - mutex_unlock(&dwc->connect_mutex); - - spin_lock_irqsave(&dwc->lock, flags); - dwc->err_state = DWC3_ERR_NONE; - spin_unlock_irqrestore(&dwc->lock, flags); - return; - -err_unrecoverable: - dev_err(dwc->dev, "Unable to recover the controller\n"); - mutex_unlock(&dwc->connect_mutex); - spin_lock_irqsave(&dwc->lock, flags); - dwc->err_state = DWC3_ERR_UNRECOVERABLE; - spin_unlock_irqrestore(&dwc->lock, flags); -} - /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4875,8 +4778,6 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); - INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); - mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4973,8 +4874,6 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; - cancel_work_sync(&dwc->err_recovery_work); - mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc); From 1ff77cbefe5a653ea12874c213ea4bd0d72c1067 Mon Sep 17 00:00:00 2001 From: Jiazi Liu Date: Tue, 15 Sep 2026 19:06:37 +0800 Subject: [PATCH 63/63] usb: dwc3: gadget: fix IRQ storm on invalid event buffer count When dwc3_check_event_buf() reads a GEVNTCOUNT value exceeding the event buffer length, commit 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") returns IRQ_NONE without writing back GEVNTCOUNT. Since the DWC3 interrupt is level-triggered, the uncleared IRQ source keeps the line asserted, causing a tight IRQ storm that accumulates 99,900 unhandled interrupts and triggers spurious.c:184 BUG -> kernel panic. The resulting call stack: __report_bad_irq+0xac/0xc8 note_interrupt+0x340/0x468 handle_irq_event+0xac/0xc0 handle_fasteoi_irq+0x120/0x228 gic_handle_irq+0x68/0x108 ... kernel BUG at kernel/irq/spurious.c:184 To reproduce, write a bogus value exceeding the event buffer length directly to the GEVNTCOUNT register: devmem 4 0x1004 Write the bogus count back to GEVNTCOUNT to clear the IRQ source, consistent with the stale event clearing pattern in dwc3_event_buffers_setup(), and schedule error recovery to reinitialize the controller. Fixes: 63ccd26cd1f6 ("usb: dwc3: gadget: check that event count does not exceed event buffer length") Cc: stable Suggested-by: Thinh Nguyen Signed-off-by: Jiazi Liu Link: https://patch.msgid.link/20260915110637.17658-1-jiazi.liu1984@gmail.com Acked-by: Thinh Nguyen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/dwc3/core.h | 19 +++++++ drivers/usb/dwc3/ep0.c | 9 ++++ drivers/usb/dwc3/gadget.c | 108 +++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 608daeb7ef10..f3dabb37f806 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -49,6 +49,7 @@ #define DWC3_ENDPOINTS_NUM 32 #define DWC3_XHCI_RESOURCES_NUM 2 #define DWC3_ISOC_MAX_RETRIES 5 +#define DWC3_ERR_RECOVERY_MAX 3 #define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ #define DWC3_EVENT_BUFFERS_SIZE 4096 @@ -841,6 +842,12 @@ enum dwc3_link_state { DWC3_LINK_STATE_MASK = 0x0f, }; +enum dwc3_err_state { + DWC3_ERR_NONE = 0, + DWC3_ERR_RECOVERY, + DWC3_ERR_UNRECOVERABLE, +}; + /* TRB Length, PCM and Status */ #define DWC3_TRB_SIZE_MASK (0x00ffffff) #define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) @@ -1004,6 +1011,7 @@ struct dwc3_glue_ops { /** * struct dwc3 - representation of our controller * @drd_work: workqueue used for role swapping + * @err_recovery_work: workqueue used for controller error recovery * @ep0_trb: trb which is used for the ctrl_req * @bounce: address of bounce buffer * @setup_buf: used while precessing STD USB requests @@ -1013,6 +1021,7 @@ struct dwc3_glue_ops { * @ep0_in_setup: one control transfer is completed and enter setup phase * @lock: for synchronizing * @mutex: for mode switching + * @connect_mutex: for the pull-up and err_recovery_work * @dev: pointer to our struct device * @sysdev: pointer to the DMA-capable device * @xhci: pointer to our xHCI child @@ -1079,6 +1088,9 @@ struct dwc3_glue_ops { * @ep0_next_event: hold the next expected event * @ep0state: state of endpoint zero * @link_state: link state + * @err_state: current error recovery state. + * @err_recovery_count: number of consecutive error recovery attempts until + * confirmed healthy and reset to 0 on reset event. * @speed: device speed (super, high, full, low) * @hwparams: copy of hwparams registers * @regset: debugfs pointer to regdump file @@ -1189,6 +1201,7 @@ struct dwc3_glue_ops { */ struct dwc3 { struct work_struct drd_work; + struct work_struct err_recovery_work; struct dwc3_trb *ep0_trb; void *bounce; u8 *setup_buf; @@ -1203,6 +1216,9 @@ struct dwc3 { /* mode switching lock */ struct mutex mutex; + /* serializes pull-up run/stop vs error recovery */ + struct mutex connect_mutex; + struct device *dev; struct device *sysdev; @@ -1332,6 +1348,9 @@ struct dwc3 { enum dwc3_ep0_next ep0_next_event; enum dwc3_ep0_state ep0state; enum dwc3_link_state link_state; + enum dwc3_err_state err_state; + + u32 err_recovery_count; u16 u2sel; u16 u2pel; diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c index 310b5ffb236a..46a34c4a046b 100644 --- a/drivers/usb/dwc3/ep0.c +++ b/drivers/usb/dwc3/ep0.c @@ -200,6 +200,11 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + ret = -ESHUTDOWN; + goto out; + } + if (!dep->endpoint.desc || !dwc->pullups_connected || !dwc->connected) { dev_err(dwc->dev, "%s: can't queue to disabled endpoint\n", dep->name); @@ -271,6 +276,10 @@ int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep0_set_halt(ep, value); spin_unlock_irqrestore(&dwc->lock, flags); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index f245e66cd13d..ee837235630a 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1149,6 +1149,10 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep, return 0; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2060,6 +2064,10 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_queue(dep, req); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2292,6 +2300,10 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } ret = __dwc3_gadget_ep_set_halt(dep, value, false); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2306,6 +2318,10 @@ static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) int ret; spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } dep->flags |= DWC3_EP_WEDGE; if (dep->number == 0 || dep->number == 1) @@ -2437,6 +2453,10 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } if (!dwc->gadget->wakeup_armed) { dev_err(dwc->dev, "not armed for remote wakeup\n"); spin_unlock_irqrestore(&dwc->lock, flags); @@ -2464,6 +2484,10 @@ static int dwc3_gadget_func_wakeup(struct usb_gadget *g, int intf_id) } spin_lock_irqsave(&dwc->lock, flags); + if (dwc->err_state != DWC3_ERR_NONE) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ESHUTDOWN; + } /* * If the link is in U3, signal for remote wakeup and wait for the * link to transition to U0 before sending device notification. @@ -2833,10 +2857,13 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) synchronize_irq(dwc->irq_gadget); + /* Serialize against dwc3_err_recovery_work() */ + mutex_lock(&dwc->connect_mutex); if (!is_on) ret = dwc3_gadget_soft_disconnect(dwc); else ret = dwc3_gadget_soft_connect(dwc); + mutex_unlock(&dwc->connect_mutex); pm_runtime_put(dwc->dev); @@ -4153,6 +4180,10 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->suspended = false; + /* The controller is recovered. */ + if (dwc->err_state == DWC3_ERR_NONE) + dwc->err_recovery_count = 0; + /* * Ideally, dwc3_reset_gadget() would trigger the function * drivers to stop any active transfers through ep disable. @@ -4640,6 +4671,12 @@ static irqreturn_t dwc3_thread_interrupt(int irq, void *_evt) return ret; } +static void dwc3_schedule_err_recovery(struct dwc3 *dwc) +{ + dwc->err_state = DWC3_ERR_RECOVERY; + schedule_work(&dwc->err_recovery_work); +} + static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) { struct dwc3 *dwc = evt->dwc; @@ -4673,9 +4710,21 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt) return IRQ_NONE; if (count > evt->length) { - dev_err_ratelimited(dwc->dev, "invalid count(%u) > evt->length(%u)\n", + dev_err(dwc->dev, "invalid count(%u) > evt->length(%u)\n", count, evt->length); - return IRQ_NONE; + /* + * This is a fatal error - the driver and controller are out of + * sync on which event has been consumed. Reinitializing the + * controller is required to recover. Write the bogus count back + * to GEVNTCOUNT to clear the IRQ source, consistent with the + * stale event clearing in dwc3_event_buffers_setup(), then + * schedule error recovery. + */ + spin_lock(&dwc->lock); + dwc3_schedule_err_recovery(dwc); + spin_unlock(&dwc->lock); + dwc3_writel(dwc, DWC3_GEVNTCOUNT(0), count); + return IRQ_HANDLED; } evt->count = count; @@ -4735,6 +4784,57 @@ static void dwc_gadget_release(struct device *dev) kfree(gadget); } +static void dwc3_err_recovery_work(struct work_struct *work) +{ + struct dwc3 *dwc = container_of(work, struct dwc3, err_recovery_work); + unsigned long flags; + int ret; + + /* serializes against dwc3_gadget_pullup() */ + mutex_lock(&dwc->connect_mutex); + + ret = dwc3_gadget_soft_disconnect(dwc); + + dwc3_disconnect_gadget_sleepable(dwc); + + if (ret) + goto err_unrecoverable; + + if (dwc->softconnect) { + u32 count; + /* + * Wait irq to finish before soft_connect resets evt->lpos and + * the event buffer registers to avoid racing with + * dwc3_process_event_buf(). + */ + synchronize_irq(dwc->irq_gadget); + + spin_lock_irqsave(&dwc->lock, flags); + count = ++dwc->err_recovery_count; + spin_unlock_irqrestore(&dwc->lock, flags); + + if (count > DWC3_ERR_RECOVERY_MAX) + goto err_unrecoverable; + + ret = dwc3_gadget_soft_connect(dwc); + if (ret) + goto err_unrecoverable; + } + mutex_unlock(&dwc->connect_mutex); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_NONE; + spin_unlock_irqrestore(&dwc->lock, flags); + return; + +err_unrecoverable: + dev_err(dwc->dev, "Unable to recover the controller\n"); + mutex_unlock(&dwc->connect_mutex); + spin_lock_irqsave(&dwc->lock, flags); + dwc->err_state = DWC3_ERR_UNRECOVERABLE; + spin_unlock_irqrestore(&dwc->lock, flags); +} + /** * dwc3_gadget_init - initializes gadget related registers * @dwc: pointer to our controller context structure @@ -4778,6 +4878,8 @@ int dwc3_gadget_init(struct dwc3 *dwc) } init_completion(&dwc->ep0_in_setup); + INIT_WORK(&dwc->err_recovery_work, dwc3_err_recovery_work); + mutex_init(&dwc->connect_mutex); dwc->gadget = kzalloc_obj(struct usb_gadget); if (!dwc->gadget) { ret = -ENOMEM; @@ -4874,6 +4976,8 @@ void dwc3_gadget_exit(struct dwc3 *dwc) if (!dwc->gadget) return; + cancel_work_sync(&dwc->err_recovery_work); + mutex_destroy(&dwc->connect_mutex); dwc3_enable_susphy(dwc, true); usb_del_gadget(dwc->gadget); dwc3_gadget_free_endpoints(dwc);